feat(hub): add period date range and trade stats to inner-light-mind
Support today/week/month/custom range selection with sick count, PnL, and per-exchange breakdown; update docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,25 +1,58 @@
|
|||||||
# 中控币种档案与永久 K 线
|
# 内照明心与永久 K 线
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
「币种档案」页(`/archive`)按 **交易所 + 币种** 一行汇总历史已平仓记录,支持筛选、交易时间线、备注/犯病情绪标签,以及基于 **永久 5m 真源** 的 K 线大图(15m/1h/4h 由 5m 聚合)。
|
「内照明心」页(`/archive`)用于 **复盘语录 + 交易记录回顾 + 按需 K 线**。左侧维护每日复盘语录(最多 100 条);右侧按日期区间列出开仓记录,展示区间统计,并可展开 K 线图表对照单笔交易。
|
||||||
|
|
||||||
与行情区 `hub_kline.db`(15 天滚动缓存)**完全独立**:档案库只增不删,从建档起永久保留。
|
与行情区 `hub_kline.db`(15 天滚动缓存)**完全独立**:档案库只增不删,从建档起永久保留。
|
||||||
|
|
||||||
|
## 页面布局
|
||||||
|
|
||||||
|
| 区域 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **复盘语录** | 左栏;按日期添加/编辑/删除,一日一条 |
|
||||||
|
| **日期与筛选** | 顶栏:本日 / 本周 / 本月 / 自选区间;盈利单、亏损单、犯病、交易所、搜索 |
|
||||||
|
| **区间统计** | 统计栏随日期选择自动更新(见下) |
|
||||||
|
| **K 线图表** | 默认折叠;点「图表」或展开后按需加载 |
|
||||||
|
| **交易记录** | 默认展开;犯病行 **红色字体**(无红底);可编辑标签与备注 |
|
||||||
|
|
||||||
|
## 日期区间
|
||||||
|
|
||||||
|
交易日按北京时间 **8:00** 切日(`TRADING_DAY_RESET_HOUR`)。
|
||||||
|
|
||||||
|
| 模式 | 范围 |
|
||||||
|
|------|------|
|
||||||
|
| **本日** | 可选单个交易日(默认当前交易日) |
|
||||||
|
| **本周** | 当周周一至当前交易日 |
|
||||||
|
| **本月** | 当月 1 日至当前交易日 |
|
||||||
|
| **区间** | 自选 `date_from`~`date_to`(含首尾交易日) |
|
||||||
|
|
||||||
|
## 区间统计(统计栏)
|
||||||
|
|
||||||
|
基于所选日期区间内 **全部开仓**(不受盈利/亏损/犯病勾选与搜索影响;交易所筛选仍生效):
|
||||||
|
|
||||||
|
| 指标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 总开仓次数 | 区间内开仓笔数 |
|
||||||
|
| 犯病次数 / 占比 | `behavior_tag = sick` 的笔数及占开仓比例 |
|
||||||
|
| 盈亏 | 区间内全部已平仓盈亏合计 |
|
||||||
|
| 剔除犯病盈亏 | 排除犯病单后的盈亏合计 |
|
||||||
|
| 各交易所 | 每所:开仓、犯病、盈亏、剔除犯病盈亏 |
|
||||||
|
|
||||||
|
表格列表仍可按盈利单 / 亏损单 / 犯病 / 搜索进一步过滤。
|
||||||
|
|
||||||
## 数据约定
|
## 数据约定
|
||||||
|
|
||||||
| 项 | 约定 |
|
| 项 | 约定 |
|
||||||
|----|------|
|
|----|------|
|
||||||
| 列表粒度 | 一所一币一行 |
|
| 交易来源 | 四所 `trade_records` + 未落库的 `strategy_trade_snapshots`,经 `/api/hub/trades/archive` 拉取 |
|
||||||
| 交易来源 | 四所 `trade_records` + 未落库的 `strategy_trade_snapshots`(gate_bot 趋势漏记时补全),经 `/api/hub/trades/archive` 拉取 |
|
| 犯病标签 | 中控 `trade_overlay.behavior_tag = sick` |
|
||||||
| 筛选 | 交易所、有盈利单、有亏损单、犯病、情绪(中控 overlay) |
|
|
||||||
| K 线真源 | 仅 **5m** 写入 `hub_symbol_archive.db` |
|
| K 线真源 | 仅 **5m** 写入 `hub_symbol_archive.db` |
|
||||||
| 建档种子 | 该币 **最早开仓** 向前 **30 天** 5m |
|
| 建档种子 | 该币 **最早开仓** 向前 **30 天** 5m |
|
||||||
| 增量同步 | 默认每 **4 小时** 补新 5m 至当前 |
|
| 增量同步 | 默认每 **4 小时** 补新 5m 至当前 |
|
||||||
| 展示周期 | Tab:**5m / 15m / 1h / 4h**,默认 **15m** |
|
| 展示周期 | Tab:**5m / 15m / 1h / 4h**,默认 **15m** |
|
||||||
| 视窗模式 | **持仓过程**(锚平仓,默认)/ **进场决策**(锚开仓);历史段为建档→平仓,可拖动/滚轮缩放看建仓前全局,**不拉到「现在」** |
|
| 视窗模式 | **持仓过程**(锚平仓,默认)/ **进场决策**(锚开仓) |
|
||||||
| 时间跳转 | 上方输入 `YYYY-MM-DD HH:MM` 后点「跳转」 |
|
| 时间跳转 | 输入 `YYYY-MM-DD HH:MM` 后点「跳转」 |
|
||||||
| 图片 | **不上传** |
|
|
||||||
|
|
||||||
## 存储
|
## 存储
|
||||||
|
|
||||||
@@ -29,20 +62,37 @@
|
|||||||
- `archive_meta` — 建档元数据
|
- `archive_meta` — 建档元数据
|
||||||
- `archive_bars_5m` — 永久 5m K 线
|
- `archive_bars_5m` — 永久 5m K 线
|
||||||
- `archive_trade_cache` — 从实例同步的交易快照
|
- `archive_trade_cache` — 从实例同步的交易快照
|
||||||
- `trade_overlay` — 犯病/情绪标签与备注(仅中控)
|
- `trade_overlay` — 犯病标签与备注(仅中控)
|
||||||
|
- `archive_review_quotes` — 复盘语录
|
||||||
|
|
||||||
## API(中控 FastAPI)
|
## API(中控 FastAPI)
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| GET | `/api/archive/meta` | 周期、交易所、同步间隔等 |
|
| GET | `/api/archive/meta` | 周期、交易所、同步间隔等 |
|
||||||
| GET | `/api/archive/list` | 币种列表(筛选 query) |
|
| GET | `/api/archive/daily-trades` | 区间交易列表与统计(见 query) |
|
||||||
| GET | `/api/archive/detail` | 单币种交易时间线 |
|
| GET | `/api/archive/quotes` | 复盘语录列表 |
|
||||||
|
| POST | `/api/archive/quotes` | 新增语录 |
|
||||||
|
| PATCH | `/api/archive/quotes/{id}` | 更新语录 |
|
||||||
|
| DELETE | `/api/archive/quotes/{id}` | 删除语录 |
|
||||||
| GET | `/api/archive/ohlcv` | K 线视窗(`timeframe` / `mode` / `anchor_ms` / `at`) |
|
| GET | `/api/archive/ohlcv` | K 线视窗(`timeframe` / `mode` / `anchor_ms` / `at`) |
|
||||||
| PATCH | `/api/archive/trade/{exchange_key}/{trade_id}` | 更新标签/备注 |
|
| PATCH | `/api/archive/trade/{exchange_key}/{trade_id}` | 更新标签/备注 |
|
||||||
| POST | `/api/archive/sync` | 立即同步四所交易 + K 线 |
|
| POST | `/api/archive/sync` | 立即同步四所交易 + K 线 |
|
||||||
|
|
||||||
实例侧新增:
|
`GET /api/archive/daily-trades` 主要 query:
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `period` | `today` / `week` / `month` / `range` |
|
||||||
|
| `trading_day` | 本日模式下的交易日 `YYYY-MM-DD` |
|
||||||
|
| `date_from` / `date_to` | 区间模式起止日 |
|
||||||
|
| `exchange_key` | 可选,按交易所筛选 |
|
||||||
|
| `filter_profit` / `filter_loss` / `filter_sick` | 仅过滤表格列表 |
|
||||||
|
| `search` | 合约 / 交易所 / 备注搜索(仅列表) |
|
||||||
|
|
||||||
|
返回 `stats` 含 `open_count`、`sick_count`、`sick_pct`、`pnl_total`、`pnl_ex_sick`、`by_exchange`。
|
||||||
|
|
||||||
|
实例侧:
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
@@ -61,20 +111,20 @@ Hub 启动后在 lifespan 中运行 `hub-archive-sync`:
|
|||||||
|
|
||||||
## 代码位置
|
## 代码位置
|
||||||
|
|
||||||
- `hub_symbol_archive_lib.py` — 库表、种子、增量、聚合、列表
|
- `hub_symbol_archive_lib.py` — 库表、区间统计、种子、增量、聚合
|
||||||
- `hub_trades_lib.py` — `fetch_trades_for_archive`
|
- `hub_trades_lib.py` — `fetch_trades_for_archive`
|
||||||
- `hub_bridge.py` — 实例 `/api/hub/trades/archive`
|
- `hub_bridge.py` — 实例 `/api/hub/trades/archive`
|
||||||
- `manual_trading_hub/hub.py` — 路由与后台同步
|
- `manual_trading_hub/hub.py` — 路由与后台同步
|
||||||
- `manual_trading_hub/static/archive.js` — 前端页
|
- `manual_trading_hub/static/archive.js` — 内照明心前端
|
||||||
|
|
||||||
## 与行情区的区别
|
## 与行情区的区别
|
||||||
|
|
||||||
| | 行情区 | 币种档案 |
|
| | 行情区 | 内照明心 |
|
||||||
|--|--------|----------|
|
|--|--------|----------|
|
||||||
| DB | `hub_kline.db` | `hub_symbol_archive.db` |
|
| DB | `hub_kline.db` | `hub_symbol_archive.db` |
|
||||||
| 保留 | 15 天滚动删除 | 建档起永久 |
|
| 保留 | 15 天滚动删除 | 建档起永久 |
|
||||||
| 周期 | 多周期直存/拉取 | 仅存 5m,高周期聚合 |
|
| 周期 | 多周期直存/拉取 | 仅存 5m,高周期聚合 |
|
||||||
| 用途 | 实时看盘 | 复盘与档案 |
|
| 用途 | 实时看盘 | 复盘语录与交易回顾 |
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
|
|||||||
+106
-20
@@ -1195,6 +1195,93 @@ def trading_day_bounds_ms(
|
|||||||
return int(start.timestamp() * 1000), int(end.timestamp() * 1000)
|
return int(start.timestamp() * 1000), int(end.timestamp() * 1000)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_period_bounds(
|
||||||
|
*,
|
||||||
|
period: str = "",
|
||||||
|
trading_day: str = "",
|
||||||
|
date_from: str = "",
|
||||||
|
date_to: str = "",
|
||||||
|
reset_hour: int = TRADING_DAY_RESET_HOUR,
|
||||||
|
) -> tuple[int, int, str, str, str]:
|
||||||
|
"""返回 (start_ms, end_ms, date_from, date_to, period_label)。"""
|
||||||
|
td = today_trading_day(reset_hour=reset_hour)
|
||||||
|
p = (period or "today").strip().lower()
|
||||||
|
if p in ("day", "today", ""):
|
||||||
|
d = (trading_day or "").strip()[:10] or td
|
||||||
|
start_ms, end_ms = trading_day_bounds_ms(d, reset_hour=reset_hour)
|
||||||
|
return start_ms, end_ms, d, d, f"本日 {d}"
|
||||||
|
if p == "week":
|
||||||
|
day_dt = datetime.strptime(td, "%Y-%m-%d")
|
||||||
|
monday = day_dt - timedelta(days=day_dt.weekday())
|
||||||
|
df = monday.strftime("%Y-%m-%d")
|
||||||
|
start_ms, _ = trading_day_bounds_ms(df, reset_hour=reset_hour)
|
||||||
|
_, end_ms = trading_day_bounds_ms(td, reset_hour=reset_hour)
|
||||||
|
return start_ms, end_ms, df, td, f"本周 {df}~{td}"
|
||||||
|
if p == "month":
|
||||||
|
day_dt = datetime.strptime(td, "%Y-%m-%d")
|
||||||
|
first = day_dt.replace(day=1)
|
||||||
|
df = first.strftime("%Y-%m-%d")
|
||||||
|
start_ms, _ = trading_day_bounds_ms(df, reset_hour=reset_hour)
|
||||||
|
_, end_ms = trading_day_bounds_ms(td, reset_hour=reset_hour)
|
||||||
|
return start_ms, end_ms, df, td, f"本月 {df}~{td}"
|
||||||
|
if p == "range":
|
||||||
|
df = (date_from or "").strip()[:10] or td
|
||||||
|
dt = (date_to or "").strip()[:10] or df
|
||||||
|
if df > dt:
|
||||||
|
df, dt = dt, df
|
||||||
|
start_ms, _ = trading_day_bounds_ms(df, reset_hour=reset_hour)
|
||||||
|
_, end_ms = trading_day_bounds_ms(dt, reset_hour=reset_hour)
|
||||||
|
label = f"区间 {df}~{dt}" if df != dt else f"区间 {df}"
|
||||||
|
return start_ms, end_ms, df, dt, label
|
||||||
|
d = (trading_day or "").strip()[:10] or td
|
||||||
|
start_ms, end_ms = trading_day_bounds_ms(d, reset_hour=reset_hour)
|
||||||
|
return start_ms, end_ms, d, d, f"本日 {d}"
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_period_stats(trade_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||||
|
total = len(trade_rows)
|
||||||
|
sick = 0
|
||||||
|
pnl_all = 0.0
|
||||||
|
pnl_ex = 0.0
|
||||||
|
by_ex: dict[str, dict[str, Any]] = {}
|
||||||
|
for td_row in trade_rows:
|
||||||
|
ex = str(td_row.get("exchange_key") or "?")
|
||||||
|
pnl = float(td_row.get("pnl_amount") or 0)
|
||||||
|
tag = str(td_row.get("behavior_tag") or "")
|
||||||
|
is_sick = tag == "sick"
|
||||||
|
if is_sick:
|
||||||
|
sick += 1
|
||||||
|
pnl_all += pnl
|
||||||
|
if not is_sick:
|
||||||
|
pnl_ex += pnl
|
||||||
|
if ex not in by_ex:
|
||||||
|
by_ex[ex] = {
|
||||||
|
"open_count": 0,
|
||||||
|
"sick_count": 0,
|
||||||
|
"pnl_total": 0.0,
|
||||||
|
"pnl_ex_sick": 0.0,
|
||||||
|
}
|
||||||
|
bucket = by_ex[ex]
|
||||||
|
bucket["open_count"] += 1
|
||||||
|
bucket["pnl_total"] += pnl
|
||||||
|
if is_sick:
|
||||||
|
bucket["sick_count"] += 1
|
||||||
|
else:
|
||||||
|
bucket["pnl_ex_sick"] += pnl
|
||||||
|
for ex in by_ex:
|
||||||
|
by_ex[ex]["pnl_total"] = round(by_ex[ex]["pnl_total"], 4)
|
||||||
|
by_ex[ex]["pnl_ex_sick"] = round(by_ex[ex]["pnl_ex_sick"], 4)
|
||||||
|
sick_pct = round(sick / total * 100, 1) if total else 0.0
|
||||||
|
return {
|
||||||
|
"open_count": total,
|
||||||
|
"sick_count": sick,
|
||||||
|
"sick_pct": sick_pct,
|
||||||
|
"pnl_total": round(pnl_all, 4),
|
||||||
|
"pnl_ex_sick": round(pnl_ex, 4),
|
||||||
|
"by_exchange": by_ex,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def list_review_quotes(*, db_path: Path | None = None) -> list[dict[str, Any]]:
|
def list_review_quotes(*, db_path: Path | None = None) -> list[dict[str, Any]]:
|
||||||
init_db(db_path)
|
init_db(db_path)
|
||||||
conn = _connect(db_path)
|
conn = _connect(db_path)
|
||||||
@@ -1310,6 +1397,9 @@ def delete_review_quote(quote_id: int, *, db_path: Path | None = None) -> bool:
|
|||||||
def list_daily_trades(
|
def list_daily_trades(
|
||||||
trading_day: str = "",
|
trading_day: str = "",
|
||||||
*,
|
*,
|
||||||
|
period: str = "",
|
||||||
|
date_from: str = "",
|
||||||
|
date_to: str = "",
|
||||||
exchange_key: str = "",
|
exchange_key: str = "",
|
||||||
filter_profit: bool = False,
|
filter_profit: bool = False,
|
||||||
filter_loss: bool = False,
|
filter_loss: bool = False,
|
||||||
@@ -1317,10 +1407,15 @@ def list_daily_trades(
|
|||||||
search: str = "",
|
search: str = "",
|
||||||
db_path: Path | None = None,
|
db_path: Path | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""按交易日列出开仓记录(默认当日),含各所开仓统计。"""
|
"""按日期区间列出开仓记录(本日/本周/本月/自选),含犯病与盈亏统计。"""
|
||||||
init_db(db_path)
|
init_db(db_path)
|
||||||
td = (trading_day or "").strip()[:10] or today_trading_day()
|
p = (period or "today").strip().lower() or "today"
|
||||||
start_ms, end_ms = trading_day_bounds_ms(td)
|
start_ms, end_ms, df, dt, period_label = resolve_period_bounds(
|
||||||
|
period=p,
|
||||||
|
trading_day=trading_day,
|
||||||
|
date_from=date_from,
|
||||||
|
date_to=date_to,
|
||||||
|
)
|
||||||
ex_filter = (exchange_key or "").strip().lower()
|
ex_filter = (exchange_key or "").strip().lower()
|
||||||
conn = _connect(db_path)
|
conn = _connect(db_path)
|
||||||
try:
|
try:
|
||||||
@@ -1329,15 +1424,6 @@ def list_daily_trades(
|
|||||||
if ex_filter:
|
if ex_filter:
|
||||||
where += " AND exchange_key=?"
|
where += " AND exchange_key=?"
|
||||||
params.append(ex_filter)
|
params.append(ex_filter)
|
||||||
stat_rows = conn.execute(
|
|
||||||
f"""
|
|
||||||
SELECT exchange_key, COUNT(*) AS open_count
|
|
||||||
FROM archive_trade_cache
|
|
||||||
WHERE {where}
|
|
||||||
GROUP BY exchange_key
|
|
||||||
""",
|
|
||||||
params,
|
|
||||||
).fetchall()
|
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
f"""
|
f"""
|
||||||
SELECT * FROM archive_trade_cache
|
SELECT * FROM archive_trade_cache
|
||||||
@@ -1347,6 +1433,7 @@ def list_daily_trades(
|
|||||||
params,
|
params,
|
||||||
).fetchall()
|
).fetchall()
|
||||||
overlays_by_ex: dict[str, dict[int, dict]] = {}
|
overlays_by_ex: dict[str, dict[int, dict]] = {}
|
||||||
|
all_rows: list[dict[str, Any]] = []
|
||||||
trades: list[dict[str, Any]] = []
|
trades: list[dict[str, Any]] = []
|
||||||
q = (search or "").strip().lower()
|
q = (search or "").strip().lower()
|
||||||
for r in rows:
|
for r in rows:
|
||||||
@@ -1354,6 +1441,7 @@ def list_daily_trades(
|
|||||||
if ex_k not in overlays_by_ex:
|
if ex_k not in overlays_by_ex:
|
||||||
overlays_by_ex[ex_k] = load_overlays(ex_k, db_path=db_path)
|
overlays_by_ex[ex_k] = load_overlays(ex_k, db_path=db_path)
|
||||||
td_row = _trade_row_to_dict(r, overlays_by_ex[ex_k].get(int(r["trade_id"])))
|
td_row = _trade_row_to_dict(r, overlays_by_ex[ex_k].get(int(r["trade_id"])))
|
||||||
|
all_rows.append(td_row)
|
||||||
pnl = float(td_row.get("pnl_amount") or 0)
|
pnl = float(td_row.get("pnl_amount") or 0)
|
||||||
tag = td_row.get("behavior_tag") or ""
|
tag = td_row.get("behavior_tag") or ""
|
||||||
if filter_profit and pnl <= 0.0001:
|
if filter_profit and pnl <= 0.0001:
|
||||||
@@ -1378,16 +1466,14 @@ def list_daily_trades(
|
|||||||
if q not in blob:
|
if q not in blob:
|
||||||
continue
|
continue
|
||||||
trades.append(td_row)
|
trades.append(td_row)
|
||||||
by_exchange = {
|
|
||||||
str(sr["exchange_key"]): int(sr["open_count"] or 0) for sr in stat_rows
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
"trading_day": td,
|
"period": p,
|
||||||
|
"period_label": period_label,
|
||||||
|
"trading_day": dt,
|
||||||
|
"date_from": df,
|
||||||
|
"date_to": dt,
|
||||||
"trades": trades,
|
"trades": trades,
|
||||||
"stats": {
|
"stats": _compute_period_stats(all_rows),
|
||||||
"open_count": sum(by_exchange.values()),
|
|
||||||
"by_exchange": by_exchange,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -2057,7 +2057,10 @@ def api_archive_list(
|
|||||||
|
|
||||||
@app.get("/api/archive/daily-trades")
|
@app.get("/api/archive/daily-trades")
|
||||||
def api_archive_daily_trades(
|
def api_archive_daily_trades(
|
||||||
|
period: str = "",
|
||||||
trading_day: str = "",
|
trading_day: str = "",
|
||||||
|
date_from: str = "",
|
||||||
|
date_to: str = "",
|
||||||
exchange_key: str = "",
|
exchange_key: str = "",
|
||||||
filter_profit: str = "",
|
filter_profit: str = "",
|
||||||
filter_loss: str = "",
|
filter_loss: str = "",
|
||||||
@@ -2066,7 +2069,10 @@ def api_archive_daily_trades(
|
|||||||
):
|
):
|
||||||
init_archive_db()
|
init_archive_db()
|
||||||
payload = list_daily_trades(
|
payload = list_daily_trades(
|
||||||
trading_day=trading_day or today_trading_day(),
|
trading_day=trading_day,
|
||||||
|
period=period or "today",
|
||||||
|
date_from=date_from,
|
||||||
|
date_to=date_to,
|
||||||
exchange_key=exchange_key,
|
exchange_key=exchange_key,
|
||||||
filter_profit=(filter_profit or "").lower() in ("1", "true", "yes", "on"),
|
filter_profit=(filter_profit or "").lower() in ("1", "true", "yes", "on"),
|
||||||
filter_loss=(filter_loss or "").lower() in ("1", "true", "yes", "on"),
|
filter_loss=(filter_loss or "").lower() in ("1", "true", "yes", "on"),
|
||||||
|
|||||||
@@ -5570,6 +5570,41 @@ body.funds-fullscreen-open {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
.archive-period-bar {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.archive-period-tabs {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.archive-period-btn {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
background: var(--inset-surface);
|
||||||
|
color: var(--muted);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.archive-period-btn.is-active {
|
||||||
|
color: var(--text);
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||||
|
}
|
||||||
|
.archive-period-range {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.archive-period-range.hidden,
|
||||||
|
.archive-period-day-input.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.archive-period-sep {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
.archive-stats-bar {
|
.archive-stats-bar {
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -5578,6 +5613,13 @@ body.funds-fullscreen-open {
|
|||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.archive-stats-sub {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
.archive-acc-section {
|
.archive-acc-section {
|
||||||
border: 1px solid var(--border-soft);
|
border: 1px solid var(--border-soft);
|
||||||
|
|||||||
@@ -9,7 +9,11 @@
|
|||||||
const elFilterProfit = document.getElementById("archive-filter-profit");
|
const elFilterProfit = document.getElementById("archive-filter-profit");
|
||||||
const elFilterLoss = document.getElementById("archive-filter-loss");
|
const elFilterLoss = document.getElementById("archive-filter-loss");
|
||||||
const elFilterSick = document.getElementById("archive-filter-sick");
|
const elFilterSick = document.getElementById("archive-filter-sick");
|
||||||
|
const elPeriodTabs = document.getElementById("archive-period-tabs");
|
||||||
const elTradingDay = document.getElementById("archive-trading-day");
|
const elTradingDay = document.getElementById("archive-trading-day");
|
||||||
|
const elPeriodRangeWrap = document.getElementById("archive-period-range-wrap");
|
||||||
|
const elDateFrom = document.getElementById("archive-date-from");
|
||||||
|
const elDateTo = document.getElementById("archive-date-to");
|
||||||
const elSearch = document.getElementById("archive-search");
|
const elSearch = document.getElementById("archive-search");
|
||||||
const elBtnChartToggle = document.getElementById("archive-btn-chart-toggle");
|
const elBtnChartToggle = document.getElementById("archive-btn-chart-toggle");
|
||||||
const elBtnRefresh = document.getElementById("archive-btn-refresh");
|
const elBtnRefresh = document.getElementById("archive-btn-refresh");
|
||||||
@@ -45,6 +49,10 @@
|
|||||||
let quotes = [];
|
let quotes = [];
|
||||||
let dailyTrades = [];
|
let dailyTrades = [];
|
||||||
let dailyStats = { open_count: 0, by_exchange: {} };
|
let dailyStats = { open_count: 0, by_exchange: {} };
|
||||||
|
let periodMode = "today";
|
||||||
|
let periodLabel = "";
|
||||||
|
let dateFrom = "";
|
||||||
|
let dateTo = "";
|
||||||
let tradingDay = "";
|
let tradingDay = "";
|
||||||
let selected = null;
|
let selected = null;
|
||||||
let trades = [];
|
let trades = [];
|
||||||
@@ -292,9 +300,35 @@
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncPeriodUI() {
|
||||||
|
if (elPeriodTabs) {
|
||||||
|
elPeriodTabs.querySelectorAll(".archive-period-btn").forEach(function (btn) {
|
||||||
|
btn.classList.toggle("is-active", btn.getAttribute("data-period") === periodMode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (elTradingDay) {
|
||||||
|
elTradingDay.classList.toggle("hidden", periodMode !== "today");
|
||||||
|
}
|
||||||
|
if (elPeriodRangeWrap) {
|
||||||
|
elPeriodRangeWrap.classList.toggle("hidden", periodMode !== "range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPeriodMode(mode) {
|
||||||
|
periodMode = mode || "today";
|
||||||
|
syncPeriodUI();
|
||||||
|
}
|
||||||
|
|
||||||
function queryDailyParams() {
|
function queryDailyParams() {
|
||||||
const q = new URLSearchParams();
|
const q = new URLSearchParams();
|
||||||
if (elTradingDay && elTradingDay.value) q.set("trading_day", elTradingDay.value);
|
q.set("period", periodMode);
|
||||||
|
if (periodMode === "today" && elTradingDay && elTradingDay.value) {
|
||||||
|
q.set("trading_day", elTradingDay.value);
|
||||||
|
}
|
||||||
|
if (periodMode === "range") {
|
||||||
|
if (elDateFrom && elDateFrom.value) q.set("date_from", elDateFrom.value);
|
||||||
|
if (elDateTo && elDateTo.value) q.set("date_to", elDateTo.value);
|
||||||
|
}
|
||||||
const ex = (elExchange && elExchange.value) || "";
|
const ex = (elExchange && elExchange.value) || "";
|
||||||
if (ex) q.set("exchange_key", ex);
|
if (ex) q.set("exchange_key", ex);
|
||||||
if (elFilterProfit && elFilterProfit.checked) q.set("filter_profit", "1");
|
if (elFilterProfit && elFilterProfit.checked) q.set("filter_profit", "1");
|
||||||
@@ -304,6 +338,14 @@
|
|||||||
return q.toString();
|
return q.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtPnlStat(v) {
|
||||||
|
const n = Number(v);
|
||||||
|
if (!Number.isFinite(n)) return "—";
|
||||||
|
const cls = n >= 0 ? "pnl-pos" : "pnl-neg";
|
||||||
|
const text = (n >= 0 ? "+" : "") + n.toFixed(2) + "U";
|
||||||
|
return '<span class="' + cls + '">' + text + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
function renderExchangeOptions() {
|
function renderExchangeOptions() {
|
||||||
if (!elExchange || !meta) return;
|
if (!elExchange || !meta) return;
|
||||||
const cur = elExchange.value;
|
const cur = elExchange.value;
|
||||||
@@ -320,14 +362,47 @@
|
|||||||
function renderStats() {
|
function renderStats() {
|
||||||
if (!elStats) return;
|
if (!elStats) return;
|
||||||
const st = dailyStats || { open_count: 0, by_exchange: {} };
|
const st = dailyStats || { open_count: 0, by_exchange: {} };
|
||||||
const parts = ["今日开仓 " + (st.open_count || 0) + " 次"];
|
const label = periodLabel || "本日";
|
||||||
|
const sickPct = st.sick_pct != null ? st.sick_pct : 0;
|
||||||
|
let html =
|
||||||
|
'<div class="archive-stats-line"><strong>' +
|
||||||
|
esc(label) +
|
||||||
|
"</strong> · 开仓 " +
|
||||||
|
(st.open_count || 0) +
|
||||||
|
" 次 · 犯病 " +
|
||||||
|
(st.sick_count || 0) +
|
||||||
|
" 次(" +
|
||||||
|
sickPct +
|
||||||
|
"%) · 盈亏 " +
|
||||||
|
fmtPnlStat(st.pnl_total) +
|
||||||
|
" · 剔除犯病盈亏 " +
|
||||||
|
fmtPnlStat(st.pnl_ex_sick) +
|
||||||
|
"</div>";
|
||||||
const byEx = st.by_exchange || {};
|
const byEx = st.by_exchange || {};
|
||||||
Object.keys(byEx)
|
const exKeys = Object.keys(byEx).sort();
|
||||||
.sort()
|
if (exKeys.length) {
|
||||||
.forEach(function (ex) {
|
const exParts = exKeys.map(function (ex) {
|
||||||
parts.push(exchangeLabel(ex) + " " + byEx[ex]);
|
const e = byEx[ex] || {};
|
||||||
|
const sickN = e.sick_count || 0;
|
||||||
|
const openN = e.open_count || 0;
|
||||||
|
const sickShare = openN ? Math.round((sickN / openN) * 1000) / 10 : 0;
|
||||||
|
return (
|
||||||
|
esc(exchangeLabel(ex)) +
|
||||||
|
" " +
|
||||||
|
openN +
|
||||||
|
"次/犯病" +
|
||||||
|
sickN +
|
||||||
|
"(" +
|
||||||
|
sickShare +
|
||||||
|
"%)/盈亏" +
|
||||||
|
fmtPnlStat(e.pnl_total) +
|
||||||
|
"/剔犯" +
|
||||||
|
fmtPnlStat(e.pnl_ex_sick)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
elStats.textContent = parts.join(" · ");
|
html += '<div class="archive-stats-sub">' + exParts.join(" · ") + "</div>";
|
||||||
|
}
|
||||||
|
elStats.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function quotePreview(text) {
|
function quotePreview(text) {
|
||||||
@@ -990,15 +1065,26 @@
|
|||||||
setStatus(j.detail || "加载失败");
|
setStatus(j.detail || "加载失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
periodMode = j.period || periodMode || "today";
|
||||||
|
periodLabel = j.period_label || periodLabel || "";
|
||||||
|
dateFrom = j.date_from || dateFrom || "";
|
||||||
|
dateTo = j.date_to || dateTo || "";
|
||||||
tradingDay = j.trading_day || tradingDay;
|
tradingDay = j.trading_day || tradingDay;
|
||||||
if (elTradingDay && tradingDay && !elTradingDay.value) elTradingDay.value = tradingDay;
|
if (elTradingDay && tradingDay) elTradingDay.value = tradingDay;
|
||||||
|
if (elDateFrom && dateFrom) elDateFrom.value = dateFrom;
|
||||||
|
if (elDateTo && dateTo) elDateTo.value = dateTo;
|
||||||
if (elQuoteDate && tradingDay && !elQuoteDate.value) elQuoteDate.value = tradingDay;
|
if (elQuoteDate && tradingDay && !elQuoteDate.value) elQuoteDate.value = tradingDay;
|
||||||
|
syncPeriodUI();
|
||||||
dailyTrades = j.trades || [];
|
dailyTrades = j.trades || [];
|
||||||
dailyStats = j.stats || { open_count: 0, by_exchange: {} };
|
dailyStats = j.stats || { open_count: 0, by_exchange: {} };
|
||||||
renderStats();
|
renderStats();
|
||||||
renderTrades();
|
renderTrades();
|
||||||
setStatus(
|
setStatus(
|
||||||
(tradingDay || "当日") + " · " + dailyTrades.length + " 笔 · " + new Date().toLocaleTimeString()
|
(periodLabel || tradingDay || "当日") +
|
||||||
|
" · 列表 " +
|
||||||
|
dailyTrades.length +
|
||||||
|
" 笔 · " +
|
||||||
|
new Date().toLocaleTimeString()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,7 +1142,19 @@
|
|||||||
if (elBtnRefresh) elBtnRefresh.addEventListener("click", loadDailyTrades);
|
if (elBtnRefresh) elBtnRefresh.addEventListener("click", loadDailyTrades);
|
||||||
if (elBtnSync) elBtnSync.addEventListener("click", syncAll);
|
if (elBtnSync) elBtnSync.addEventListener("click", syncAll);
|
||||||
if (elExchange) elExchange.addEventListener("change", loadDailyTrades);
|
if (elExchange) elExchange.addEventListener("change", loadDailyTrades);
|
||||||
|
if (elPeriodTabs) {
|
||||||
|
elPeriodTabs.addEventListener("click", function (ev) {
|
||||||
|
const btn = ev.target.closest(".archive-period-btn");
|
||||||
|
if (!btn) return;
|
||||||
|
const next = btn.getAttribute("data-period") || "today";
|
||||||
|
if (next === periodMode) return;
|
||||||
|
setPeriodMode(next);
|
||||||
|
loadDailyTrades();
|
||||||
|
});
|
||||||
|
}
|
||||||
if (elTradingDay) elTradingDay.addEventListener("change", loadDailyTrades);
|
if (elTradingDay) elTradingDay.addEventListener("change", loadDailyTrades);
|
||||||
|
if (elDateFrom) elDateFrom.addEventListener("change", loadDailyTrades);
|
||||||
|
if (elDateTo) elDateTo.addEventListener("change", loadDailyTrades);
|
||||||
[elFilterProfit, elFilterLoss, elFilterSick].forEach(function (el) {
|
[elFilterProfit, elFilterLoss, elFilterSick].forEach(function (el) {
|
||||||
if (el) el.addEventListener("change", loadDailyTrades);
|
if (el) el.addEventListener("change", loadDailyTrades);
|
||||||
});
|
});
|
||||||
@@ -1117,6 +1215,7 @@
|
|||||||
if (!inited) {
|
if (!inited) {
|
||||||
loadMarkAutoPref();
|
loadMarkAutoPref();
|
||||||
setChartOpen(false);
|
setChartOpen(false);
|
||||||
|
syncPeriodUI();
|
||||||
bindEvents();
|
bindEvents();
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,10 +266,21 @@
|
|||||||
<label class="chk-label"><input type="checkbox" id="archive-filter-profit" /> 盈利单</label>
|
<label class="chk-label"><input type="checkbox" id="archive-filter-profit" /> 盈利单</label>
|
||||||
<label class="chk-label"><input type="checkbox" id="archive-filter-loss" /> 亏损单</label>
|
<label class="chk-label"><input type="checkbox" id="archive-filter-loss" /> 亏损单</label>
|
||||||
<label class="chk-label"><input type="checkbox" id="archive-filter-sick" /> 犯病</label>
|
<label class="chk-label"><input type="checkbox" id="archive-filter-sick" /> 犯病</label>
|
||||||
<label class="archive-field">
|
<div class="archive-period-bar archive-field">
|
||||||
<span>日期</span>
|
<span>日期</span>
|
||||||
<input id="archive-trading-day" type="date" />
|
<div class="archive-period-tabs" id="archive-period-tabs" role="tablist">
|
||||||
</label>
|
<button type="button" class="archive-period-btn is-active" data-period="today">本日</button>
|
||||||
|
<button type="button" class="archive-period-btn" data-period="week">本周</button>
|
||||||
|
<button type="button" class="archive-period-btn" data-period="month">本月</button>
|
||||||
|
<button type="button" class="archive-period-btn" data-period="range">区间</button>
|
||||||
|
</div>
|
||||||
|
<input id="archive-trading-day" type="date" class="archive-period-day-input" title="本日交易日" />
|
||||||
|
<span id="archive-period-range-wrap" class="archive-period-range hidden">
|
||||||
|
<input id="archive-date-from" type="date" title="起始日" />
|
||||||
|
<span class="archive-period-sep">~</span>
|
||||||
|
<input id="archive-date-to" type="date" title="结束日" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<button type="button" id="archive-btn-chart-toggle" class="ghost">图表</button>
|
<button type="button" id="archive-btn-chart-toggle" class="ghost">图表</button>
|
||||||
<label class="archive-field archive-search-field">
|
<label class="archive-field archive-search-field">
|
||||||
<span>搜索</span>
|
<span>搜索</span>
|
||||||
@@ -573,7 +584,7 @@
|
|||||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||||
<script src="/assets/chart_draw.js?v=20260609-market-day-split"></script>
|
<script src="/assets/chart_draw.js?v=20260609-market-day-split"></script>
|
||||||
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
|
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
|
||||||
<script src="/assets/archive.js?v=20260612-inner-light-fix"></script>
|
<script src="/assets/archive.js?v=20260612-period-stats"></script>
|
||||||
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
||||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||||
|
|||||||
+16
-12
@@ -1,6 +1,6 @@
|
|||||||
# 多账户交易中控 — 使用说明
|
# 多账户交易中控 — 使用说明
|
||||||
|
|
||||||
本文档说明 **manual_trading_hub** 的架构、启动方式、界面操作与故障排查。中控聚合四所 **持仓/条件单/余额/关键位/趋势计划监控 + 撤单/紧急全平**,并提供 **资金概况**、**行情区 K 线** 与 **币种档案(永久 K 线复盘)**;**人工下单、关键位、策略交易(趋势回调 / 顺势加仓)、交易复盘** 均在各实例网页操作(点监控卡片 **「实例」**)。资金概况见 **[资金概况说明.md](./资金概况说明.md)**;行情区细则见 **[行情区说明.md](./行情区说明.md)**;币种档案见 **[docs/hub-symbol-archive-kline.md](../docs/hub-symbol-archive-kline.md)**。
|
本文档说明 **manual_trading_hub** 的架构、启动方式、界面操作与故障排查。中控聚合四所 **持仓/条件单/余额/关键位/趋势计划监控 + 撤单/紧急全平**,并提供 **资金概况**、**行情区 K 线** 与 **内照明心(复盘语录 + 永久 K 线)**;**人工下单、关键位、策略交易(趋势回调 / 顺势加仓)、交易复盘** 均在各实例网页操作(点监控卡片 **「实例」**)。资金概况见 **[资金概况说明.md](./资金概况说明.md)**;行情区细则见 **[行情区说明.md](./行情区说明.md)**;内照明心见 **[docs/hub-symbol-archive-kline.md](../docs/hub-symbol-archive-kline.md)**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
浏览器
|
浏览器
|
||||||
├─ /monitor 监控区(持仓、关键位、趋势计划、全平)
|
├─ /monitor 监控区(持仓、关键位、趋势计划、全平)
|
||||||
├─ /market 行情区(K 线、技术指标、持仓价格线)
|
├─ /market 行情区(K 线、技术指标、持仓价格线)
|
||||||
├─ /archive 币种档案(交易时间线 + 永久 5m K 线)
|
├─ /archive 内照明心(复盘语录 + 交易记录 + 永久 5m K 线)
|
||||||
├─ /funds 资金概况(总资金曲线、分户资金与回撤)
|
├─ /funds 资金概况(总资金曲线、分户资金与回撤)
|
||||||
├─ /dashboard 数据看板(四户当日总览,SSE 推送;见 [数据看板说明.md](./数据看板说明.md))
|
├─ /dashboard 数据看板(四户当日总览,SSE 推送;见 [数据看板说明.md](./数据看板说明.md))
|
||||||
├─ /ai AI 教练(交易教练 / 普通聊天;见 [AI教练说明.md](./AI教练说明.md))
|
├─ /ai AI 教练(交易教练 / 普通聊天;见 [AI教练说明.md](./AI教练说明.md))
|
||||||
@@ -130,7 +130,7 @@ pm2 save
|
|||||||
|
|
||||||
- 监控区:`http://127.0.0.1:5100/monitor`
|
- 监控区:`http://127.0.0.1:5100/monitor`
|
||||||
- 行情区:`http://127.0.0.1:5100/market`
|
- 行情区:`http://127.0.0.1:5100/market`
|
||||||
- 币种档案:`http://127.0.0.1:5100/archive`
|
- 内照明心:`http://127.0.0.1:5100/archive`
|
||||||
- 资金概况:`http://127.0.0.1:5100/funds`
|
- 资金概况:`http://127.0.0.1:5100/funds`
|
||||||
- 系统设置:`http://127.0.0.1:5100/settings`
|
- 系统设置:`http://127.0.0.1:5100/settings`
|
||||||
|
|
||||||
@@ -183,14 +183,16 @@ Chrome **桌面快捷方式**图标来自站点 `favicon` / `manifest`(已配
|
|||||||
|
|
||||||
数据经中控 → 各实例 `GET /api/hub/ohlcv`(`hub_ohlcv_lib`)。升级 hub 与四实例 Flask 后请 **强刷浏览器**;异常 K 线可点 **强制刷新**。
|
数据经中控 → 各实例 `GET /api/hub/ohlcv`(`hub_ohlcv_lib`)。升级 hub 与四实例 Flask 后请 **强刷浏览器**;异常 K 线可点 **强制刷新**。
|
||||||
|
|
||||||
### 4.2.1 币种档案 `/archive`
|
### 4.2.1 内照明心 `/archive`
|
||||||
|
|
||||||
| 功能 | 说明 |
|
| 功能 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **列表** | 一所一币一行;数据来自四所 `trade_records`(`GET /api/hub/trades/archive`) |
|
| **复盘语录** | 左栏按日添加/编辑;最多 100 条 |
|
||||||
| **筛选** | 交易所、有盈利单、有亏损单、犯病/情绪标签(中控 overlay,不上传图片) |
|
| **日期** | **本日 / 本周 / 本月 / 自选区间**(交易日 8:00 切日) |
|
||||||
| **明细** | 交易时间线;可编辑备注与犯病/情绪标签 |
|
| **区间统计** | 总开仓、犯病次数与占比、盈亏、剔除犯病盈亏、各交易所分项 |
|
||||||
| **K 线** | 独立库 `data/hub_symbol_archive.db`;仅存 **5m** 真源,**15m/1h/4h** 聚合;默认 Tab **15m** |
|
| **筛选** | 盈利单、亏损单、犯病(仅过滤表格;统计栏不受此三项影响) |
|
||||||
|
| **交易记录** | 区间内开仓列表;犯病行红色字体;可编辑备注与犯病标签 |
|
||||||
|
| **K 线** | 默认折叠按需加载;独立库 `data/hub_symbol_archive.db`;仅存 **5m** 真源,**15m/1h/4h** 聚合 |
|
||||||
| **建档** | 最早开仓向前 **30 天** 5m 种子;之后每 **4h** 增量(Hub 后台 + 可点「同步」) |
|
| **建档** | 最早开仓向前 **30 天** 5m 种子;之后每 **4h** 增量(Hub 后台 + 可点「同步」) |
|
||||||
| **视窗** | **持仓过程**(锚平仓)/ **进场决策**(锚开仓);支持时间输入跳转 |
|
| **视窗** | **持仓过程**(锚平仓)/ **进场决策**(锚开仓);支持时间输入跳转 |
|
||||||
|
|
||||||
@@ -354,7 +356,9 @@ PM2:仓库 `ecosystem.config.cjs` 默认只有四 agent;第五户需自行 `
|
|||||||
| GET | `/api/chart/meta` | 行情区:交易所、周期、limit |
|
| GET | `/api/chart/meta` | 行情区:交易所、周期、limit |
|
||||||
| GET | `/api/chart/ohlcv` | 行情区 K 线(`exchange_key`、`symbol`、`timeframe`、可选 `refresh=1`) |
|
| GET | `/api/chart/ohlcv` | 行情区 K 线(`exchange_key`、`symbol`、`timeframe`、可选 `refresh=1`) |
|
||||||
| GET | `/api/hub/fund-overview` | 资金概况:总/分户资金、180 日曲线、回撤 |
|
| GET | `/api/hub/fund-overview` | 资金概况:总/分户资金、180 日曲线、回撤 |
|
||||||
| GET | `/api/archive/meta` | 币种档案:周期、同步间隔 |
|
| GET | `/api/archive/meta` | 内照明心:周期、同步间隔 |
|
||||||
|
| GET | `/api/archive/daily-trades` | 内照明心:区间交易与统计(`period` / `date_from` / `date_to`) |
|
||||||
|
| GET | `/api/archive/quotes` | 内照明心:复盘语录 |
|
||||||
| GET | `/api/archive/list` | 币种列表(筛选 query) |
|
| GET | `/api/archive/list` | 币种列表(筛选 query) |
|
||||||
| GET | `/api/archive/detail` | 单币种交易时间线 |
|
| GET | `/api/archive/detail` | 单币种交易时间线 |
|
||||||
| GET | `/api/archive/ohlcv` | 档案 K 线视窗 |
|
| GET | `/api/archive/ohlcv` | 档案 K 线视窗 |
|
||||||
@@ -370,7 +374,7 @@ PM2:仓库 `ecosystem.config.cjs` 默认只有四 agent;第五户需自行 `
|
|||||||
| `/api/hub/ping` | 连通与能力 |
|
| `/api/hub/ping` | 连通与能力 |
|
||||||
| `/api/hub/monitor` | 关键位、机器人单、趋势计划 |
|
| `/api/hub/monitor` | 关键位、机器人单、趋势计划 |
|
||||||
| `/api/hub/ohlcv` | 行情区 OHLCV(ccxt 拉取,供中控聚合缓存) |
|
| `/api/hub/ohlcv` | 行情区 OHLCV(ccxt 拉取,供中控聚合缓存) |
|
||||||
| `/api/hub/trades/archive` | 币种档案:近 N 天已平仓(`days` / `limit`) |
|
| `/api/hub/trades/archive` | 内照明心:近 N 天已平仓(`days` / `limit`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -392,7 +396,7 @@ PM2:仓库 `ecosystem.config.cjs` 默认只有四 agent;第五户需自行 `
|
|||||||
| `HUB_SESSION_DAYS` | `7` | 登录保持天数 |
|
| `HUB_SESSION_DAYS` | `7` | 登录保持天数 |
|
||||||
| `HUB_KLINE_RETENTION_DAYS` | `15` | 行情区 K 线库保留天数 |
|
| `HUB_KLINE_RETENTION_DAYS` | `15` | 行情区 K 线库保留天数 |
|
||||||
| `HUB_KLINE_DB_PATH` | `data/hub_kline.db` | K 线 SQLite 路径 |
|
| `HUB_KLINE_DB_PATH` | `data/hub_kline.db` | K 线 SQLite 路径 |
|
||||||
| `HUB_ARCHIVE_DB_PATH` | `data/hub_symbol_archive.db` | 币种档案永久 K 线库 |
|
| `HUB_ARCHIVE_DB_PATH` | `data/hub_symbol_archive.db` | 内照明心永久 K 线库 |
|
||||||
| `HUB_ARCHIVE_SYNC_INTERVAL_SEC` | `14400` | 档案 K 线后台同步间隔(秒) |
|
| `HUB_ARCHIVE_SYNC_INTERVAL_SEC` | `14400` | 档案 K 线后台同步间隔(秒) |
|
||||||
| `HUB_ARCHIVE_TRADE_DAYS` | `365` | 同步交易记录回看天数 |
|
| `HUB_ARCHIVE_TRADE_DAYS` | `365` | 同步交易记录回看天数 |
|
||||||
| `HUB_ARCHIVE_TRADE_LIMIT` | `2000` | 单所同步交易条数上限 |
|
| `HUB_ARCHIVE_TRADE_LIMIT` | `2000` | 单所同步交易条数上限 |
|
||||||
@@ -505,7 +509,7 @@ pm2 save && pm2 startup
|
|||||||
|------|------|
|
|------|------|
|
||||||
| [使用说明.md](./使用说明.md) | 本文 |
|
| [使用说明.md](./使用说明.md) | 本文 |
|
||||||
| [行情区说明.md](./行情区说明.md) | K 线周期、缓存、快捷键、API |
|
| [行情区说明.md](./行情区说明.md) | K 线周期、缓存、快捷键、API |
|
||||||
| [docs/hub-symbol-archive-kline.md](../docs/hub-symbol-archive-kline.md) | 币种档案、永久 5m、建档与同步 |
|
| [docs/hub-symbol-archive-kline.md](../docs/hub-symbol-archive-kline.md) | 内照明心、区间统计、永久 5m、建档与同步 |
|
||||||
| [部署文档.md](./部署文档.md) | Ubuntu / PM2 / 反代 |
|
| [部署文档.md](./部署文档.md) | Ubuntu / PM2 / 反代 |
|
||||||
| [常见问题.md](./常见问题.md) | 故障实录与排障 |
|
| [常见问题.md](./常见问题.md) | 故障实录与排障 |
|
||||||
| [README.md](./README.md) | 速览 |
|
| [README.md](./README.md) | 速览 |
|
||||||
|
|||||||
Reference in New Issue
Block a user