"""内照明心复盘语录 → 交易教练点评。""" from __future__ import annotations from typing import Any from hub_ai.chat import _is_ai_error_reply from hub_ai.client import generate_text, model_label from hub_ai.config import CHAT_MAX_CONTINUATIONS, CHAT_MAX_OUTPUT_TOKENS, CHAT_TEMPERATURE from hub_ai.prompts import CHAT_SYSTEM, build_archive_quote_review_prompt from hub_ai.store import CHAT_BOT_TRADING, append_chat_message, create_new_session, list_chat_sessions from hub_symbol_archive_lib import list_daily_trades def _tag_label(tag: str) -> str: t = (tag or "").strip().lower() if t == "sick": return "犯病" if t == "emotion": return "情绪化" return t or "—" def _fmt_pnl(v: Any) -> str: try: n = float(v or 0) except (TypeError, ValueError): return "—" sign = "+" if n > 0 else "" return f"{sign}{n:.2f}U" def format_archive_trades_for_ai(payload: dict[str, Any]) -> str: trades = payload.get("trades") or [] stats = payload.get("stats") or {} lines = [ ( f"统计:开仓 {int(stats.get('open_count') or 0)} 笔," f"犯病 {int(stats.get('sick_count') or 0)} 笔," f"盈亏合计 {_fmt_pnl(stats.get('pnl_total'))}," f"剔除犯病盈亏 {_fmt_pnl(stats.get('pnl_ex_sick'))}" ) ] if not trades: lines.append("(该日无交易记录)") return "\n".join(lines) for i, t in enumerate(trades, 1): ex = str(t.get("exchange_key") or t.get("account_exchange_key") or "—") sym = str(t.get("symbol") or "—") direction = str(t.get("direction") or "—") opened = str(t.get("opened_at") or "—") closed = str(t.get("closed_at") or "—") hold = str(t.get("hold_minutes_text") or t.get("hold_minutes") or "—") result = str(t.get("result") or "—") pnl = _fmt_pnl(t.get("pnl_amount")) entry = str(t.get("entry_type") or t.get("entry_reason") or t.get("monitor_type") or "—") tag = _tag_label(str(t.get("behavior_tag") or "")) note = str(t.get("note") or "").strip() line = ( f"{i}. {ex} | {sym} | {direction} | 开仓类型 {entry} | " f"开 {opened} | 平 {closed} | 持仓 {hold} | 结果 {result} | " f"盈亏 {pnl} | 标签 {tag}" ) if note: line += f" | 备注 {note}" lines.append(line) return "\n".join(lines) def send_archive_quote_review( *, quote_date: str, content: str, ) -> dict[str, Any]: text = (content or "").strip() if not text: return {"ok": False, "msg": "语录内容不能为空"} day = (quote_date or "").strip()[:10] if not day: return {"ok": False, "msg": "语录日期无效"} session = create_new_session( trading_day=day, title=f"复盘 {day}", bot_mode=CHAT_BOT_TRADING, ) sid = session["id"] archive_payload = list_daily_trades(trading_day=day, period="today") archive_trades_text = format_archive_trades_for_ai(archive_payload) append_chat_message(sid, "user", text) user_prompt = build_archive_quote_review_prompt( quote_date=day, archive_trades_text=archive_trades_text, user_message=text, ) reply = generate_text( system=CHAT_SYSTEM, user=user_prompt, temperature=CHAT_TEMPERATURE, max_tokens=CHAT_MAX_OUTPUT_TOKENS, max_continuations=CHAT_MAX_CONTINUATIONS, ) if _is_ai_error_reply(reply): return {"ok": False, "msg": reply, "session_id": sid} session = append_chat_message(sid, "assistant", reply) return { "ok": True, "trading_day": day, "session": session, "sessions": list_chat_sessions(), "reply": reply, "model": model_label(), }