diff --git a/app/api/__pycache__/futures_analysis.cpython-311.pyc b/app/api/__pycache__/futures_analysis.cpython-311.pyc index 377031d..f684a90 100644 Binary files a/app/api/__pycache__/futures_analysis.cpython-311.pyc and b/app/api/__pycache__/futures_analysis.cpython-311.pyc differ diff --git a/app/api/futures_analysis.py b/app/api/futures_analysis.py index ad75ff7..d64af42 100644 --- a/app/api/futures_analysis.py +++ b/app/api/futures_analysis.py @@ -870,7 +870,7 @@ def get_ai_analysis(symbol: str, force_refresh: bool = False, db: Session = Depe @router.get("/ai-analysis/{symbol}/history") -def get_ai_analysis_history(symbol: str, limit: int = 10, analysis_db: Session = Depends(get_analysis_db)): +def get_ai_analysis_history(symbol: str, limit: int = 20, analysis_db: Session = Depends(get_analysis_db)): """获取AI分析历史记录""" try: records = analysis_db.query(AIAnalysisCache).filter( @@ -885,9 +885,7 @@ def get_ai_analysis_history(symbol: str, limit: int = 10, analysis_db: Session = "id": r.id, "symbol": r.symbol, "analysis_time": r.created_at.isoformat(), - "summary": r.analysis_data.get("summary", ""), - "trading_suggestion": r.analysis_data.get("trading_suggestion", {}), - "confidence": r.analysis_data.get("trading_suggestion", {}).get("confidence", 0) + "analysis_data": r.analysis_data } for r in records] } except Exception as e: @@ -896,3 +894,34 @@ def get_ai_analysis_history(symbol: str, limit: int = 10, analysis_db: Session = "success": False, "error": f"获取历史记录失败: {str(e)}" } + + +@router.get("/ai-analysis/history/{record_id}") +def get_ai_analysis_detail(record_id: int, analysis_db: Session = Depends(get_analysis_db)): + """获取单条AI分析记录详情""" + try: + record = analysis_db.query(AIAnalysisCache).filter( + AIAnalysisCache.id == record_id + ).first() + + if not record: + return { + "success": False, + "error": "记录不存在" + } + + return { + "success": True, + "data": { + "id": record.id, + "symbol": record.symbol, + "analysis_time": record.created_at.isoformat(), + "analysis_data": record.analysis_data + } + } + except Exception as e: + logger.error(f"获取AI分析详情失败: {e}") + return { + "success": False, + "error": f"获取记录详情失败: {str(e)}" + } diff --git a/app/static/futures_analysis.css b/app/static/futures_analysis.css index d60e210..2575a38 100644 --- a/app/static/futures_analysis.css +++ b/app/static/futures_analysis.css @@ -1597,6 +1597,328 @@ body.theme-minimal .sort-select select:hover { border-color: var(--text-muted); } +/* AI历史记录详情弹窗样式 */ +.ai-history-detail { + padding: 8px 0; +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color); +} + +.detail-header h4 { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 8px; +} + +.detail-header h4 i { + color: var(--cyan); +} + +.detail-time { + font-size: 12px; + color: var(--text-muted); + display: flex; + align-items: center; + gap: 6px; +} + +.detail-summary { + background: rgba(6, 182, 212, 0.05); + border-left: 3px solid var(--cyan); + padding: 12px 16px; + margin-bottom: 16px; + border-radius: 4px; + display: flex; + gap: 10px; + align-items: flex-start; +} + +.detail-summary i { + color: var(--cyan); + font-size: 14px; + margin-top: 2px; +} + +.detail-summary p { + font-size: 14px; + color: var(--text-secondary); + line-height: 1.6; + flex: 1; +} + +.detail-suggestion { + margin-bottom: 16px; +} + +.suggestion-card { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + border-radius: 8px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); +} + +.suggestion-card.long { + border-color: var(--green); + background: rgba(16, 185, 129, 0.05); +} + +.suggestion-card.short { + border-color: var(--red); + background: rgba(239, 68, 68, 0.05); +} + +.suggestion-card.neutral { + border-color: var(--amber); + background: rgba(245, 158, 11, 0.05); +} + +.suggestion-card i { + font-size: 24px; +} + +.suggestion-card.long i { + color: var(--green); +} + +.suggestion-card.short i { + color: var(--red); +} + +.suggestion-card.neutral i { + color: var(--amber); +} + +.suggestion-text { + font-size: 20px; + font-weight: 700; + flex: 1; +} + +.suggestion-card.long .suggestion-text { + color: var(--green); +} + +.suggestion-card.short .suggestion-text { + color: var(--red); +} + +.suggestion-card.neutral .suggestion-text { + color: var(--amber); +} + +.confidence-text { + font-size: 14px; + color: var(--text-muted); +} + +.detail-metrics { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; + margin-bottom: 20px; +} + +.metric-card { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 12px; + text-align: center; +} + +.metric-label { + display: block; + font-size: 11px; + color: var(--text-muted); + margin-bottom: 6px; +} + +.metric-value { + display: block; + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.metric-value.down { + color: var(--red); +} + +.detail-section { + margin-bottom: 20px; +} + +.detail-section h5 { + font-size: 14px; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: 12px; + display: flex; + align-items: center; + gap: 8px; +} + +.detail-section h5 i { + color: var(--cyan); + font-size: 13px; +} + +.four-d-table { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +.four-d-table th { + background: var(--bg-secondary); + padding: 8px 10px; + text-align: left; + color: var(--text-muted); + font-weight: 600; + border-bottom: 1px solid var(--border-color); +} + +.four-d-table td { + padding: 10px; + border-bottom: 1px solid rgba(56, 189, 248, 0.05); + color: var(--text-secondary); +} + +.four-d-table tr:hover td { + background: rgba(6, 182, 212, 0.03); +} + +.kdj-diagnosis-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; +} + +.kdj-item { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 10px 12px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.kdj-label { + font-size: 11px; + color: var(--text-muted); +} + +.kdj-value { + font-size: 13px; + color: var(--text-secondary); + font-weight: 500; +} + +.pivot-points-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 8px; +} + +.pivot-item { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 10px; + text-align: center; + display: flex; + flex-direction: column; + gap: 4px; +} + +.pivot-item span:first-child { + font-size: 11px; + color: var(--text-muted); + font-weight: 600; +} + +.pivot-item strong { + font-size: 14px; + color: var(--text-primary); +} + +.pivot-item.resistance span:first-child { + color: var(--red); +} + +.pivot-item.resistance strong { + color: var(--red); +} + +.pivot-item.support span:first-child { + color: var(--green); +} + +.pivot-item.support strong { + color: var(--green); +} + +.pivot-item.center { + border-color: var(--purple); + background: rgba(139, 92, 246, 0.05); +} + +.pivot-item.center span:first-child { + color: var(--purple); +} + +.pivot-item.center strong { + color: var(--purple); +} + +.warning-list { + list-style: none; + padding: 0; +} + +.warning-list li { + background: rgba(245, 158, 11, 0.05); + border-left: 3px solid var(--amber); + padding: 10px 12px; + margin-bottom: 8px; + border-radius: 4px; + font-size: 12px; + color: var(--text-secondary); + line-height: 1.5; +} + +.warning-list li:last-child { + margin-bottom: 0; +} + +@media (max-width: 768px) { + .detail-metrics { + grid-template-columns: repeat(2, 1fr); + } + + .pivot-points-grid { + grid-template-columns: repeat(3, 1fr); + } + + .kdj-diagnosis-grid { + grid-template-columns: 1fr; + } +} + /* ============================================ AI智能分析样式 ============================================ */ diff --git a/app/static/futures_analysis.js b/app/static/futures_analysis.js index 03b10ab..b5e0ed6 100644 --- a/app/static/futures_analysis.js +++ b/app/static/futures_analysis.js @@ -520,7 +520,7 @@ function updateDetailView(data) { async function loadHistoryList(symbol) { try { - const response = await fetch(`${API_BASE}/analysis/history/${symbol}?limit=10`); + const response = await fetch(`${API_BASE}/ai-analysis/${symbol}/history?limit=20`); const data = await response.json(); if (data.success) { renderHistoryList(data.data); @@ -537,13 +537,18 @@ async function loadAIAnalysis() { const content = document.getElementById('ai-analysis-content'); try { + console.log(`加载合约 ${currentSymbol} 的AI分析...`); const response = await fetch(`${API_BASE}/ai-analysis/${currentSymbol}`); const data = await response.json(); + console.log(`合约 ${currentSymbol} AI分析响应:`, data); + if (data.success && data.data) { + console.log(`合约 ${currentSymbol} 分析数据 - symbol:`, data.data.symbol); currentAIAnalysis = data.data; displayAIAnalysisResult(data.data); } else { + console.log(`合约 ${currentSymbol} 无分析结果`); content.innerHTML = `
@@ -552,7 +557,7 @@ async function loadAIAnalysis() { `; } } catch (error) { - console.error('加载AI分析失败:', error); + console.error(`加载合约 ${currentSymbol} AI分析失败:`, error); content.innerHTML = `
@@ -569,28 +574,34 @@ function renderHistoryList(records) { return; } - container.innerHTML = records.map(record => ` -
-
- ${record.analysis_time ? record.analysis_time.replace('T', ' ').substring(0, 16) : '--'} - ${record.suggestion || '--'} - 评分: ${record.trend_score || '--'} -
-
-
- MACD - ${record.macd_signal || '--'} + console.log('渲染历史记录,记录数量:', records.length); + console.log('历史记录合约分布:', records.map(r => r.symbol)); + + container.innerHTML = records.map(record => { + const analysisData = record.analysis_data || {}; + const suggestion = analysisData.trading_suggestion || {}; + const timeStr = record.analysis_time ? record.analysis_time.replace('T', ' ').substring(0, 16) : '--'; + const summary = analysisData.summary || '--'; + const direction = suggestion.direction || '--'; + const confidence = suggestion.confidence || 0; + + console.log(`历史记录 ID:${record.id} 合约:${record.symbol}`); + + return ` +
+
+ ${timeStr} + ${summary.substring(0, 30)}${summary.length > 30 ? '...' : ''} + 方向: ${direction} | 置信度: ${confidence}%
-
- RSI - ${record.rsi_value || '--'} +
+
-
-
- `).join(''); + `; + }).join(''); } function showSuggestionModal(data) { @@ -717,10 +728,13 @@ function showHistoryModal(record) { } async function loadKlineData(symbol, period) { + if (!symbol) return; + try { const response = await fetch(`${API_BASE}/kline/${symbol}?period=${period}`); const data = await response.json(); - if (data.success) { + + if (data.success && data.data) { renderKlineChart(data.data); } } catch (error) { @@ -728,6 +742,161 @@ async function loadKlineData(symbol, period) { } } +async function showAIHistoryDetail(recordId) { + try { + const response = await fetch(`${API_BASE}/ai-analysis/history/${recordId}`); + const data = await response.json(); + + if (data.success && data.data) { + const record = data.data; + const result = record.analysis_data; + const timestamp = new Date(record.analysis_time).toLocaleString('zh-CN'); + + // 构建弹窗内容 + const modalBody = document.getElementById('ai-analysis-modal-body'); + + const direction = result.trading_suggestion?.direction || '观望'; + const directionClass = direction === '做多' ? 'long' : direction === '做空' ? 'short' : 'neutral'; + const directionIcon = direction === '做多' ? 'fa-arrow-up' : direction === '做空' ? 'fa-arrow-down' : 'fa-arrows-left-right'; + const confidence = result.trading_suggestion?.confidence || 0; + + modalBody.innerHTML = ` +
+
+

${record.symbol} AI分析报告

+ ${timestamp} +
+ +
+ +

${result.summary || '暂无总结'}

+
+ + + + + ${result.four_dimensional ? ` +
+
AI思维分析
+ + + + + + + + + + + + ${Object.entries(result.four_dimensional).map(([period, d]) => ` + + + + + + + + `).join('')} + +
周期MACD趋势成交量KDJ状态结论
${period}${d.macd?.trend || '--'}${d.volume?.status || '--'}${d.kdj?.status || '--'}${d.conclusion || '--'}
+
+ ` : ''} + +
+
+ 入场区间 + ${result.trading_suggestion?.entry_range?.min || '--'}-${result.trading_suggestion?.entry_range?.max || '--'} +
+
+ 止损位 + ${result.trading_suggestion?.stop_loss || '--'} +
+
+ 建议仓位 + ${result.trading_suggestion?.position_size || '--'} +
+
+ 纪律评分 + ${result.discipline_score?.total || '--'}/${result.discipline_score?.max || '11'} +
+
+ + ${result.kdj_diagnosis ? ` +
+
KDJ诊断
+
+
+ 当前状态 + ${result.kdj_diagnosis.current_status || '--'} +
+
+ 背离 + ${result.kdj_diagnosis.divergence || '--'} +
+
+ 钝化 + ${result.kdj_diagnosis.paralysis || '--'} +
+
+ 建议 + ${result.kdj_diagnosis.recommendation || '--'} +
+
+
+ ` : ''} + + ${result.pivot_points ? ` +
+
关键点位
+
+
+ R2${result.pivot_points.r2 || '--'} +
+
+ R1${result.pivot_points.r1 || '--'} +
+
+ PP${result.pivot_points.pp || '--'} +
+
+ S1${result.pivot_points.s1 || '--'} +
+
+ S2${result.pivot_points.s2 || '--'} +
+
+
+ ` : ''} + + ${result.risk_warnings && result.risk_warnings.length > 0 ? ` +
+
风险提示
+
    + ${result.risk_warnings.map(w => `
  • ${w}
  • `).join('')} +
+
+ ` : ''} +
+ `; + + // 显示弹窗 + document.getElementById('ai-analysis-modal').classList.add('active'); + } else { + showToast('error', '加载失败', data.error || '记录不存在'); + } + } catch (error) { + console.error('加载历史记录详情失败:', error); + showToast('error', '加载失败', '网络错误,请稍后重试'); + } +} + function renderKlineChart(data) { if (klineChart) { klineChart.dispose(); diff --git a/check_cache.py b/check_cache.py new file mode 100644 index 0000000..77b12e4 --- /dev/null +++ b/check_cache.py @@ -0,0 +1,12 @@ +import sqlite3 + +conn = sqlite3.connect('data/futures_analysis.db') +cursor = conn.execute('SELECT id, symbol, created_at FROM ai_analysis_cache ORDER BY created_at DESC LIMIT 10') + +print('AI分析缓存记录:') +print('ID | 合约 | 创建时间') +print('-' * 60) +for row in cursor: + print(f'{row[0]} | {row[1]} | {row[2]}') + +conn.close()