""" 交易反思 AI 分析服务 - 基于反思内容重新分析交易并提炼经验 """ import json import logging import re from typing import Dict, List, Optional from sqlalchemy.orm import Session from app.analysis_models import TradePair, TradeReflection, TradeAIAnalysis, TradeExperience from app.services.ai_analysis import AIFuturesAnalyzer logger = logging.getLogger(__name__) REFLECTION_ANALYSIS_PROMPT = """你是一位资深的交易心理与策略复盘教练。请基于以下交易数据和交易者的反思内容,进行深度分析。 === 交易基础信息 === 品种:{variety} 方向:{direction_text} 开仓日期时间:{open_date} {open_time} 平仓日期时间:{close_date} {close_time} 开仓均价:{open_price} 平仓均价:{close_price} 交易手数:{volume} 平仓盈亏:{close_pnl} 手续费:{commission} 净盈亏:{net_pnl} === 交易者反思内容 === 入场理由:{entry_reason} 入场时机评价:{entry_timing} 仓位管理反思:{position_management} 出场理由:{exit_reason} 出场时机评价:{exit_timing} 纪律评分(1-5):{discipline_score} 自由反思:{free_reflection} === 当日整体反思 === 情绪状态:{emotion_state} 市场判断:{market_judgment} 当日总结:{daily_summary} 改进方向:{daily_improvements} === 任务要求 === 请严格按以下 JSON 格式输出分析结果(不要输出任何其他内容): {{ "overall_evaluation": "综合评价:这笔交易的执行质量、决策逻辑、与市场的匹配度等", "strengths": ["优点1", "优点2", "优点3"], "weaknesses": ["不足1", "不足2", "不足3"], "experience_suggestion": "提炼出的核心经验教训或改进建议,语言简洁 actionable", "suggestion_type": "lesson", "actionable_plan": ["下次可以做的具体改进1", "下次可以做的具体改进2"] }} 注意: 1. suggestion_type 只能是 lesson(经验)、tip(技巧)、warning(警告)三者之一 2. 必须结合反思内容进行分析,不要脱离交易者自己的总结 3. 如果交易亏损,重点分析亏损原因和避免方案 4. 如果交易盈利,重点分析盈利是否来自计划还是运气 """ def _timing_text(timing: Optional[str]) -> str: """时机评价转中文""" mapping = {"good": "良好", "fair": "一般", "poor": "较差"} return mapping.get(timing, timing or "未评价") def _direction_text(direction: Optional[str]) -> str: """方向转中文""" mapping = {"long": "多头", "short": "空头"} return mapping.get(direction, direction or "未知") def _get_records_text(db: Session, pair: TradePair) -> str: """获取配对关联的原始记录信息""" from app.analysis_models import TradeRecord record_ids = (pair.open_record_ids or []) + (pair.close_record_ids or []) if not record_ids: return "" records = db.query(TradeRecord).filter(TradeRecord.id.in_(record_ids)).all() lines = [] for r in sorted(records, key=lambda x: f"{x.trade_date} {x.trade_time or ''}"): lines.append(f"{r.trade_date} {r.trade_time or ''} {r.symbol} {r.direction}{r.offset} {r.price}×{r.volume}") return "\n".join(lines) def build_reflection_prompt(db: Session, pair: TradePair, reflection: TradeReflection, daily_reflection: Optional[Dict] = None) -> str: """构建反思增强分析提示词""" daily = daily_reflection or {} # 获取第一条开仓和第一条平仓记录的时间 from app.analysis_models import TradeRecord open_time = "" close_time = "" open_record_ids = pair.open_record_ids or [] close_record_ids = pair.close_record_ids or [] if open_record_ids: open_rec = db.query(TradeRecord).filter(TradeRecord.id == open_record_ids[0]).first() if open_rec: open_time = open_rec.trade_time or "" if close_record_ids: close_rec = db.query(TradeRecord).filter(TradeRecord.id == close_record_ids[0]).first() if close_rec: close_time = close_rec.trade_time or "" return REFLECTION_ANALYSIS_PROMPT.format( variety=pair.variety, direction_text=_direction_text(pair.direction), open_date=pair.open_date or "", open_time=open_time, close_date=pair.close_date or "", close_time=close_time, open_price=pair.open_price, close_price=pair.close_price, volume=pair.total_volume, close_pnl=pair.close_pnl, commission=pair.total_commission, net_pnl=pair.net_pnl, entry_reason=reflection.entry_reason or "未填写", entry_timing=_timing_text(reflection.entry_timing), position_management=reflection.position_management or "未填写", exit_reason=reflection.exit_reason or "未填写", exit_timing=_timing_text(reflection.exit_timing), discipline_score=reflection.discipline_score or "未评分", free_reflection=reflection.free_reflection or "未填写", emotion_state=daily.get("emotion_state", "未填写"), market_judgment=daily.get("market_judgment", "未填写"), daily_summary=daily.get("summary", "未填写"), daily_improvements=daily.get("improvements", "未填写"), ) def _parse_ai_response(response: str) -> Dict: """解析 AI 返回的 JSON""" try: # 尝试直接解析 data = json.loads(response) return data except json.JSONDecodeError: pass # 尝试从 Markdown 代码块中提取 code_block_pattern = re.compile(r'```(?:json)?\s*([\s\S]*?)```', re.MULTILINE) matches = code_block_pattern.findall(response) for match in matches: try: return json.loads(match.strip()) except json.JSONDecodeError: continue # 尝试从文本中提取第一个 JSON 对象 json_pattern = re.compile(r'\{[\s\S]*\}') matches = json_pattern.findall(response) for match in matches: try: return json.loads(match) except json.JSONDecodeError: continue # 解析失败,返回原始内容作为 overall_evaluation return { "overall_evaluation": response, "strengths": [], "weaknesses": [], "experience_suggestion": "", "suggestion_type": "lesson", "actionable_plan": [], } def _get_next_version(db: Session, trade_pair_id: int) -> int: """获取下一个版本号""" latest = db.query(TradeAIAnalysis).filter( TradeAIAnalysis.trade_pair_id == trade_pair_id ).order_by(TradeAIAnalysis.version.desc()).first() return (latest.version + 1) if latest else 1 def analyze_with_reflection(db: Session, trade_pair_id: int) -> Dict: """ 基于反思内容重新分析交易 返回:{success, data/version, message} """ pair = db.query(TradePair).filter(TradePair.id == trade_pair_id).first() if not pair: return {"success": False, "message": "交易配对不存在"} reflection = db.query(TradeReflection).filter( TradeReflection.trade_pair_id == trade_pair_id ).first() if not reflection: return {"success": False, "message": "该交易尚未填写反思,无法进行分析"} # 获取当日反思 daily_reflection = None from app.analysis_models import DailyReflection dr = db.query(DailyReflection).filter( DailyReflection.reflection_date == pair.close_date ).first() if dr: daily_reflection = { "emotion_state": dr.emotion_state, "market_judgment": dr.market_judgment, "summary": dr.summary, "improvements": dr.improvements, } # 构建提示词 prompt = build_reflection_prompt(db, pair, reflection, daily_reflection) # 调用 AI analyzer = AIFuturesAnalyzer(db) model = analyzer.get_active_model() if not model: return {"success": False, "message": "未配置AI模型或模型未激活"} response = analyzer.call_ai_model(prompt, model) if not response: return {"success": False, "message": "AI模型返回空响应,请稍后重试"} # 解析结果 parsed = _parse_ai_response(response) # 计算版本号 version = _get_next_version(db, trade_pair_id) # 保存分析结果 analysis = TradeAIAnalysis( trade_pair_id=trade_pair_id, version=version, overall_evaluation=parsed.get("overall_evaluation", ""), strengths=parsed.get("strengths", []), weaknesses=parsed.get("weaknesses", []), experience_suggestion=parsed.get("experience_suggestion", ""), suggestion_type=parsed.get("suggestion_type", "lesson"), raw_response=response, prompt_snapshot=prompt, ) db.add(analysis) # 更新反思的 AI 分析状态 reflection.ai_analyzed = True reflection.ai_version = version db.commit() return { "success": True, "data": { "version": version, "overall_evaluation": analysis.overall_evaluation, "strengths": analysis.strengths, "weaknesses": analysis.weaknesses, "experience_suggestion": analysis.experience_suggestion, "suggestion_type": analysis.suggestion_type, "actionable_plan": parsed.get("actionable_plan", []), }, } def get_analysis_history(db: Session, trade_pair_id: int) -> List[Dict]: """获取交易的所有 AI 分析版本""" analyses = db.query(TradeAIAnalysis).filter( TradeAIAnalysis.trade_pair_id == trade_pair_id ).order_by(TradeAIAnalysis.version.desc()).all() return [{ "id": a.id, "version": a.version, "overall_evaluation": a.overall_evaluation, "strengths": a.strengths, "weaknesses": a.weaknesses, "experience_suggestion": a.experience_suggestion, "suggestion_type": a.suggestion_type, "saved_to_experience": a.saved_to_experience, "experience_id": a.experience_id, "created_at": a.created_at.strftime('%Y-%m-%d %H:%M:%S') if a.created_at else None, } for a in analyses] def get_latest_analysis(db: Session, trade_pair_id: int) -> Optional[Dict]: """获取最新 AI 分析结果""" analysis = db.query(TradeAIAnalysis).filter( TradeAIAnalysis.trade_pair_id == trade_pair_id ).order_by(TradeAIAnalysis.version.desc()).first() if not analysis: return None return { "id": analysis.id, "version": analysis.version, "overall_evaluation": analysis.overall_evaluation, "strengths": analysis.strengths, "weaknesses": analysis.weaknesses, "experience_suggestion": analysis.experience_suggestion, "suggestion_type": analysis.suggestion_type, "saved_to_experience": analysis.saved_to_experience, "experience_id": analysis.experience_id, "created_at": analysis.created_at.strftime('%Y-%m-%d %H:%M:%S') if analysis.created_at else None, } def save_suggestion_as_experience(db: Session, analysis_id: int, title: Optional[str] = None, content: Optional[str] = None, tags: Optional[List[str]] = None) -> Dict: """将 AI 分析建议保存为经验""" analysis = db.query(TradeAIAnalysis).filter(TradeAIAnalysis.id == analysis_id).first() if not analysis: return {"success": False, "message": "分析记录不存在"} pair = db.query(TradePair).filter(TradePair.id == analysis.trade_pair_id).first() exp_title = title or analysis.experience_suggestion[:50] if analysis.experience_suggestion else "AI提炼经验" exp_content = content or analysis.experience_suggestion or "" exp = TradeExperience( title=exp_title, content=exp_content, exp_type=analysis.suggestion_type or "lesson", source_pair_id=analysis.trade_pair_id, source_date=pair.close_date if pair else None, tags=tags or [], ) db.add(exp) db.flush() analysis.saved_to_experience = True analysis.experience_id = exp.id db.commit() return {"success": True, "data": {"experience_id": exp.id}}