"""内照明心复盘语录 → 交易教练点评。""" from __future__ import annotations from typing import Any from hub_ai.client import generate_text, model_label from hub_ai.rolling_summary import refresh_session_rolling_summary from hub_ai.text_util import clip_text, is_ai_error_reply from hub_ai.config import ( CHAT_MAX_CONTINUATIONS, CHAT_MAX_OUTPUT_TOKENS, CHAT_TEMPERATURE, CHAT_USER_MESSAGE_MAX_CHARS, ) 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, delete_chat_session, get_active_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 _fmt_pct(v: Any) -> str: try: n = float(v) except (TypeError, ValueError): return "—" return f"{n:.1f}%" def _fmt_rr(v: Any) -> str: try: n = float(v) except (TypeError, ValueError): return "—" return f"{n:.2f}:1" 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('win_count') or 0)} / 亏损 {int(stats.get('loss_count') or 0)}," f"平均盈利 {_fmt_pnl(stats.get('avg_win'))},平均亏损 {_fmt_pnl(stats.get('avg_loss'))}," f"胜率 {_fmt_pct(stats.get('win_rate'))},盈亏比 {_fmt_rr(stats.get('profit_loss_ratio'))}," f"最大盈利 {_fmt_pnl(stats.get('max_win'))},最大亏损 {_fmt_pnl(stats.get('max_loss'))}," 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) max_rows = 50 if len(trades) > max_rows: lines.append(f"(共 {len(trades)} 笔,以下展示最近 {max_rows} 笔)") for i, t in enumerate(trades[:max_rows], 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 = clip_text(str(t.get("note") or "").strip(), 80) 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) user_for_prompt = clip_text(text, CHAT_USER_MESSAGE_MAX_CHARS) user_prompt = build_archive_quote_review_prompt( quote_date=day, archive_trades_text=archive_trades_text, user_message=user_for_prompt, ) 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): delete_chat_session(sid) return {"ok": False, "msg": reply} append_chat_message(sid, "user", text) session = append_chat_message(sid, "assistant", reply) refresh_session_rolling_summary( sid, prior_summary="", user_text=text, assistant_text=reply, bot_mode=CHAT_BOT_TRADING, ) session = get_active_session() or session return { "ok": True, "trading_day": day, "session": session, "sessions": list_chat_sessions(), "reply": reply, "model": model_label(), }