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:
dekun
2026-06-11 18:09:39 +08:00
parent 5f79a62b13
commit 7b0b8996fe
7 changed files with 360 additions and 62 deletions
+7 -1
View File
@@ -2057,7 +2057,10 @@ def api_archive_list(
@app.get("/api/archive/daily-trades")
def api_archive_daily_trades(
period: str = "",
trading_day: str = "",
date_from: str = "",
date_to: str = "",
exchange_key: str = "",
filter_profit: str = "",
filter_loss: str = "",
@@ -2066,7 +2069,10 @@ def api_archive_daily_trades(
):
init_archive_db()
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,
filter_profit=(filter_profit or "").lower() in ("1", "true", "yes", "on"),
filter_loss=(filter_loss or "").lower() in ("1", "true", "yes", "on"),
+42
View File
@@ -5570,6 +5570,41 @@ body.funds-fullscreen-open {
padding: 12px;
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 {
padding: 10px 12px;
border-radius: 8px;
@@ -5578,6 +5613,13 @@ body.funds-fullscreen-open {
font-size: 0.82rem;
color: var(--text);
line-height: 1.45;
display: flex;
flex-direction: column;
gap: 4px;
}
.archive-stats-sub {
font-size: 0.78rem;
color: var(--muted);
}
.archive-acc-section {
border: 1px solid var(--border-soft);
+108 -9
View File
@@ -9,7 +9,11 @@
const elFilterProfit = document.getElementById("archive-filter-profit");
const elFilterLoss = document.getElementById("archive-filter-loss");
const elFilterSick = document.getElementById("archive-filter-sick");
const elPeriodTabs = document.getElementById("archive-period-tabs");
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 elBtnChartToggle = document.getElementById("archive-btn-chart-toggle");
const elBtnRefresh = document.getElementById("archive-btn-refresh");
@@ -45,6 +49,10 @@
let quotes = [];
let dailyTrades = [];
let dailyStats = { open_count: 0, by_exchange: {} };
let periodMode = "today";
let periodLabel = "";
let dateFrom = "";
let dateTo = "";
let tradingDay = "";
let selected = null;
let trades = [];
@@ -292,9 +300,35 @@
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() {
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) || "";
if (ex) q.set("exchange_key", ex);
if (elFilterProfit && elFilterProfit.checked) q.set("filter_profit", "1");
@@ -304,6 +338,14 @@
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() {
if (!elExchange || !meta) return;
const cur = elExchange.value;
@@ -320,14 +362,47 @@
function renderStats() {
if (!elStats) return;
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 || {};
Object.keys(byEx)
.sort()
.forEach(function (ex) {
parts.push(exchangeLabel(ex) + " " + byEx[ex]);
const exKeys = Object.keys(byEx).sort();
if (exKeys.length) {
const exParts = exKeys.map(function (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) {
@@ -990,15 +1065,26 @@
setStatus(j.detail || "加载失败");
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;
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;
syncPeriodUI();
dailyTrades = j.trades || [];
dailyStats = j.stats || { open_count: 0, by_exchange: {} };
renderStats();
renderTrades();
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 (elBtnSync) elBtnSync.addEventListener("click", syncAll);
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 (elDateFrom) elDateFrom.addEventListener("change", loadDailyTrades);
if (elDateTo) elDateTo.addEventListener("change", loadDailyTrades);
[elFilterProfit, elFilterLoss, elFilterSick].forEach(function (el) {
if (el) el.addEventListener("change", loadDailyTrades);
});
@@ -1117,6 +1215,7 @@
if (!inited) {
loadMarkAutoPref();
setChartOpen(false);
syncPeriodUI();
bindEvents();
inited = true;
}
+15 -4
View File
@@ -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-loss" /> 亏损单</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>
<input id="archive-trading-day" type="date" />
</label>
<div class="archive-period-tabs" id="archive-period-tabs" role="tablist">
<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>
<label class="archive-field archive-search-field">
<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="/assets/chart_draw.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/dashboard.js?v=20260612-dash-monitor-count"></script>
<script src="/assets/ai_review_render.js?v=3"></script>
+16 -12
View File
@@ -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 监控区(持仓、关键位、趋势计划、全平)
├─ /market 行情区(K 线、技术指标、持仓价格线)
├─ /archive 币种档案(交易时间线 + 永久 5m K 线)
├─ /archive 内照明心(复盘语录 + 交易记录 + 永久 5m K 线)
├─ /funds 资金概况(总资金曲线、分户资金与回撤)
├─ /dashboard 数据看板(四户当日总览,SSE 推送;见 [数据看板说明.md](./数据看板说明.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/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/settings`
@@ -183,14 +183,16 @@ Chrome **桌面快捷方式**图标来自站点 `favicon` / `manifest`(已配
数据经中控 → 各实例 `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` |
| **筛选** | 交易所、有盈利单、有亏损单、犯病/情绪标签(中控 overlay,不上传图片 |
| **明细** | 交易时间线;可编辑备注与犯病/情绪标签 |
| **K 线** | 独立库 `data/hub_symbol_archive.db`;仅存 **5m** 真源,**15m/1h/4h** 聚合;默认 Tab **15m** |
| **复盘语录** | 左栏按日添加/编辑;最多 100 条 |
| **日期** | **本日 / 本周 / 本月 / 自选区间**(交易日 8:00 切日 |
| **区间统计** | 总开仓、犯病次数与占比、盈亏、剔除犯病盈亏、各交易所分项 |
| **筛选** | 盈利单、亏损单、犯病(仅过滤表格;统计栏不受此三项影响) |
| **交易记录** | 区间内开仓列表;犯病行红色字体;可编辑备注与犯病标签 |
| **K 线** | 默认折叠按需加载;独立库 `data/hub_symbol_archive.db`;仅存 **5m** 真源,**15m/1h/4h** 聚合 |
| **建档** | 最早开仓向前 **30 天** 5m 种子;之后每 **4h** 增量(Hub 后台 + 可点「同步」) |
| **视窗** | **持仓过程**(锚平仓)/ **进场决策**(锚开仓);支持时间输入跳转 |
@@ -354,7 +356,9 @@ PM2:仓库 `ecosystem.config.cjs` 默认只有四 agent;第五户需自行 `
| GET | `/api/chart/meta` | 行情区:交易所、周期、limit |
| GET | `/api/chart/ohlcv` | 行情区 K 线(`exchange_key``symbol``timeframe`、可选 `refresh=1` |
| 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/detail` | 单币种交易时间线 |
| GET | `/api/archive/ohlcv` | 档案 K 线视窗 |
@@ -370,7 +374,7 @@ PM2:仓库 `ecosystem.config.cjs` 默认只有四 agent;第五户需自行 `
| `/api/hub/ping` | 连通与能力 |
| `/api/hub/monitor` | 关键位、机器人单、趋势计划 |
| `/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_KLINE_RETENTION_DAYS` | `15` | 行情区 K 线库保留天数 |
| `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_TRADE_DAYS` | `365` | 同步交易记录回看天数 |
| `HUB_ARCHIVE_TRADE_LIMIT` | `2000` | 单所同步交易条数上限 |
@@ -505,7 +509,7 @@ pm2 save && pm2 startup
|------|------|
| [使用说明.md](./使用说明.md) | 本文 |
| [行情区说明.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) | 故障实录与排障 |
| [README.md](./README.md) | 速览 |