From fb8c4f6587976f4e981e3131f09ad7beca9c5beb Mon Sep 17 00:00:00 2001 From: Lxy Date: Sat, 23 May 2026 12:48:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=A3=E6=9E=90ai=E5=88=86=E6=9E=90?= =?UTF-8?q?=E5=88=B0=E5=90=84=E4=B8=AA=E5=8D=A1=E7=89=87=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/ai_analysis.py | 6 ++ app/static/futures_analysis.css | 93 ++++++++++++++++++++ app/static/futures_analysis.html | 11 +++ app/static/futures_analysis.js | 143 ++++++++++++++++++++++++++++--- 4 files changed, 242 insertions(+), 11 deletions(-) diff --git a/app/services/ai_analysis.py b/app/services/ai_analysis.py index dddc5a3..bdce2ab 100644 --- a/app/services/ai_analysis.py +++ b/app/services/ai_analysis.py @@ -53,6 +53,12 @@ class AIAnalysisPrompt: "volume": {"status": "放量/缩量/正常", "ratio": 2.0}, "kdj": {"k": 30, "d": 25, "j": 40, "status": "超卖", "signal": "金叉/死叉"}, "conclusion": "择入场结论" + }, + "5min": { + "macd": {"trend": "up/down/neutral", "histogram": "放大/缩小/背离", "position": "零轴上/下"}, + "volume": {"status": "放量/缩量/正常", "ratio": 1.8}, + "kdj": {"k": 50, "d": 45, "j": 60, "status": "超买/超卖/中性", "signal": "金叉/死叉"}, + "conclusion": "精确定位入场点" } }, "kdj_diagnosis": { diff --git a/app/static/futures_analysis.css b/app/static/futures_analysis.css index 2575a38..b600b01 100644 --- a/app/static/futures_analysis.css +++ b/app/static/futures_analysis.css @@ -1967,6 +1967,99 @@ body.theme-minimal .sort-select select:hover { min-height: 120px; } +/* 情景预案卡片样式 */ +.scenario-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.scenario-item { + display: grid; + grid-template-columns: 80px 60px 1fr; + align-items: center; + gap: 12px; + padding: 10px 12px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + transition: all 0.2s ease; +} + +.scenario-item:hover { + border-color: var(--border-glow); + background: var(--bg-card-hover); +} + +.scenario-name { + font-size: 13px; + font-weight: 600; + color: var(--cyan); +} + +.scenario-probability { + font-size: 13px; + font-weight: 600; + color: var(--amber); + text-align: center; +} + +.scenario-action { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; +} + +/* 多周期趋势样式优化 */ +.trends-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; +} + +.trend-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; +} + +.trend-period { + font-size: 12px; + color: var(--text-muted); + font-weight: 500; +} + +.trend-badge { + font-size: 12px; + font-weight: 600; + padding: 4px 10px; + border-radius: 4px; + text-align: center; +} + +.trend-badge.up { + color: var(--green); + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(16, 185, 129, 0.3); +} + +.trend-badge.down { + color: var(--red); + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); +} + +.trend-badge.neutral { + color: var(--amber); + background: rgba(245, 158, 11, 0.1); + border: 1px solid rgba(245, 158, 11, 0.3); +} + .ai-analysis-placeholder { display: flex; flex-direction: column; diff --git a/app/static/futures_analysis.html b/app/static/futures_analysis.html index 853594e..98f5637 100644 --- a/app/static/futures_analysis.html +++ b/app/static/futures_analysis.html @@ -382,6 +382,17 @@ + + +
diff --git a/app/static/futures_analysis.js b/app/static/futures_analysis.js index b5e0ed6..68517a6 100644 --- a/app/static/futures_analysis.js +++ b/app/static/futures_analysis.js @@ -795,15 +795,22 @@ async function showAIHistoryDetail(recordId) { - ${Object.entries(result.four_dimensional).map(([period, d]) => ` - - ${period} - ${d.macd?.trend || '--'} - ${d.volume?.status || '--'} - ${d.kdj?.status || '--'} - ${d.conclusion || '--'} - - `).join('')} + ${(() => { + const periodNames = { '60min': '60分钟', '30min': '30分钟', '15min': '15分钟', '5min': '5分钟' }; + const periodOrder = ['60min', '30min', '15min', '5min']; + const sortedEntries = Object.entries(result.four_dimensional).sort((a, b) => { + return periodOrder.indexOf(a[0]) - periodOrder.indexOf(b[0]); + }); + return sortedEntries.map(([period, d]) => ` + + ${periodNames[period] || period} + ${d.macd?.trend || '--'} + ${d.volume?.status || '--'} + ${d.kdj?.status || '--'} + ${d.conclusion || '--'} + + `).join(''); + })()}
@@ -1421,6 +1428,119 @@ function displayAIAnalysisResult(data) {
`; + + // 同步AI分析数据到主面板各个卡片 + syncAIToPanels(result); +} + +function syncAIToPanels(result) { + const suggestion = result.trading_suggestion || {}; + const fourDim = result.four_dimensional || {}; + const pivotPoints = result.pivot_points || {}; + const kdjDiag = result.kdj_diagnosis || {}; + const scenarios = result.scenario_plans || {}; + + // 1. 同步到AI交易建议卡片 + const suggestionBadge = document.getElementById('suggestion-badge'); + if (suggestionBadge) { + suggestionBadge.textContent = suggestion.direction || '--'; + suggestionBadge.className = `suggestion-badge ${suggestion.direction === '做多' ? 'up' : suggestion.direction === '做空' ? 'down' : 'neutral'}`; + } + + const entryPriceEl = document.getElementById('entry-price'); + if (entryPriceEl) entryPriceEl.textContent = suggestion.entry_range ? `${suggestion.entry_range.min}-${suggestion.entry_range.max}` : '--'; + + const targetPriceEl = document.getElementById('target-price'); + if (targetPriceEl) { + const takeProfit = suggestion.take_profit?.[0]; + targetPriceEl.textContent = takeProfit?.price || '--'; + } + + const stopLossEl = document.getElementById('stop-loss'); + if (stopLossEl) stopLossEl.textContent = suggestion.stop_loss || '--'; + + const riskLevelEl = document.getElementById('risk-level'); + if (riskLevelEl) riskLevelEl.textContent = suggestion.position_size || '--'; + + // 2. 同步到技术指标卡片 + // 从60min周期提取MACD和KDJ信息 + const macd60 = fourDim['60min']?.macd || {}; + const kdj60 = fourDim['60min']?.kdj || {}; + + const macdSignalEl = document.getElementById('macd-signal'); + if (macdSignalEl) macdSignalEl.textContent = macd60.trend || '--'; + + const macdDetailEl = document.getElementById('macd-detail'); + if (macdDetailEl) macdDetailEl.textContent = macd60.position ? `${macd60.position} | ${macd60.histogram || ''}` : '--'; + + const kdjSignalEl = document.getElementById('kdj-signal'); + if (kdjSignalEl) kdjSignalEl.textContent = kdj60.status || '--'; + + const kdjDetailEl = document.getElementById('kdj-detail'); + if (kdjDetailEl) kdjDetailEl.textContent = kdj60.signal || '--'; + + // 3. 同步到关键点位卡片 + if (pivotPoints.r1) { + const r1El = document.getElementById('resistance-1'); + if (r1El) r1El.querySelector('span:last-child').textContent = pivotPoints.r1; + } + if (pivotPoints.r2) { + const r2El = document.getElementById('resistance-2'); + if (r2El) r2El.querySelector('span:last-child').textContent = pivotPoints.r2; + } + if (pivotPoints.pp) { + const ppEl = document.getElementById('pivot-point'); + if (ppEl) ppEl.querySelector('span:last-child').textContent = pivotPoints.pp; + } + if (pivotPoints.s1) { + const s1El = document.getElementById('support-1'); + if (s1El) s1El.querySelector('span:last-child').textContent = pivotPoints.s1; + } + if (pivotPoints.s2) { + const s2El = document.getElementById('support-2'); + if (s2El) s2El.querySelector('span:last-child').textContent = pivotPoints.s2; + } + + // 4. 同步到多周期趋势卡片 + const periodTrendsEl = document.getElementById('period-trends'); + if (periodTrendsEl && Object.keys(fourDim).length > 0) { + const periodNames = { '60min': '60分钟', '30min': '30分钟', '15min': '15分钟', '5min': '5分钟' }; + const periodOrder = ['60min', '30min', '15min', '5min']; + + // 按固定顺序排列周期 + const sortedEntries = Object.entries(fourDim).sort((a, b) => { + return periodOrder.indexOf(a[0]) - periodOrder.indexOf(b[0]); + }); + + periodTrendsEl.innerHTML = sortedEntries.map(([period, data]) => { + const trend = data.conclusion || data.macd?.trend || 'neutral'; + const trendClass = trend.includes('多') || trend === 'up' ? 'up' : trend.includes('空') || trend === 'down' ? 'down' : 'neutral'; + const trendText = trend.includes('多') ? '偏多' : trend.includes('空') ? '偏空' : '震荡'; + return `
${periodNames[period] || period}${trendText}
`; + }).join(''); + } + + // 5. 同步到情景预案卡片 + const scenarioPanel = document.getElementById('scenario-panel'); + const scenarioPlansEl = document.getElementById('scenario-plans'); + if (scenarioPanel && scenarioPlansEl && Object.keys(scenarios).length > 0) { + scenarioPanel.style.display = 'block'; + const scenarioNames = { + 'breakthrough': '突破', + 'consolidation': '震荡', + 'reversal': '反转', + 'news_impact': '消息影响' + }; + scenarioPlansEl.innerHTML = Object.entries(scenarios).map(([key, data]) => ` +
+ ${scenarioNames[key] || key} + ${data.probability || 0}% + ${data.action || '--'} +
+ `).join(''); + } else if (scenarioPanel) { + scenarioPanel.style.display = 'none'; + } } function showAIDetailModal() { @@ -1433,13 +1553,14 @@ function showAIDetailModal() { const modalBody = document.getElementById('ai-analysis-modal-body'); let fourDimensionalHTML = ''; - const periods = ['60min', '30min', '15min']; + const periods = ['60min', '30min', '15min', '5min']; + const periodNames = { '60min': '60分钟', '30min': '30分钟', '15min': '15分钟', '5min': '5分钟' }; periods.forEach(period => { const data = result.four_dimensional?.[period]; if (data) { fourDimensionalHTML += ` - ${period} + ${periodNames[period] || period}
趋势: ${data.macd?.trend || '--'}
位置: ${data.macd?.position || '--'}