""" Polymarket预测市场分析器 分析预测市场,生成AI预测和交易机会推荐 """ import json import re from typing import Dict, List, Any, Optional from datetime import datetime from app.utils.logger import get_logger from app.utils.db import get_db_connection from app.services.llm import LLMService from app.services.market_data_collector import get_market_data_collector from app.data_sources.polymarket import PolymarketDataSource logger = get_logger(__name__) class PolymarketAnalyzer: """预测市场AI分析器""" def __init__(self): self.llm_service = LLMService() self.data_collector = get_market_data_collector() self.polymarket_source = PolymarketDataSource() def analyze_market(self, market_id: str, user_id: int = None, use_cache: bool = True) -> Dict: """ 分析单个预测市场 Args: market_id: 市场ID user_id: 用户ID(可选,用于用户特定分析) use_cache: 是否使用缓存的分析结果(默认True) Returns: 分析结果字典 """ try: # 1. 获取市场数据 market = self.polymarket_source.get_market_details(market_id) if not market: return { "error": "Market not found", "market_id": market_id } # 2. 如果使用缓存,检查是否有缓存的分析结果(30分钟有效) if use_cache: cached_analysis = self._get_cached_analysis(market_id, user_id) if cached_analysis: cache_minutes = 30 # 缓存30分钟 if self._is_analysis_fresh(cached_analysis, max_age_minutes=cache_minutes): logger.debug(f"Using cached analysis for market {market_id}") return cached_analysis # 3. 收集相关数据 related_news = self._get_related_news(market['question']) related_assets = self._identify_related_assets(market['question']) asset_data = self._get_asset_data(related_assets) # 4. AI分析 ai_result = self._ai_predict_probability( question=market['question'], current_market_prob=market['current_probability'], related_news=related_news, asset_data=asset_data ) # 5. 计算机会评分 opportunity_score = self._calculate_opportunity_score( ai_prob=ai_result['predicted_probability'], market_prob=market['current_probability'], confidence=ai_result['confidence'] ) # 6. 生成推荐 recommendation = self._generate_recommendation( divergence=ai_result['predicted_probability'] - market['current_probability'], confidence=ai_result['confidence'] ) # 7. 构建分析结果 analysis_result = { "market_id": market_id, "ai_predicted_probability": ai_result['predicted_probability'], "market_probability": market['current_probability'], "divergence": ai_result['predicted_probability'] - market['current_probability'], "recommendation": recommendation, "confidence_score": ai_result['confidence'], "reasoning": ai_result['reasoning'], "key_factors": ai_result.get('key_factors', []), "risk_factors": ai_result.get('risk_factors', []), "related_assets": related_assets, "risk_level": self._assess_risk(market, ai_result), "opportunity_score": opportunity_score } # 8. 保存到数据库 self._save_analysis_to_db(analysis_result, user_id) return analysis_result except Exception as e: logger.error(f"Failed to analyze market {market_id}: {e}", exc_info=True) return { "error": str(e), "market_id": market_id } def generate_asset_trading_opportunities(self, market_id: str) -> List[Dict]: """ 基于预测市场生成相关资产的交易机会 Args: market_id: 预测市场ID Returns: 资产交易机会列表 """ try: # 1. 分析预测市场 market_analysis = self.analyze_market(market_id) if market_analysis.get('error'): return [] # 2. 识别相关资产 related_assets = market_analysis.get('related_assets', []) if not related_assets: return [] # 3. 对每个资产进行技术分析 opportunities = [] for asset in related_assets: try: # 推断市场类型 market_type = self._infer_market(asset) # 获取资产数据 asset_data = self.data_collector.collect_all( market=market_type, symbol=asset, timeframe="1D", include_polymarket=False # 避免循环 ) # 技术分析 technical_analysis = self._analyze_technical(asset_data) # 结合预测市场信号 if market_analysis['recommendation'] == "YES": # 预测事件发生概率高 → 相关资产可能上涨 signal = "BUY" if technical_analysis.get('trend') == "bullish" else "HOLD" elif market_analysis['recommendation'] == "NO": # 预测事件发生概率低 → 相关资产可能下跌 signal = "SELL" if technical_analysis.get('trend') == "bearish" else "HOLD" else: signal = "HOLD" # 计算综合置信度 confidence = ( market_analysis['confidence_score'] * 0.6 + technical_analysis.get('confidence', 50) * 0.4 ) if signal != "HOLD" and confidence > 60: opportunities.append({ "asset": asset, "market": market_type, "signal": signal, "confidence": round(confidence, 2), "reasoning": f"预测市场分析:{market_analysis['reasoning'][:200]}。技术面:{technical_analysis.get('summary', '')[:200]}", "related_prediction": { "market_id": market_id, "question": market_analysis.get('question', ''), "ai_probability": market_analysis['ai_predicted_probability'], "market_probability": market_analysis['market_probability'] }, "entry_suggestion": technical_analysis.get('entry_suggestion', {}) }) except Exception as e: logger.debug(f"Failed to analyze asset {asset} for market {market_id}: {e}") continue # 保存机会到数据库 if opportunities: self._save_opportunities_to_db(market_id, opportunities) return opportunities except Exception as e: logger.error(f"Failed to generate asset opportunities for {market_id}: {e}") return [] def _ai_predict_probability(self, question: str, current_market_prob: float, related_news: List, asset_data: Dict) -> Dict: """使用AI预测事件概率""" try: # 构建prompt news_text = "\n".join([f"- {n.get('title', '')[:100]}" for n in related_news[:5]]) asset_text = "" if asset_data: price_data = asset_data.get('price', {}) indicators = asset_data.get('indicators', {}) if price_data: asset_text = f""" 相关资产数据: - 当前价格: {price_data.get('current_price', 'N/A')} - 24h涨跌幅: {price_data.get('change_24h', 0):.2f}% - RSI: {indicators.get('rsi', {}).get('value', 'N/A')} - MACD: {indicators.get('macd', {}).get('signal', 'N/A')} """ prompt = f"""分析以下预测市场事件,评估其发生的概率: 问题:{question} 当前市场概率:{current_market_prob}% 相关新闻: {news_text if news_text else "暂无相关新闻"} {asset_text} 请基于以下维度分析: 1. 历史类似事件的成功率 2. 当前新闻和趋势 3. 相关资产价格走势和技术指标 4. 宏观环境因素(VIX、DXY、利率等) 5. 市场情绪指标 输出JSON格式: {{ "predicted_probability": 72.5, // 你预测的概率(0-100) "confidence": 75.0, // 置信度(0-100) "reasoning": "详细分析...", "key_factors": ["因素1", "因素2"], "risk_factors": ["风险1", "风险2"] }}""" # 调用LLM messages = [ { "role": "system", "content": "你是一个专业的市场分析师,擅长分析预测市场事件。请基于提供的数据,客观评估事件发生的概率。" }, { "role": "user", "content": prompt } ] result = self.llm_service.call_llm_api( messages=messages, use_json_mode=True, temperature=0.3 ) # 解析结果 if isinstance(result, str): result = json.loads(result) # 验证和规范化 predicted_prob = float(result.get('predicted_probability', current_market_prob)) predicted_prob = max(0, min(100, predicted_prob)) # 限制在0-100 confidence = float(result.get('confidence', 70)) confidence = max(0, min(100, confidence)) return { 'predicted_probability': round(predicted_prob, 2), 'confidence': round(confidence, 2), 'reasoning': result.get('reasoning', ''), 'key_factors': result.get('key_factors', []), 'risk_factors': result.get('risk_factors', []) } except Exception as e: logger.error(f"AI prediction failed: {e}", exc_info=True) # 返回默认值 return { 'predicted_probability': current_market_prob, 'confidence': 50.0, 'reasoning': f'分析失败: {str(e)}', 'key_factors': [], 'risk_factors': [] } def _calculate_opportunity_score(self, ai_prob: float, market_prob: float, confidence: float) -> float: """ 计算机会评分(0-100) 逻辑: - AI与市场差异越大,机会越好 - 置信度越高,机会越好 """ divergence = abs(ai_prob - market_prob) # 差异越大,机会越好(最大40分) divergence_score = min(divergence * 2, 40) # 置信度越高,机会越好(最大60分) confidence_score = confidence * 0.6 return round(divergence_score + confidence_score, 2) def _generate_recommendation(self, divergence: float, confidence: float) -> str: """ 生成推荐:YES/NO/HOLD 逻辑: - AI概率 > 市场概率 + 5% 且置信度 > 60 → YES - AI概率 < 市场概率 - 5% 且置信度 > 60 → NO - 其他 → HOLD """ if divergence > 5 and confidence > 60: return "YES" elif divergence < -5 and confidence > 60: return "NO" else: return "HOLD" def _assess_risk(self, market: Dict, ai_result: Dict) -> str: """评估风险等级""" confidence = ai_result.get('confidence', 50) divergence = abs(ai_result.get('predicted_probability', 50) - market.get('current_probability', 50)) if confidence < 50 or divergence > 30: return "high" elif confidence < 70 or divergence > 15: return "medium" else: return "low" def _get_related_news(self, question: str) -> List[Dict]: """获取相关问题相关的新闻""" # 提取关键词 keywords = self._extract_keywords(question) # 这里可以调用新闻API,暂时返回空列表 # 实际实现时可以调用现有的新闻服务 return [] def _identify_related_assets(self, question: str) -> List[str]: """识别问题中提到的相关资产""" assets = [] # 加密货币关键词映射 crypto_keywords = { 'BTC': ['BTC', 'Bitcoin', 'bitcoin', 'btc'], 'ETH': ['ETH', 'Ethereum', 'ethereum', 'eth'], 'SOL': ['SOL', 'Solana', 'solana', 'sol'], 'BNB': ['BNB', 'Binance', 'binance', 'bnb'], 'XRP': ['XRP', 'Ripple', 'ripple', 'xrp'], 'ADA': ['ADA', 'Cardano', 'cardano', 'ada'], 'DOGE': ['DOGE', 'Dogecoin', 'dogecoin', 'doge'], 'AVAX': ['AVAX', 'Avalanche', 'avalanche', 'avax'], 'DOT': ['DOT', 'Polkadot', 'polkadot', 'dot'], 'MATIC': ['MATIC', 'Polygon', 'polygon', 'matic'] } question_upper = question.upper() for symbol, keywords in crypto_keywords.items(): if any(kw in question_upper for kw in keywords): assets.append(f"{symbol}/USDT") # 去重 return list(set(assets)) def _get_asset_data(self, assets: List[str]) -> Optional[Dict]: """获取资产数据(取第一个资产)""" if not assets: return None try: asset = assets[0] market_type = self._infer_market(asset) return self.data_collector.collect_all( market=market_type, symbol=asset, timeframe="1D" ) except Exception as e: logger.debug(f"Failed to get asset data for {assets}: {e}") return None def _analyze_technical(self, asset_data: Dict) -> Dict: """简单的技术分析""" if not asset_data: return { 'trend': 'neutral', 'confidence': 50, 'summary': '数据不足', 'entry_suggestion': {} } indicators = asset_data.get('indicators', {}) price_data = asset_data.get('price', {}) # 简单的趋势判断 rsi = indicators.get('rsi', {}).get('value', 50) macd_signal = indicators.get('macd', {}).get('signal', 'neutral') trend = 'neutral' if rsi > 60 and macd_signal == 'bullish': trend = 'bullish' elif rsi < 40 and macd_signal == 'bearish': trend = 'bearish' confidence = 60 if abs(rsi - 50) > 15 else 50 return { 'trend': trend, 'confidence': confidence, 'summary': f'RSI: {rsi:.1f}, MACD: {macd_signal}', 'entry_suggestion': {} } def _infer_market(self, symbol: str) -> str: """推断市场类型""" if '/' in symbol: return "Crypto" elif len(symbol) <= 5 and symbol.isupper(): return "USStock" else: return "Crypto" # 默认 def _extract_keywords(self, text: str) -> List[str]: """提取关键词""" # 简单的关键词提取 words = re.findall(r'\b[A-Z][a-z]+\b|\b[A-Z]{2,}\b', text) return [w.lower() for w in words if len(w) > 2] def _get_cached_analysis(self, market_id: str, user_id: int = None) -> Optional[Dict]: """获取缓存的分析结果""" try: with get_db_connection() as db: cur = db.cursor() query = """ SELECT ai_predicted_probability, market_probability, divergence, recommendation, confidence_score, opportunity_score, reasoning, key_factors, related_assets, created_at FROM qd_polymarket_ai_analysis WHERE market_id = %s """ params = [market_id] if user_id: query += " AND user_id = %s" params.append(user_id) else: query += " AND user_id IS NULL" query += " ORDER BY created_at DESC LIMIT 1" cur.execute(query, params) row = cur.fetchone() cur.close() if row: # RealDictCursor返回字典,使用键访问 key_factors_raw = row.get('key_factors') key_factors = [] if key_factors_raw: try: if isinstance(key_factors_raw, str): key_factors = json.loads(key_factors_raw) else: key_factors = key_factors_raw if isinstance(key_factors_raw, list) else [] except: key_factors = [] return { "market_id": market_id, "ai_predicted_probability": float(row.get('ai_predicted_probability') or 0), "market_probability": float(row.get('market_probability') or 0), "divergence": float(row.get('divergence') or 0), "recommendation": row.get('recommendation') or 'HOLD', "confidence_score": float(row.get('confidence_score') or 0), "opportunity_score": float(row.get('opportunity_score') or 0), "reasoning": row.get('reasoning') or '', "key_factors": key_factors, "related_assets": row.get('related_assets') if row.get('related_assets') else [], "created_at": row.get('created_at') } except Exception as e: logger.debug(f"Failed to get cached analysis: {e}") return None def _is_analysis_fresh(self, analysis: Dict, max_age_minutes: int = 30) -> bool: """检查分析结果是否新鲜""" created_at = analysis.get('created_at') if not created_at: return False if isinstance(created_at, str): created_at = datetime.fromisoformat(created_at.replace('Z', '+00:00')) age = (datetime.now() - created_at.replace(tzinfo=None)).total_seconds() / 60 return age < max_age_minutes def _save_analysis_to_db(self, analysis: Dict, user_id: int = None): """保存分析结果到数据库""" try: with get_db_connection() as db: cur = db.cursor() cur.execute(""" INSERT INTO qd_polymarket_ai_analysis (market_id, user_id, ai_predicted_probability, market_probability, divergence, recommendation, confidence_score, opportunity_score, reasoning, key_factors, related_assets, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW()) """, ( analysis['market_id'], user_id, analysis['ai_predicted_probability'], analysis['market_probability'], analysis['divergence'], analysis['recommendation'], analysis['confidence_score'], analysis['opportunity_score'], analysis['reasoning'], json.dumps(analysis.get('key_factors', [])), analysis.get('related_assets', []) )) db.commit() cur.close() except Exception as e: logger.error(f"Failed to save analysis to DB: {e}") def _save_opportunities_to_db(self, market_id: str, opportunities: List[Dict]): """保存交易机会到数据库""" try: with get_db_connection() as db: cur = db.cursor() for opp in opportunities: cur.execute(""" INSERT INTO qd_polymarket_asset_opportunities (market_id, asset_symbol, asset_market, signal, confidence, reasoning, entry_suggestion, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s, NOW()) """, ( market_id, opp['asset'], opp['market'], opp['signal'], opp['confidence'], opp['reasoning'], json.dumps(opp.get('entry_suggestion', {})) )) db.commit() cur.close() except Exception as e: logger.error(f"Failed to save opportunities to DB: {e}")