feat: show review fields in symbol archive trade table

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 12:20:29 +08:00
parent 1dcf62bb08
commit 4918699276
5 changed files with 338 additions and 11 deletions
+114 -7
View File
@@ -4,6 +4,12 @@ from __future__ import annotations
from datetime import datetime, timedelta
from typing import Any, Callable, Optional
from strategy_trade_labels import (
MONITOR_TYPE_ROLL,
MONITOR_TYPE_TREND_PULLBACK,
entry_reason_for_monitor_type,
)
TRADE_COMPLETED_RESULTS = (
"止盈",
"止损",
@@ -78,6 +84,74 @@ def _effective_field(d: dict, reviewed_key: str, base_key: str, default: Any = N
return default
def format_hold_minutes(minutes: Any) -> str:
try:
total = int(minutes or 0)
except (TypeError, ValueError):
return "0分钟"
if total <= 0:
return "0分钟"
hours = total // 60
mins = total % 60
if hours:
return f"{hours}小时{mins}分钟"
return f"{mins}分钟"
def _normalize_monitor_type_label(raw: Any) -> str:
mt = str(raw or "").strip()
if mt in ("trend_pullback", "trend"):
return MONITOR_TYPE_TREND_PULLBACK
if mt in ("roll",):
return MONITOR_TYPE_ROLL
return mt
def effective_entry_type(d: dict) -> str:
"""复盘开仓类型优先,与实例交易记录 effective_entry_reason 一致。"""
er = _effective_field(d, "reviewed_entry_reason", "entry_reason")
if er is not None and str(er).strip():
return str(er).strip()
mt = _normalize_monitor_type_label(d.get("monitor_type"))
er2 = entry_reason_for_monitor_type(mt)
if er2:
return er2
kst = str(d.get("key_signal_type") or "").strip()
if kst:
return kst
return mt
def effective_hold_minutes(
d: dict,
*,
opened_ms: int | None = None,
closed_ms: int | None = None,
) -> int:
hm = _effective_field(d, "reviewed_hold_minutes", "hold_minutes")
if hm is not None and str(hm).strip() != "":
try:
return max(0, int(hm))
except (TypeError, ValueError):
pass
hs = _effective_field(d, "reviewed_hold_seconds", "hold_seconds")
if hs is not None and str(hs).strip() != "":
try:
return max(0, int(int(hs) // 60))
except (TypeError, ValueError):
pass
oms = opened_ms if opened_ms is not None else d.get("opened_at_ms")
cms = closed_ms if closed_ms is not None else d.get("closed_at_ms")
try:
oms_i = int(oms) if oms not in (None, "") else None
cms_i = int(cms) if cms not in (None, "") else None
except (TypeError, ValueError):
oms_i = cms_i = None
if oms_i and cms_i and cms_i > oms_i:
return max(0, int((cms_i - oms_i) // 60_000))
return 0
def _effective_pnl(d: dict) -> float:
reviewed = d.get("reviewed_pnl_amount")
if reviewed is not None and str(reviewed).strip() != "":
@@ -204,6 +278,18 @@ def _normalize_archive_trade_row(
trade_id = int(d.get("id"))
except (TypeError, ValueError):
return None
opened_ms_i = int(opened_ms) if opened_ms else None
closed_ms_i = int(closed_ms) if closed_ms else None
hold_m = effective_hold_minutes(d, opened_ms=opened_ms_i, closed_ms=closed_ms_i)
entry_type = effective_entry_type(d)
reviewed = bool(
d.get("reviewed_at")
or d.get("reviewed_result")
or d.get("reviewed_opened_at")
or d.get("reviewed_closed_at")
or d.get("reviewed_entry_reason")
or d.get("reviewed_hold_minutes")
)
return {
"id": trade_id,
"exchange_key": (exchange_key or "").strip().lower(),
@@ -213,17 +299,20 @@ def _normalize_archive_trade_row(
"pnl_amount": round(pnl, 4),
"closed_at": closed_at,
"opened_at": opened_at,
"opened_at_ms": int(opened_ms) if opened_ms else None,
"closed_at_ms": int(closed_ms) if closed_ms else None,
"monitor_type": d.get("monitor_type"),
"opened_at_ms": opened_ms_i,
"closed_at_ms": closed_ms_i,
"monitor_type": _normalize_monitor_type_label(d.get("monitor_type")),
"entry_type": entry_type,
"entry_reason": entry_type,
"hold_minutes": hold_m,
"hold_minutes_text": format_hold_minutes(hold_m),
"actual_rr": d.get("actual_rr"),
"planned_rr": d.get("planned_rr"),
"trade_style": d.get("trade_style"),
"entry_reason": d.get("entry_reason"),
"trigger_price": d.get("trigger_price"),
"stop_loss": _effective_field(d, "reviewed_stop_loss", "stop_loss"),
"take_profit": _effective_field(d, "reviewed_take_profit", "take_profit"),
"reviewed": bool(d.get("reviewed_at") or d.get("reviewed_result")),
"reviewed": reviewed,
"trading_day": trading_day_from_dt(close_dt, reset_hour),
}
@@ -278,10 +367,16 @@ def _archive_trade_select_sql(cols: set[str]) -> str:
"closed_at_ms",
"created_at",
"monitor_type",
"key_signal_type",
"actual_rr",
"planned_rr",
"trade_style",
"entry_reason",
"reviewed_entry_reason",
"hold_minutes",
"reviewed_hold_minutes",
"hold_seconds",
"reviewed_hold_seconds",
"trigger_price",
"stop_loss",
"take_profit",
@@ -341,7 +436,15 @@ def _normalize_snapshot_archive_row(
except (TypeError, ValueError):
pnl = 0.0
st = str(snap.get("strategy_type") or "").strip()
monitor_type = "trend_pullback" if st == "trend_pullback" else ("roll" if st == "roll" else st)
monitor_type = _normalize_monitor_type_label(
"trend_pullback" if st == "trend_pullback" else ("roll" if st == "roll" else st)
)
hold_m = effective_hold_minutes(
{},
opened_ms=opened_ms,
closed_ms=closed_ms,
)
entry_type = entry_reason_for_monitor_type(monitor_type) or monitor_type
return {
"id": -snap_id,
"symbol": (snap.get("symbol") or "").strip().upper(),
@@ -353,10 +456,14 @@ def _normalize_snapshot_archive_row(
"opened_at_ms": opened_ms,
"closed_at_ms": closed_ms,
"monitor_type": monitor_type,
"entry_reason": "trend_pullback" if st == "trend_pullback" else monitor_type,
"entry_type": entry_type,
"entry_reason": entry_type,
"hold_minutes": hold_m,
"hold_minutes_text": format_hold_minutes(hold_m),
"from_snapshot": True,
"snapshot_id": snap_id,
"trend_plan_id": snap.get("source_id"),
"reviewed": False,
"trading_day": trading_day_from_dt(close_dt, reset_hour),
}