Files
DinQuant/backend_api_python/app/services/polymarket_analyzer.py
T

648 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Polymarket Prediction Market Analyzer
Analyze prediction markets and generate AI predictions and trading opportunity recommendations
"""
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:
"""Prediction Market AI Analyzer"""
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, language: str = 'zh-CN', model: str = None) -> Dict:
"""
Analyze a single prediction market
Args:
market_id: Market ID
user_id: User ID (optional, for user-specific analysis)
use_cache: whether to use cached analysis results (default True)
language: language setting ('zh-CN' or 'en-US'), used to generate AI analysis results in the corresponding language
Returns:
Analysis results dictionary
"""
try:
# 1. Get market data
market = self.polymarket_source.get_market_details(market_id)
if not market:
return {
"error": "Market not found",
"market_id": market_id
}
# 2. If cache is used, check whether there are cached analysis results (valid for 30 minutes)
if use_cache:
cached_analysis = self._get_cached_analysis(market_id, user_id)
if cached_analysis:
cache_minutes = 30 # Cache for 30 minutes
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. Collect relevant data
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 analysis
ai_result = self._ai_predict_probability(
question=market['question'],
current_market_prob=market['current_probability'],
related_news=related_news,
asset_data=asset_data,
language=language
)
# 5. Calculate opportunity scores
opportunity_score = self._calculate_opportunity_score(
ai_prob=ai_result['predicted_probability'],
market_prob=market['current_probability'],
confidence=ai_result['confidence']
)
# 6. Generate recommendations
recommendation = self._generate_recommendation(
divergence=ai_result['predicted_probability'] - market['current_probability'],
confidence=ai_result['confidence']
)
# 7. Construct analysis results
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. Save to database
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]:
"""
Generate trading opportunities for related assets based on prediction markets
Args:
market_id: prediction market ID
Returns:
List of asset trading opportunities
"""
try:
# 1. Analyze prediction markets
market_analysis = self.analyze_market(market_id)
if market_analysis.get('error'):
return []
# 2. Identify relevant assets
related_assets = market_analysis.get('related_assets', [])
if not related_assets:
return []
# 3. Perform technical analysis on each asset
opportunities = []
for asset in related_assets:
try:
# Infer market type
market_type = self._infer_market(asset)
# Get asset data
asset_data = self.data_collector.collect_all(
market=market_type,
symbol=asset,
timeframe="1D",
include_polymarket=False # avoid loops
)
# technical analysis
technical_analysis = self._analyze_technical(asset_data)
# Incorporate Predictive Market Signals
if market_analysis['recommendation'] == "YES":
# Predicted event probability is high → related assets may rise
signal = "BUY" if technical_analysis.get('trend') == "bullish" else "HOLD"
elif market_analysis['recommendation'] == "NO":
# The probability of the predicted event is low → the related assets may fall
signal = "SELL" if technical_analysis.get('trend') == "bearish" else "HOLD"
else:
signal = "HOLD"
# Calculate overall confidence
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
# Save opportunity to database
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, language: str = 'zh-CN') -> Dict:
"""Using AI to predict event probabilities"""
try:
# Build prompt based on language settings
is_english = language.lower() in ['en', 'en-us', 'en_us']
# build 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:
if is_english:
asset_text = f"""
Related Asset Data:
- Current Price: {price_data.get('current_price', 'N/A')}
- 24h Change: {price_data.get('change_24h', 0):.2f}%
- RSI: {indicators.get('rsi', {}).get('value', 'N/A')}
- MACD: {indicators.get('macd', {}).get('signal', 'N/A')}
"""
else:
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')}
"""
if is_english:
prompt = f"""Analyze the following prediction market event and assess its probability of occurrence:
Question: {question}
Current Market Probability: {current_market_prob}%
Related News:
{news_text if news_text else "No related news available"}
{asset_text}
Please analyze based on the following dimensions:
1. Success rate of similar historical events
2. Current news and trends
3. Related asset price movements and technical indicators
4. Macro environment factors (VIX, DXY, interest rates, etc.)
5. Market sentiment indicators
Output JSON format:
{{
"predicted_probability": 72.5, // Your predicted probability (0-100)
"confidence": 75.0, // Confidence level (0-100)
"reasoning": "Detailed analysis...",
"key_factors": ["Factor 1", "Factor 2"],
"risk_factors": ["Risk 1", "Risk 2"]
}}
IMPORTANT: All text in the JSON response (reasoning, key_factors, risk_factors) must be in English."""
system_prompt = "You are a professional market analyst specializing in prediction market analysis. Please objectively assess the probability of events occurring based on the provided data. Respond in English."
else:
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"]
}}
重要提示:JSON响应中的所有文本(reasoning、key_factors、risk_factors)必须使用中文。"""
system_prompt = "你是一个专业的市场分析师,擅长分析预测市场事件。请基于提供的数据,客观评估事件发生的概率。请使用中文回答。"
# Call LLM
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": prompt
}
]
result = self.llm_service.call_llm_api(
messages=messages,
use_json_mode=True,
temperature=0.3
)
# Parse results
if isinstance(result, str):
result = json.loads(result)
# Validation and normalization
predicted_prob = float(result.get('predicted_probability', current_market_prob))
predicted_prob = max(0, min(100, predicted_prob)) # Limit to 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 to default value
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:
"""
Calculate opportunity rating (0-100)
logic:
- The greater the difference between AI and the market, the better the opportunity
- The higher the confidence, the better the chance
"""
divergence = abs(ai_prob - market_prob)
# The bigger the difference, the better the chance (maximum 40 points)
divergence_score = min(divergence * 2, 40)
# The higher the confidence level, the better the chance (maximum 60 points)
confidence_score = confidence * 0.6
return round(divergence_score + confidence_score, 2)
def _generate_recommendation(self, divergence: float, confidence: float) -> str:
"""
Generate recommendations: YES/NO/HOLD
logic:
- AI Probability > Market Probability + 5% and Confidence > 60 → YES
- AI probability < market probability - 5% and confidence level > 60 → NO
- Others → 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:
"""Assess risk level"""
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]:
"""Get news on relevant issues"""
# Extract keywords
keywords = self._extract_keywords(question)
# Here you can call the news API and temporarily return an empty list
# In actual implementation, existing news services can be called
return []
def _identify_related_assets(self, question: str) -> List[str]:
"""Identify related assets mentioned in the question"""
assets = []
# Cryptocurrency Keyword Mapping
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")
# Remove duplicates
return list(set(assets))
def _get_asset_data(self, assets: List[str]) -> Optional[Dict]:
"""Get asset data (get the first asset)"""
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:
"""simple technical analysis"""
if not asset_data:
return {
'trend': 'neutral',
'confidence': 50,
'summary': '数据不足',
'entry_suggestion': {}
}
indicators = asset_data.get('indicators', {})
price_data = asset_data.get('price', {})
# Simple trend judgment
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:
"""Infer market type"""
if '/' in symbol:
return "Crypto"
elif len(symbol) <= 5 and symbol.isupper():
return "USStock"
else:
return "Crypto" # default
def _extract_keywords(self, text: str) -> List[str]:
"""Extract keywords"""
# Simple keyword extraction
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]:
"""Get cached analysis results"""
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 returns the dictionary, accessed using keys
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, language: str = 'en-US', model: str = None):
"""
Save analysis results to database
Args:
analysis: dictionary of analysis results
user_id: user ID
language: language settings
model: model used
"""
try:
with get_db_connection() as db:
cur = db.cursor()
# 1. Save to qd_polymarket_ai_analysis table (Polymarket special table)
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', [])
))
# 2. Save to the qd_analysis_tasks table at the same time (for administrator statistics and unified historical record viewing)
market_info = analysis.get('market', {})
market_title = market_info.get('question', '') or market_info.get('title', '') or f"Polymarket Market {analysis['market_id']}"
result_json = json.dumps({
'market_id': analysis['market_id'],
'market_title': market_title,
'analysis': analysis,
'market': market_info,
'type': 'polymarket' # Mark as Polymarket analysis
}, ensure_ascii=False)
cur.execute("""
INSERT INTO qd_analysis_tasks
(user_id, market, symbol, model, language, status, result_json, error_message, created_at, completed_at)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
RETURNING id
""", (
int(user_id) if user_id else 1,
'Polymarket', # market field
str(analysis['market_id']), # symbol field stores market_id
str(model) if model else '',
str(language),
'completed',
result_json,
''
))
task_row = cur.fetchone()
task_id = task_row['id'] if task_row else None
db.commit()
cur.close()
if task_id:
logger.debug(f"Saved Polymarket analysis to both tables: task_id={task_id}, market_id={analysis['market_id']}")
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}")