62e48dab92
- Add 15-day fund snapshot store and /api/hub/account on all instances - Summary includes yesterday/today trades, fund columns, and section 5 操作建议 - Chat context distinguishes empty positions from local monitors - Support image/document attachments in AI chat Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
"""中控 AI:今日总结生成。"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from hub_ai.client import generate_text, model_label
|
|
from hub_ai.context import (
|
|
build_daily_context,
|
|
collect_closed_trades_snapshot,
|
|
format_account_remark,
|
|
)
|
|
from hub_ai.prompts import SUMMARY_SYSTEM, build_summary_user_prompt
|
|
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),
|
|
"by_account": {
|
|
str(ac.get("key") or ac.get("id")): {
|
|
"key": ac.get("key"),
|
|
"name": ac.get("name"),
|
|
"status": ac.get("status"),
|
|
"funding_usdt": ac.get("funding_usdt"),
|
|
"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 {},
|
|
"issues": ac.get("issues") or [],
|
|
}
|
|
for ac in accounts
|
|
},
|
|
}
|
|
|
|
|
|
def generate_daily_summary(
|
|
exchanges: list[dict],
|
|
*,
|
|
trading_day: str | None = None,
|
|
force: bool = False,
|
|
) -> dict[str, Any]:
|
|
ctx = build_daily_context(exchanges, trading_day=trading_day)
|
|
day = ctx["trading_day"]
|
|
if not force:
|
|
latest = get_latest_summary(day)
|
|
if latest and latest.get("context_hash") == ctx.get("context_hash"):
|
|
return {
|
|
"ok": True,
|
|
"cached": True,
|
|
"trading_day": day,
|
|
"summary": latest,
|
|
"model": latest.get("model") or model_label(),
|
|
}
|
|
|
|
system = SUMMARY_SYSTEM.replace("{trading_day}", day)
|
|
user = build_summary_user_prompt(ctx["text"], day)
|
|
content = generate_text(system=system, user=user, temperature=0.15)
|
|
if content.startswith("AI 调用失败"):
|
|
return {"ok": False, "msg": content, "trading_day": day}
|
|
|
|
stats_snapshot = _stats_snapshot_from_ctx(ctx)
|
|
row = append_summary(
|
|
trading_day=day,
|
|
content_md=content,
|
|
model=model_label(),
|
|
context_hash=ctx.get("context_hash") or "",
|
|
stats_snapshot=stats_snapshot,
|
|
)
|
|
return {
|
|
"ok": True,
|
|
"cached": False,
|
|
"trading_day": day,
|
|
"summary": row,
|
|
"model": model_label(),
|
|
"context": ctx,
|
|
}
|
|
|
|
|
|
def summary_list(trading_day: str | None = None) -> list[dict]:
|
|
return list_summaries(trading_day=trading_day)
|