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
+106 -20
View File
@@ -1195,6 +1195,93 @@ def trading_day_bounds_ms(
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]]:
init_db(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(
trading_day: str = "",
*,
period: str = "",
date_from: str = "",
date_to: str = "",
exchange_key: str = "",
filter_profit: bool = False,
filter_loss: bool = False,
@@ -1317,10 +1407,15 @@ def list_daily_trades(
search: str = "",
db_path: Path | None = None,
) -> dict[str, Any]:
"""交易日列出开仓记录(默认当日),含各所开仓统计。"""
"""按日期区间列出开仓记录(本日/本周/本月/自选),含犯病与盈亏统计。"""
init_db(db_path)
td = (trading_day or "").strip()[:10] or today_trading_day()
start_ms, end_ms = trading_day_bounds_ms(td)
p = (period or "today").strip().lower() or "today"
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()
conn = _connect(db_path)
try:
@@ -1329,15 +1424,6 @@ def list_daily_trades(
if ex_filter:
where += " AND exchange_key=?"
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(
f"""
SELECT * FROM archive_trade_cache
@@ -1347,6 +1433,7 @@ def list_daily_trades(
params,
).fetchall()
overlays_by_ex: dict[str, dict[int, dict]] = {}
all_rows: list[dict[str, Any]] = []
trades: list[dict[str, Any]] = []
q = (search or "").strip().lower()
for r in rows:
@@ -1354,6 +1441,7 @@ def list_daily_trades(
if ex_k not in overlays_by_ex:
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"])))
all_rows.append(td_row)
pnl = float(td_row.get("pnl_amount") or 0)
tag = td_row.get("behavior_tag") or ""
if filter_profit and pnl <= 0.0001:
@@ -1378,16 +1466,14 @@ def list_daily_trades(
if q not in blob:
continue
trades.append(td_row)
by_exchange = {
str(sr["exchange_key"]): int(sr["open_count"] or 0) for sr in stat_rows
}
return {
"trading_day": td,
"period": p,
"period_label": period_label,
"trading_day": dt,
"date_from": df,
"date_to": dt,
"trades": trades,
"stats": {
"open_count": sum(by_exchange.values()),
"by_exchange": by_exchange,
},
"stats": _compute_period_stats(all_rows),
}
finally:
conn.close()