diff --git a/manual_trading_hub/hub_ai/context.py b/manual_trading_hub/hub_ai/context.py index 74517c7..7db2a54 100644 --- a/manual_trading_hub/hub_ai/context.py +++ b/manual_trading_hub/hub_ai/context.py @@ -517,6 +517,97 @@ def format_context_text(payload: dict) -> str: return "\n".join(lines).strip() +def format_summary_context_text(payload: dict) -> str: + """今日总结专用:仅当日平仓/持仓/监控,不含昨日明细与资金走势。""" + lines = [] + totals = payload.get("totals") or {} + day = totals.get("trading_day") + lines.append( + f"【合计·今日 {day}】平仓盈亏 {totals.get('total_pnl_u')}U | " + f"笔数 {totals.get('closed_count')}(胜{totals.get('win_count')}/负{totals.get('loss_count')})| " + f"实盘持仓 {totals.get('open_position_count', 0)} 仓 | " + f"浮盈亏 {totals.get('float_pnl_u')}U | " + f"资金账户合计 {_fmt_fund(totals.get('total_funding_usdt'))} | " + f"交易账户合计 {_fmt_fund(totals.get('total_trading_usdt'))}" + ) + lines.append( + f"【说明】交易日={day}。" + "「持仓」= 交易所 Agent 实盘;「趋势/关键位/监控单/加仓」= 本地计划,不等于已开仓。" + ) + lines.append("") + for ac in payload.get("accounts") or []: + st = ac.get("trade_stats") or {} + lines.append(f"--- 账户:{ac.get('name')} ({ac.get('key')}) ---") + lines.append(f"状态:{ac.get('status')}") + if ac.get("status") == "未监控": + lines.append("") + continue + lines.append( + f"资金账户 {_fmt_fund(ac.get('funding_usdt'))} | " + f"交易账户 {_fmt_fund(ac.get('trading_usdt'))} | " + f"可用 {_fmt_fund(ac.get('available_trading_usdt'))}" + ) + lines.append( + f"今日({day})平仓:{st.get('closed_count')} 笔,盈亏 {st.get('total_pnl_u')}U " + f"(胜{st.get('win_count')}/负{st.get('loss_count')})" + ) + open_n = int(ac.get("open_position_count") or _account_open_position_count(ac)) + if open_n <= 0: + lines.append("当前交易所持仓:无(空仓)") + else: + lines.append( + f"当前交易所持仓:{open_n} 仓 | 浮盈亏合计 {ac.get('float_pnl_u')}U" + ) + mon = ac.get("monitor_lines") or {} + if mon.get("trends"): + lines.append("趋势回调计划(本地,非持仓):") + for row in mon["trends"][:8]: + lines.append(f" - {row}") + if mon.get("rolls"): + lines.append("顺势加仓(本地,非持仓):") + for row in mon["rolls"][:8]: + lines.append(f" - {row}") + if mon.get("keys"): + lines.append("关键位监控(本地,非持仓):") + for row in mon["keys"][:8]: + lines.append(f" - {row}") + if mon.get("orders"): + lines.append("进行中的下单监控(本地,非持仓):") + for row in mon["orders"][:8]: + lines.append(f" - {row}") + positions = ac.get("positions") or [] + if positions: + lines.append("持仓明细(交易所实盘):") + for p in positions[:8]: + if not isinstance(p, dict): + continue + sym = p.get("symbol") or "?" + side = p.get("side") or "?" + contracts = p.get("contracts") or p.get("size") or "?" + upnl = _position_float_pnl(p) + lines.append(f" - {sym} {side} 张数{contracts} 浮盈亏{upnl:.4f}U") + lines.append( + f"Agent合约余额:{ac.get('balance_usdt') if ac.get('balance_usdt') is not None else '未知'} USDT" + ) + trades_today = ac.get("trades") or [] + if trades_today: + lines.append("今日平仓明细:") + for t in trades_today[:15]: + lines.append(f" - {_format_trade_line(t)}") + else: + lines.append("今日平仓明细:无") + issues = ac.get("issues") or [] + if issues: + lines.append("关注点:" + ";".join(issues)) + lines.append("") + return "\n".join(lines).strip() + + +def summary_context_hash(payload: dict) -> str: + text = format_summary_context_text(payload) + return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16] + + def format_account_remark(ac: dict) -> str: """分户表格备注:监控摘要 + 持仓。""" parts: list[str] = [] @@ -548,14 +639,20 @@ def format_account_remark(ac: dict) -> str: return ";".join(parts) -def collect_closed_trades_snapshot(accounts: list[dict], *, today: str, yesterday: str) -> list[dict]: +def collect_closed_trades_snapshot( + accounts: list[dict], + *, + today: str, + yesterday: str | None = None, +) -> list[dict]: rows: list[dict] = [] for ac in accounts or []: name = ac.get("name") or ac.get("key") - for t in ac.get("trades_yesterday") or []: - if not isinstance(t, dict): - continue - rows.append({**t, "account_name": name, "trading_day": yesterday}) + if yesterday: + for t in ac.get("trades_yesterday") or []: + if not isinstance(t, dict): + continue + rows.append({**t, "account_name": name, "trading_day": yesterday}) for t in ac.get("trades") or []: if not isinstance(t, dict): continue diff --git a/manual_trading_hub/hub_ai/prompts.py b/manual_trading_hub/hub_ai/prompts.py index 997f4ae..41d8555 100644 --- a/manual_trading_hub/hub_ai/prompts.py +++ b/manual_trading_hub/hub_ai/prompts.py @@ -5,20 +5,19 @@ SUMMARY_SYSTEM = """ 硬性规则: - 只能陈述数据中明确出现的数字与事实;禁止编造成交、止损、扛单、行情预测。 -- 上下文含「昨日+今日」两个交易日的平仓与「近15日资金快照」;须连贯引用,不得只写单日而忽略另一日。 +- 上下文仅含「今日」一个交易日的平仓、持仓与监控;不得引用昨日、历史走势或数据里未出现的账户名。 - 未监控的账户必须标注「未监控」,不得臆测其盈亏。 - 连接失败或数据缺失的账户如实写明,不要猜测。 - 趋势回调计划、顺势加仓、关键位监控、进行中的下单监控:仅据数据列示,无则写「无」。 -- 第1~4节保持客观台账;**第5节操作建议**可基于资金账户/交易账户余额、15日资金变化、仓位与监控单,给出简短、可执行的资金与仓位安排建议(仍禁止预测涨跌、保证收益)。 -- 禁止夸张词(致命、崩溃、灾难等)。 +- 第1~4节保持客观台账;**第5节操作建议**可基于当日资金账户/交易账户余额、仓位与监控单,给出简短、可执行的资金与仓位安排建议(仍禁止预测涨跌、保证收益)。 +- 禁止输出 pipe 分隔的 Markdown 表格或「详细数据支持」附录;禁止夸张词(致命、崩溃、灾难等)。 输出格式(Markdown,标题必须一致): **今日交易总结({trading_day})** **1. 总览** -- **对比说明**:昨日 vs 今日(交易日日期见数据) - **合计盈亏(U)**:今日平仓合计 … -- **平仓笔数**:今日 …(胜 / 负 / 平);昨日 … +- **平仓笔数**:今日 …(胜 / 负 / 平) - **当前持仓浮盈亏(U)**:… - **资金合计**:资金账户 … / 交易账户 …(仅已监控且有数据账户) @@ -29,10 +28,10 @@ SUMMARY_SYSTEM = """ 仅有依据时列出(亏损、浮亏、监控/趋势/关键位异常、资金缺口等);若无则写「无」。 **4. 数据说明** -列出数据缺口(某户未启用、接口失败、缺15日资金快照等)。 +列出数据缺口(某户未启用、接口失败等)。 **5. 操作建议** -基于各户资金账户与交易账户余额、近15日资金走势、持仓与监控单,给出 2~5 条简短建议(如:是否需要从资金账户补充交易账户、哪户风险敞口偏高等)。无依据则写「暂无」。 +基于各户当日资金账户与交易账户余额、持仓与监控单,给出 2~5 条简短建议(如:是否需要从资金账户补充交易账户、哪户风险敞口偏高等)。无依据则写「暂无」。 """.strip() @@ -57,7 +56,7 @@ def build_summary_user_prompt(context_text: str, trading_day: str) -> str: return f""" 交易日(今日):{trading_day} -以下为中控聚合的多账户数据(含昨日+今日平仓、近15日资金快照、趋势回调/顺势加仓/关键位/监控单): +以下为中控聚合的多账户数据(仅今日平仓、持仓、趋势回调/顺势加仓/关键位/监控单): {context_text} """.strip() diff --git a/manual_trading_hub/hub_ai/summary.py b/manual_trading_hub/hub_ai/summary.py index 691c247..5c05279 100644 --- a/manual_trading_hub/hub_ai/summary.py +++ b/manual_trading_hub/hub_ai/summary.py @@ -8,6 +8,8 @@ from hub_ai.context import ( build_daily_context, collect_closed_trades_snapshot, format_account_remark, + format_summary_context_text, + summary_context_hash, ) from hub_ai.prompts import SUMMARY_SYSTEM, build_summary_user_prompt from hub_ai.store import append_summary, get_latest_summary, list_summaries @@ -15,13 +17,10 @@ from hub_ai.store import append_summary, get_latest_summary, list_summaries def _stats_snapshot_from_ctx(ctx: dict) -> dict: day = ctx.get("trading_day") - prev = ctx.get("prev_trading_day") accounts = ctx.get("accounts") or [] return { "totals": ctx.get("totals"), - "prev_trading_day": prev, - "fund_history": ctx.get("fund_history"), - "closed_trades": collect_closed_trades_snapshot(accounts, today=day, yesterday=prev), + "closed_trades": collect_closed_trades_snapshot(accounts, today=day), "by_account": { str(ac.get("key") or ac.get("id")): { "key": ac.get("key"), @@ -31,9 +30,7 @@ def _stats_snapshot_from_ctx(ctx: dict) -> dict: "trading_usdt": ac.get("trading_usdt"), "available_trading_usdt": ac.get("available_trading_usdt"), "pnl_u": (ac.get("trade_stats") or {}).get("total_pnl_u"), - "pnl_u_yesterday": (ac.get("trade_stats_yesterday") or {}).get("total_pnl_u"), "closed_count": (ac.get("trade_stats") or {}).get("closed_count"), - "closed_count_yesterday": (ac.get("trade_stats_yesterday") or {}).get("closed_count"), "float_pnl_u": ac.get("float_pnl_u"), "remark": format_account_remark(ac), "monitor_lines": ac.get("monitor_lines") or {}, @@ -52,9 +49,16 @@ def generate_daily_summary( ) -> dict[str, Any]: ctx = build_daily_context(exchanges, trading_day=trading_day) day = ctx["trading_day"] + summary_payload = { + "trading_day": day, + "totals": ctx.get("totals"), + "accounts": ctx.get("accounts"), + } + summary_text = format_summary_context_text(summary_payload) + digest = summary_context_hash(summary_payload) if not force: latest = get_latest_summary(day) - if latest and latest.get("context_hash") == ctx.get("context_hash"): + if latest and latest.get("context_hash") == digest: return { "ok": True, "cached": True, @@ -64,7 +68,7 @@ def generate_daily_summary( } system = SUMMARY_SYSTEM.replace("{trading_day}", day) - user = build_summary_user_prompt(ctx["text"], day) + user = build_summary_user_prompt(summary_text, day) content = generate_text(system=system, user=user, temperature=0.15) if content.startswith("AI 调用失败"): return {"ok": False, "msg": content, "trading_day": day} @@ -74,7 +78,7 @@ def generate_daily_summary( trading_day=day, content_md=content, model=model_label(), - context_hash=ctx.get("context_hash") or "", + context_hash=digest, stats_snapshot=stats_snapshot, ) return { diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 5c1c2af..35f9ef7 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -3092,7 +3092,7 @@ .join(""); return ( `
` + - `

平仓明细(昨日 + 今日)

` + + `

平仓明细(今日)

` + `
${head}${body}
` + `
` );