feat: auto sick tag in archive from journal mood issues
内照明心同步时匹配实例复盘情绪标签,自动标注犯病并锁定标签;中控不再手动选犯病。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -468,7 +468,23 @@ def _trade_row_to_dict(row: sqlite3.Row, overlay: dict | None = None) -> dict[st
|
||||
if key in d and d[key] not in (None, ""):
|
||||
out[key] = d[key]
|
||||
ov = overlay or {}
|
||||
out["behavior_tag"] = ov.get("behavior_tag") or ""
|
||||
from lib.trade.account_risk_lib import parse_mood_issues
|
||||
|
||||
journal_issues = parse_mood_issues(
|
||||
out.get("journal_mood_issues") or payload.get("journal_mood_issues")
|
||||
)
|
||||
tag_from_journal = bool(
|
||||
out.get("journal_mood_sick")
|
||||
or payload.get("journal_mood_sick")
|
||||
or journal_issues
|
||||
)
|
||||
tag = (ov.get("behavior_tag") or "").strip().lower()
|
||||
if tag_from_journal:
|
||||
tag = "sick"
|
||||
out["behavior_tag"] = tag
|
||||
out["behavior_tag_from_journal"] = tag_from_journal
|
||||
if journal_issues:
|
||||
out["journal_mood_issues"] = journal_issues
|
||||
out["note"] = ov.get("note") or ""
|
||||
out["trade_id"] = out.get("trade_id") or out.get("id")
|
||||
ex_col = str(d.get("exchange_key") or "").strip().lower()
|
||||
@@ -551,6 +567,31 @@ def upsert_trade_overlay(
|
||||
return {"exchange_key": ex_k, "trade_id": tid, "behavior_tag": tag, "note": note_text}
|
||||
|
||||
|
||||
def apply_journal_behavior_overlays(
|
||||
exchange_key: str,
|
||||
trades: list[dict[str, Any]],
|
||||
*,
|
||||
db_path: Path | None = None,
|
||||
) -> int:
|
||||
"""复盘情绪标签 → 写入 trade_overlay.behavior_tag=sick(仅正 trade_id)。"""
|
||||
ex_k = (exchange_key or "").strip().lower()
|
||||
if not ex_k:
|
||||
return 0
|
||||
n = 0
|
||||
for t in trades or []:
|
||||
if not isinstance(t, dict) or not t.get("journal_mood_sick"):
|
||||
continue
|
||||
try:
|
||||
tid = int(t.get("id"))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if tid <= 0:
|
||||
continue
|
||||
upsert_trade_overlay(ex_k, tid, behavior_tag="sick", db_path=db_path)
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def list_symbol_rows(
|
||||
*,
|
||||
exchange_key: str = "",
|
||||
@@ -1154,6 +1195,7 @@ def sync_exchange_symbol_archives(
|
||||
"""同步单所:交易缓存 + 各币种 K 线种子/增量。"""
|
||||
ex_k = (exchange_key or "").strip().lower()
|
||||
cache_stats = upsert_trades_cache(ex_k, trades, db_path=db_path, prune_missing=True)
|
||||
apply_journal_behavior_overlays(ex_k, trades, db_path=db_path)
|
||||
|
||||
by_sym: dict[str, int] = {}
|
||||
for t in trades or []:
|
||||
|
||||
@@ -617,7 +617,98 @@ def fetch_trades_for_archive(
|
||||
key=lambda x: int(x.get("closed_at_ms") or 0),
|
||||
reverse=True,
|
||||
)
|
||||
return merged[:lim]
|
||||
merged = merged[:lim]
|
||||
attach_journal_mood_tags(conn, merged, cutoff_s=cutoff_s)
|
||||
return merged
|
||||
|
||||
|
||||
def _symbol_coin_base(symbol: str) -> str:
|
||||
s = (symbol or "").strip().upper()
|
||||
if "/" in s:
|
||||
return s.split("/")[0]
|
||||
return s
|
||||
|
||||
|
||||
def _datetime_minute_key(raw: Any) -> str:
|
||||
if raw is None:
|
||||
return ""
|
||||
s = str(raw).strip().replace("T", " ").replace("Z", "")
|
||||
if len(s) >= 16:
|
||||
return s[:16]
|
||||
return s[:10] if len(s) >= 10 else s
|
||||
|
||||
|
||||
def journal_trade_match_key(symbol: str, opened_at: Any, closed_at: Any) -> tuple[str, str, str]:
|
||||
return (
|
||||
_symbol_coin_base(symbol),
|
||||
_datetime_minute_key(opened_at),
|
||||
_datetime_minute_key(closed_at),
|
||||
)
|
||||
|
||||
|
||||
def load_journal_mood_match_index(
|
||||
conn,
|
||||
*,
|
||||
cutoff_s: str,
|
||||
) -> dict[tuple[str, str, str], list[str]]:
|
||||
"""复盘 mood_issues → 交易匹配键(币种 + 开/平仓分钟)。"""
|
||||
from lib.trade.account_risk_lib import parse_mood_issues
|
||||
|
||||
cols = _table_columns(conn, "journal_entries")
|
||||
if not cols or "mood_issues" not in cols:
|
||||
return {}
|
||||
close_expr = "REPLACE(COALESCE(close_datetime, open_datetime, created_at), 'T', ' ')"
|
||||
open_expr = "REPLACE(COALESCE(open_datetime, created_at), 'T', ' ')"
|
||||
rows = conn.execute(
|
||||
f"""
|
||||
SELECT coin, open_datetime, close_datetime, mood_issues
|
||||
FROM journal_entries
|
||||
WHERE {close_expr} >= ? OR {open_expr} >= ?
|
||||
""",
|
||||
(cutoff_s, cutoff_s),
|
||||
).fetchall()
|
||||
out: dict[tuple[str, str, str], list[str]] = {}
|
||||
for row in rows:
|
||||
d = _row_dict(row)
|
||||
issues = parse_mood_issues(d.get("mood_issues"))
|
||||
if not issues:
|
||||
continue
|
||||
coin = str(d.get("coin") or "").strip().upper()
|
||||
sym = coin if "/" in coin else (f"{coin}/USDT" if coin else "")
|
||||
key = journal_trade_match_key(sym, d.get("open_datetime"), d.get("close_datetime"))
|
||||
if not key[0]:
|
||||
continue
|
||||
out[key] = issues
|
||||
return out
|
||||
|
||||
|
||||
def attach_journal_mood_tags(
|
||||
conn,
|
||||
trades: list[dict[str, Any]],
|
||||
*,
|
||||
cutoff_s: str,
|
||||
) -> None:
|
||||
"""实例复盘勾选情绪标签 → 档案交易自动标犯病(hub 同步用)。"""
|
||||
if not trades:
|
||||
return
|
||||
mood_index = load_journal_mood_match_index(conn, cutoff_s=cutoff_s)
|
||||
if not mood_index:
|
||||
return
|
||||
for t in trades:
|
||||
if not isinstance(t, dict):
|
||||
continue
|
||||
key = journal_trade_match_key(
|
||||
str(t.get("symbol") or ""),
|
||||
t.get("opened_at"),
|
||||
t.get("closed_at"),
|
||||
)
|
||||
issues = mood_index.get(key)
|
||||
if not issues:
|
||||
continue
|
||||
t["journal_mood_issues"] = issues
|
||||
t["journal_mood_sick"] = True
|
||||
t["behavior_tag_from_journal"] = True
|
||||
t["behavior_tag"] = "sick"
|
||||
|
||||
|
||||
def summarize_trades(trades: list[dict]) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user