Files
DinQuant/backend_api_python/app/routes/polymarket.py
T
TIANHE db91fa4580 v2.2.2
Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
2026-03-01 17:20:37 +08:00

329 lines
12 KiB
Python

"""
Polymarket预测市场API路由
提供按需分析接口(只读,不涉及交易)
"""
from flask import Blueprint, jsonify, request, g
from app.utils.auth import login_required
from app.utils.logger import get_logger
from app.utils.db import get_db_connection
from app.data_sources.polymarket import PolymarketDataSource
import re
import json
logger = get_logger(__name__)
polymarket_bp = Blueprint('polymarket', __name__)
# 初始化服务
polymarket_source = PolymarketDataSource()
@polymarket_bp.route("/analyze", methods=["POST"])
@login_required
def analyze_polymarket():
"""
分析Polymarket预测市场(用户输入链接或标题)
POST /api/polymarket/analyze
Body: {
"input": "https://polymarket.com/event/xxx" 或 "市场标题",
"language": "zh-CN" (optional)
}
流程:
1. 从输入中解析market_id或slug
2. 从API获取市场数据
3. 检查计费并扣除积分
4. 调用AI分析
5. 返回分析结果
"""
try:
from app.services.billing_service import BillingService
from app.services.polymarket_analyzer import PolymarketAnalyzer
from decimal import Decimal
user_id = getattr(g, 'user_id', None)
if not user_id:
return jsonify({
"code": 0,
"msg": "User not authenticated",
"data": None
}), 401
data = request.get_json() or {}
input_text = (data.get('input') or '').strip()
language = data.get('language', 'zh-CN')
if not input_text:
return jsonify({
"code": 0,
"msg": "Input is required (Polymarket URL or market title)",
"data": None
}), 400
# 1. 解析market_id或slug
market_id = None
slug = None
# 尝试从URL中提取
url_patterns = [
r'polymarket\.com/event/([^/?]+)',
r'polymarket\.com/markets/(\d+)',
r'polymarket\.com/market/(\d+)',
]
for pattern in url_patterns:
match = re.search(pattern, input_text)
if match:
extracted = match.group(1)
# 如果是数字,是market_id;否则是slug
if extracted.isdigit():
market_id = extracted
else:
slug = extracted
break
# 如果没有从URL提取到,尝试搜索市场
if not market_id and not slug:
# 尝试通过标题搜索
logger.info(f"Searching for market by title: {input_text[:100]}")
search_results = polymarket_source.search_markets(input_text, limit=5)
if search_results:
# 使用第一个搜索结果
market_id = search_results[0].get('market_id')
logger.info(f"Found market via search: {market_id}")
if not market_id and not slug:
return jsonify({
"code": 0,
"msg": "Could not parse market ID or slug from input. Please provide a valid Polymarket URL or market title.",
"data": None
}), 400
# 2. 获取市场数据
if market_id:
market = polymarket_source.get_market_details(market_id)
elif slug:
# 通过slug查找市场(需要先搜索)
search_results = polymarket_source.search_markets(slug, limit=10)
market = None
for result in search_results:
if result.get('slug') == slug or slug in (result.get('question') or ''):
market = result
market_id = result.get('market_id')
break
if not market and search_results:
# 使用第一个搜索结果
market = search_results[0]
market_id = market.get('market_id')
if not market:
return jsonify({
"code": 0,
"msg": "Market not found. Please check the URL or title.",
"data": None
}), 404
if not market_id:
market_id = market.get('market_id')
if not market_id:
return jsonify({
"code": 0,
"msg": "Invalid market data",
"data": None
}), 400
# 3. 检查计费
billing = BillingService()
cost = 0
if billing.is_billing_enabled():
cost = billing.get_feature_cost('polymarket_deep_analysis')
if cost > 0:
user_credits = billing.get_user_credits(user_id)
if user_credits < Decimal(str(cost)):
return jsonify({
"code": 0,
"msg": "Insufficient credits",
"data": {
"required": cost,
"current": float(user_credits),
"shortage": float(Decimal(str(cost)) - user_credits)
}
}), 400
# 扣除积分(使用check_and_consume方法,它会自动从配置中获取成本)
success, error_msg = billing.check_and_consume(
user_id=user_id,
feature='polymarket_deep_analysis',
reference_id=f"polymarket_{market_id}"
)
if not success:
# 检查是否是积分不足的错误
if error_msg.startswith('insufficient_credits'):
parts = error_msg.split(':')
if len(parts) >= 3:
current_credits = parts[1]
required_credits = parts[2]
return jsonify({
"code": 0,
"msg": "Insufficient credits",
"data": {
"required": float(required_credits),
"current": float(current_credits),
"shortage": float(Decimal(required_credits) - Decimal(current_credits))
}
}), 400
return jsonify({
"code": 0,
"msg": f"Failed to deduct credits: {error_msg}",
"data": None
}), 500
# 4. 执行AI分析(传递语言和模型参数)
analyzer = PolymarketAnalyzer()
model = request.get_json().get('model') # 可选:从请求中获取模型参数
analysis_result = analyzer.analyze_market(
market_id,
user_id=user_id,
use_cache=False,
language=language,
model=model
)
if analysis_result.get('error'):
return jsonify({
"code": 0,
"msg": analysis_result.get('error', 'Analysis failed'),
"data": None
}), 500
# 5. 获取剩余积分
remaining_credits = 0
if billing.is_billing_enabled():
remaining_credits = float(billing.get_user_credits(user_id))
return jsonify({
"code": 1,
"msg": "success",
"data": {
"market": market,
"analysis": analysis_result,
"credits_charged": cost,
"remaining_credits": remaining_credits
}
})
except Exception as e:
logger.error(f"Polymarket analyze API failed: {e}", exc_info=True)
return jsonify({
"code": 0,
"msg": str(e),
"data": None
}), 500
@polymarket_bp.route("/history", methods=["GET"])
@login_required
def get_polymarket_history():
"""
Get user's Polymarket analysis history.
GET /api/polymarket/history?page=1&page_size=20
"""
try:
user_id = g.user_id
page = request.args.get('page', 1, type=int)
page_size = min(request.args.get('page_size', 20, type=int), 100)
offset = (page - 1) * page_size
with get_db_connection() as db:
cur = db.cursor()
# 获取总数
cur.execute("""
SELECT COUNT(*) AS total
FROM qd_analysis_tasks
WHERE user_id = %s AND market = 'Polymarket'
""", (user_id,))
total_row = cur.fetchone()
total = total_row['total'] if total_row else 0
# 获取历史记录
cur.execute("""
SELECT
t.id,
t.symbol AS market_id,
t.model,
t.language,
t.status,
t.created_at,
t.completed_at,
t.result_json
FROM qd_analysis_tasks t
WHERE t.user_id = %s AND t.market = 'Polymarket'
ORDER BY t.created_at DESC
LIMIT %s OFFSET %s
""", (user_id, page_size, offset))
rows = cur.fetchall() or []
cur.close()
# 解析结果
items = []
for row in rows:
result_json = row.get('result_json', '{}')
try:
result_data = json.loads(result_json) if result_json else {}
except:
result_data = {}
market_data = result_data.get('market', {})
analysis_data = result_data.get('analysis', {})
created_at = row.get('created_at')
completed_at = row.get('completed_at')
if created_at and hasattr(created_at, 'isoformat'):
created_at = created_at.isoformat()
if completed_at and hasattr(completed_at, 'isoformat'):
completed_at = completed_at.isoformat()
items.append({
'id': row.get('id'),
'market_id': row.get('market_id'),
'market_title': market_data.get('question') or market_data.get('title') or f"Market {row.get('market_id')}",
'market_url': market_data.get('polymarket_url'),
'ai_predicted_probability': analysis_data.get('ai_predicted_probability'),
'market_probability': analysis_data.get('market_probability'),
'recommendation': analysis_data.get('recommendation'),
'opportunity_score': analysis_data.get('opportunity_score'),
'confidence_score': analysis_data.get('confidence_score'),
'status': row.get('status'),
'created_at': created_at,
'completed_at': completed_at
})
return jsonify({
"code": 1,
"msg": "success",
"data": {
"items": items,
"total": total,
"page": page,
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size
}
})
except Exception as e:
logger.error(f"Get Polymarket history failed: {e}", exc_info=True)
return jsonify({
"code": 0,
"msg": str(e),
"data": None
}), 500