|
|
|
@ -520,7 +520,7 @@ function updateDetailView(data) {
|
|
|
|
|
|
|
|
|
|
|
|
async function loadHistoryList(symbol) {
|
|
|
|
async function loadHistoryList(symbol) {
|
|
|
|
try {
|
|
|
|
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();
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
|
|
if (data.success) {
|
|
|
|
renderHistoryList(data.data);
|
|
|
|
renderHistoryList(data.data);
|
|
|
|
@ -537,13 +537,18 @@ async function loadAIAnalysis() {
|
|
|
|
const content = document.getElementById('ai-analysis-content');
|
|
|
|
const content = document.getElementById('ai-analysis-content');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
console.log(`加载合约 ${currentSymbol} 的AI分析...`);
|
|
|
|
const response = await fetch(`${API_BASE}/ai-analysis/${currentSymbol}`);
|
|
|
|
const response = await fetch(`${API_BASE}/ai-analysis/${currentSymbol}`);
|
|
|
|
const data = await response.json();
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`合约 ${currentSymbol} AI分析响应:`, data);
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success && data.data) {
|
|
|
|
if (data.success && data.data) {
|
|
|
|
|
|
|
|
console.log(`合约 ${currentSymbol} 分析数据 - symbol:`, data.data.symbol);
|
|
|
|
currentAIAnalysis = data.data;
|
|
|
|
currentAIAnalysis = data.data;
|
|
|
|
displayAIAnalysisResult(data.data);
|
|
|
|
displayAIAnalysisResult(data.data);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.log(`合约 ${currentSymbol} 无分析结果`);
|
|
|
|
content.innerHTML = `
|
|
|
|
content.innerHTML = `
|
|
|
|
<div class="ai-analysis-placeholder">
|
|
|
|
<div class="ai-analysis-placeholder">
|
|
|
|
<i class="fas fa-brain"></i>
|
|
|
|
<i class="fas fa-brain"></i>
|
|
|
|
@ -552,7 +557,7 @@ async function loadAIAnalysis() {
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
console.error('加载AI分析失败:', error);
|
|
|
|
console.error(`加载合约 ${currentSymbol} AI分析失败:`, error);
|
|
|
|
content.innerHTML = `
|
|
|
|
content.innerHTML = `
|
|
|
|
<div class="ai-analysis-placeholder">
|
|
|
|
<div class="ai-analysis-placeholder">
|
|
|
|
<i class="fas fa-brain"></i>
|
|
|
|
<i class="fas fa-brain"></i>
|
|
|
|
@ -569,28 +574,34 @@ function renderHistoryList(records) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = records.map(record => `
|
|
|
|
console.log('渲染历史记录,记录数量:', records.length);
|
|
|
|
<div class="history-item" onclick="showHistoryModal(${JSON.stringify(record).replace(/"/g, '"')})">
|
|
|
|
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 `
|
|
|
|
|
|
|
|
<div class="history-item" onclick="showAIHistoryDetail(${record.id})">
|
|
|
|
<div class="history-item-left">
|
|
|
|
<div class="history-item-left">
|
|
|
|
<span class="history-time">${record.analysis_time ? record.analysis_time.replace('T', ' ').substring(0, 16) : '--'}</span>
|
|
|
|
<span class="history-time">${timeStr}</span>
|
|
|
|
<span class="history-suggestion ${record.suggestion_type || 'neutral'}">${record.suggestion || '--'}</span>
|
|
|
|
<span class="history-suggestion">${summary.substring(0, 30)}${summary.length > 30 ? '...' : ''}</span>
|
|
|
|
<span class="history-score">评分: ${record.trend_score || '--'}</span>
|
|
|
|
<span class="history-score">方向: ${direction} | 置信度: ${confidence}%</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="history-item-right">
|
|
|
|
<div class="history-item-right">
|
|
|
|
<div class="history-metric">
|
|
|
|
<button class="history-detail-btn" onclick="event.stopPropagation(); showAIHistoryDetail(${record.id})">
|
|
|
|
<span class="history-metric-label">MACD</span>
|
|
|
|
|
|
|
|
<span class="history-metric-value">${record.macd_signal || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="history-metric">
|
|
|
|
|
|
|
|
<span class="history-metric-label">RSI</span>
|
|
|
|
|
|
|
|
<span class="history-metric-value">${record.rsi_value || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<button class="history-detail-btn" onclick="event.stopPropagation(); showHistoryModal(${JSON.stringify(record).replace(/"/g, '"')})">
|
|
|
|
|
|
|
|
<i class="fas fa-chevron-right"></i>
|
|
|
|
<i class="fas fa-chevron-right"></i>
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`).join('');
|
|
|
|
`;
|
|
|
|
|
|
|
|
}).join('');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showSuggestionModal(data) {
|
|
|
|
function showSuggestionModal(data) {
|
|
|
|
@ -717,10 +728,13 @@ function showHistoryModal(record) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadKlineData(symbol, period) {
|
|
|
|
async function loadKlineData(symbol, period) {
|
|
|
|
|
|
|
|
if (!symbol) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const response = await fetch(`${API_BASE}/kline/${symbol}?period=${period}`);
|
|
|
|
const response = await fetch(`${API_BASE}/kline/${symbol}?period=${period}`);
|
|
|
|
const data = await response.json();
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success && data.data) {
|
|
|
|
renderKlineChart(data.data);
|
|
|
|
renderKlineChart(data.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} 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 = `
|
|
|
|
|
|
|
|
<div class="ai-history-detail">
|
|
|
|
|
|
|
|
<div class="detail-header">
|
|
|
|
|
|
|
|
<h4><i class="fas fa-file-alt"></i> ${record.symbol} AI分析报告</h4>
|
|
|
|
|
|
|
|
<span class="detail-time"><i class="fas fa-clock"></i> ${timestamp}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="detail-summary">
|
|
|
|
|
|
|
|
<i class="fas fa-quote-left"></i>
|
|
|
|
|
|
|
|
<p>${result.summary || '暂无总结'}</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- AI交易建议卡片已隐藏 -->
|
|
|
|
|
|
|
|
<!-- <div class="detail-suggestion">
|
|
|
|
|
|
|
|
<div class="suggestion-card ${directionClass}">
|
|
|
|
|
|
|
|
<i class="fas ${directionIcon}"></i>
|
|
|
|
|
|
|
|
<span class="suggestion-text">${direction}</span>
|
|
|
|
|
|
|
|
<span class="confidence-text">置信度: ${confidence}%</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div> -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
${result.four_dimensional ? `
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
|
|
<h5><i class="fas fa-brain"></i> AI思维分析</h5>
|
|
|
|
|
|
|
|
<table class="four-d-table">
|
|
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
|
|
<th>周期</th>
|
|
|
|
|
|
|
|
<th>MACD趋势</th>
|
|
|
|
|
|
|
|
<th>成交量</th>
|
|
|
|
|
|
|
|
<th>KDJ状态</th>
|
|
|
|
|
|
|
|
<th>结论</th>
|
|
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
|
|
${Object.entries(result.four_dimensional).map(([period, d]) => `
|
|
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
|
|
<td><strong>${period}</strong></td>
|
|
|
|
|
|
|
|
<td>${d.macd?.trend || '--'}</td>
|
|
|
|
|
|
|
|
<td>${d.volume?.status || '--'}</td>
|
|
|
|
|
|
|
|
<td>${d.kdj?.status || '--'}</td>
|
|
|
|
|
|
|
|
<td>${d.conclusion || '--'}</td>
|
|
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
`).join('')}
|
|
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="detail-metrics">
|
|
|
|
|
|
|
|
<div class="metric-card">
|
|
|
|
|
|
|
|
<span class="metric-label">入场区间</span>
|
|
|
|
|
|
|
|
<span class="metric-value">${result.trading_suggestion?.entry_range?.min || '--'}-${result.trading_suggestion?.entry_range?.max || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="metric-card">
|
|
|
|
|
|
|
|
<span class="metric-label">止损位</span>
|
|
|
|
|
|
|
|
<span class="metric-value down">${result.trading_suggestion?.stop_loss || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="metric-card">
|
|
|
|
|
|
|
|
<span class="metric-label">建议仓位</span>
|
|
|
|
|
|
|
|
<span class="metric-value">${result.trading_suggestion?.position_size || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="metric-card">
|
|
|
|
|
|
|
|
<span class="metric-label">纪律评分</span>
|
|
|
|
|
|
|
|
<span class="metric-value">${result.discipline_score?.total || '--'}/${result.discipline_score?.max || '11'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
${result.kdj_diagnosis ? `
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
|
|
<h5><i class="fas fa-stethoscope"></i> KDJ诊断</h5>
|
|
|
|
|
|
|
|
<div class="kdj-diagnosis-grid">
|
|
|
|
|
|
|
|
<div class="kdj-item">
|
|
|
|
|
|
|
|
<span class="kdj-label">当前状态</span>
|
|
|
|
|
|
|
|
<span class="kdj-value">${result.kdj_diagnosis.current_status || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="kdj-item">
|
|
|
|
|
|
|
|
<span class="kdj-label">背离</span>
|
|
|
|
|
|
|
|
<span class="kdj-value">${result.kdj_diagnosis.divergence || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="kdj-item">
|
|
|
|
|
|
|
|
<span class="kdj-label">钝化</span>
|
|
|
|
|
|
|
|
<span class="kdj-value">${result.kdj_diagnosis.paralysis || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="kdj-item" style="grid-column: 1 / -1;">
|
|
|
|
|
|
|
|
<span class="kdj-label">建议</span>
|
|
|
|
|
|
|
|
<span class="kdj-value">${result.kdj_diagnosis.recommendation || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
${result.pivot_points ? `
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
|
|
<h5><i class="fas fa-crosshairs"></i> 关键点位</h5>
|
|
|
|
|
|
|
|
<div class="pivot-points-grid">
|
|
|
|
|
|
|
|
<div class="pivot-item resistance">
|
|
|
|
|
|
|
|
<span>R2</span><strong>${result.pivot_points.r2 || '--'}</strong>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="pivot-item resistance">
|
|
|
|
|
|
|
|
<span>R1</span><strong>${result.pivot_points.r1 || '--'}</strong>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="pivot-item center">
|
|
|
|
|
|
|
|
<span>PP</span><strong>${result.pivot_points.pp || '--'}</strong>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="pivot-item support">
|
|
|
|
|
|
|
|
<span>S1</span><strong>${result.pivot_points.s1 || '--'}</strong>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="pivot-item support">
|
|
|
|
|
|
|
|
<span>S2</span><strong>${result.pivot_points.s2 || '--'}</strong>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
${result.risk_warnings && result.risk_warnings.length > 0 ? `
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
|
|
|
<h5><i class="fas fa-exclamation-triangle"></i> 风险提示</h5>
|
|
|
|
|
|
|
|
<ul class="warning-list">
|
|
|
|
|
|
|
|
${result.risk_warnings.map(w => `<li>${w}</li>`).join('')}
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
` : ''}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 显示弹窗
|
|
|
|
|
|
|
|
document.getElementById('ai-analysis-modal').classList.add('active');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
showToast('error', '加载失败', data.error || '记录不存在');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('加载历史记录详情失败:', error);
|
|
|
|
|
|
|
|
showToast('error', '加载失败', '网络错误,请稍后重试');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderKlineChart(data) {
|
|
|
|
function renderKlineChart(data) {
|
|
|
|
if (klineChart) {
|
|
|
|
if (klineChart) {
|
|
|
|
klineChart.dispose();
|
|
|
|
klineChart.dispose();
|
|
|
|
|