fix: feed today-only data to AI daily summary to reduce hallucination
Shrink summary context and prompts to today's trades and positions only, and tighten anti-fabrication rules. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -3092,7 +3092,7 @@
|
||||
.join("");
|
||||
return (
|
||||
`<div class="ai-closed-trades-wrap">` +
|
||||
`<h4 class="ai-closed-trades-title">平仓明细(昨日 + 今日)</h4>` +
|
||||
`<h4 class="ai-closed-trades-title">平仓明细(今日)</h4>` +
|
||||
`<div class="ai-ac-table-wrap"><table class="ai-ac-table ai-closed-trades-table">${head}<tbody>${body}</tbody></table></div>` +
|
||||
`</div>`
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user