|
|
|
@ -26,6 +26,8 @@ function setupEventListeners() {
|
|
|
|
document.getElementById('btn-clear').addEventListener('click', function () {
|
|
|
|
document.getElementById('btn-clear').addEventListener('click', function () {
|
|
|
|
handleClear();
|
|
|
|
handleClear();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupSortButtons();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 生成复盘 ====================
|
|
|
|
// ==================== 生成复盘 ====================
|
|
|
|
@ -191,11 +193,18 @@ function renderHero(meta) {
|
|
|
|
const el = document.getElementById('hero-section');
|
|
|
|
const el = document.getElementById('hero-section');
|
|
|
|
if (!el) return;
|
|
|
|
if (!el) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 当数据日期与复盘日期不一致时,显示提示
|
|
|
|
|
|
|
|
let dataDateNotice = '';
|
|
|
|
|
|
|
|
if (meta.actual_data_date && !meta.data_date_matches) {
|
|
|
|
|
|
|
|
dataDateNotice = `<div class="data-date-notice">⚠ 当前页面收盘价、涨跌幅为 <b>${meta.actual_data_date}</b>(最近交易日)数据,非 ${meta.review_date} 当日数据</div>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
el.innerHTML = `
|
|
|
|
el.innerHTML = `
|
|
|
|
<div class="hero-title">
|
|
|
|
<div class="hero-title">
|
|
|
|
<h1>${meta.review_date} <span class="hero-weekday">${meta.week_day || ''}</span></h1>
|
|
|
|
<h1>${meta.review_date} <span class="hero-weekday">${meta.week_day || ''}</span></h1>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="hero-subtitle">${meta.data_basis || ''}</div>
|
|
|
|
<div class="hero-subtitle">${meta.data_basis || ''}</div>
|
|
|
|
|
|
|
|
${dataDateNotice}
|
|
|
|
<div class="hero-conclusion">${meta.core_conclusion || ''}</div>
|
|
|
|
<div class="hero-conclusion">${meta.core_conclusion || ''}</div>
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
showSection('hero-section');
|
|
|
|
showSection('hero-section');
|
|
|
|
@ -264,8 +273,11 @@ function renderGreenSection(plans, scores) {
|
|
|
|
const el = document.getElementById('green-section');
|
|
|
|
const el = document.getElementById('green-section');
|
|
|
|
if (!el) return;
|
|
|
|
if (!el) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const grid = document.getElementById('green-grid');
|
|
|
|
|
|
|
|
if (!grid) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!plans || plans.length === 0) {
|
|
|
|
if (!plans || plans.length === 0) {
|
|
|
|
el.innerHTML = `<div class="section-title green-title">🟢 交易机会(绿色区)</div><div class="empty-hint">暂无交易机会</div>`;
|
|
|
|
grid.innerHTML = `<div class="empty-hint">暂无交易机会</div>`;
|
|
|
|
showSection('green-section');
|
|
|
|
showSection('green-section');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -277,16 +289,17 @@ function renderGreenSection(plans, scores) {
|
|
|
|
const s = scoreMap[plan.symbol] || {};
|
|
|
|
const s = scoreMap[plan.symbol] || {};
|
|
|
|
const dirClass = plan.direction === 'long' ? 'dir-long' : 'dir-short';
|
|
|
|
const dirClass = plan.direction === 'long' ? 'dir-long' : 'dir-short';
|
|
|
|
const dirText = plan.direction === 'long' ? '做多' : '做空';
|
|
|
|
const dirText = plan.direction === 'long' ? '做多' : '做空';
|
|
|
|
|
|
|
|
const dataDateTag = s.data_date ? `<span class="data-date-tag" title="数据日期">${s.data_date}</span>` : '';
|
|
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
return `
|
|
|
|
<div class="opp-card green-card">
|
|
|
|
<div class="opp-card green-card" onclick="showDetail('${plan.symbol}','${(plan.name || '').replace(/'/g, "\\'")}')" style="cursor:pointer">
|
|
|
|
<div class="opp-card-header">
|
|
|
|
<div class="opp-card-header">
|
|
|
|
<a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${plan.symbol}','${plan.name || ''}')">${plan.symbol} ${plan.name || ''}</a>
|
|
|
|
<span class="symbol-link">${plan.symbol} ${plan.name || ''}</span>
|
|
|
|
<span class="score-badge">${plan.composite_score}</span>
|
|
|
|
<span class="score-badge">${plan.composite_score}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="opp-card-rows">
|
|
|
|
<div class="opp-card-rows">
|
|
|
|
<div class="opp-row"><span class="opp-label">方向</span><span class="${dirClass}">${dirText}</span></div>
|
|
|
|
<div class="opp-row"><span class="opp-label">方向</span><span class="${dirClass}">${dirText}</span></div>
|
|
|
|
<div class="opp-row"><span class="opp-label">收盘价</span><span>${formatPrice(s.close_price)}</span></div>
|
|
|
|
<div class="opp-row"><span class="opp-label">收盘价</span><span>${formatPrice(s.close_price)} ${dataDateTag}</span></div>
|
|
|
|
<div class="opp-row"><span class="opp-label">涨跌幅</span>${formatChange(s.change_pct)}</div>
|
|
|
|
<div class="opp-row"><span class="opp-label">涨跌幅</span>${formatChange(s.change_pct)}</div>
|
|
|
|
<div class="opp-row"><span class="opp-label">量比</span><span>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
|
|
|
|
<div class="opp-row"><span class="opp-label">量比</span><span>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -306,142 +319,164 @@ function renderGreenSection(plans, scores) {
|
|
|
|
</div>`;
|
|
|
|
</div>`;
|
|
|
|
}).join('');
|
|
|
|
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
el.innerHTML = `
|
|
|
|
grid.innerHTML = cards;
|
|
|
|
<div class="section-title green-title">🟢 交易机会(绿色区)</div>
|
|
|
|
|
|
|
|
<div class="opp-grid">${cards}</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
showSection('green-section');
|
|
|
|
showSection('green-section');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 黄色关注区 ====================
|
|
|
|
// ==================== 黄色关注区 ====================
|
|
|
|
|
|
|
|
|
|
|
|
function renderYellowSection(scores) {
|
|
|
|
function renderYellowSection(scores) {
|
|
|
|
const el = document.getElementById('yellow-section');
|
|
|
|
const tbody = document.getElementById('yellow-list-body');
|
|
|
|
if (!el) return;
|
|
|
|
if (!tbody) return;
|
|
|
|
|
|
|
|
|
|
|
|
const yellowItems = (scores || []).filter(s => s.category === 'yellow');
|
|
|
|
const yellowItems = (scores || []).filter(s => s.category === 'yellow');
|
|
|
|
|
|
|
|
|
|
|
|
if (yellowItems.length === 0) {
|
|
|
|
if (yellowItems.length === 0) {
|
|
|
|
el.innerHTML = `<div class="section-title yellow-title">🟡 关注列表(黄色区)</div><div class="empty-hint">暂无关注品种</div>`;
|
|
|
|
tbody.innerHTML = `<tr><td colspan="9" class="empty-hint">暂无关注品种</td></tr>`;
|
|
|
|
showSection('yellow-section');
|
|
|
|
showSection('yellow-section');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const items = yellowItems.map(s => `
|
|
|
|
tbody.innerHTML = yellowItems.map((s, i) => `
|
|
|
|
<div class="watch-item yellow-item">
|
|
|
|
<tr onclick="showDetail('${s.symbol}','${(s.name || '').replace(/'/g, "\\'")}')" style="cursor:pointer">
|
|
|
|
<div class="watch-left">
|
|
|
|
<td>${i + 1}</td>
|
|
|
|
<a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${s.symbol}','${s.name || ''}')">${s.symbol} ${s.name || ''}</a>
|
|
|
|
<td><span class="symbol-link">${s.symbol} ${s.name || ''}</span></td>
|
|
|
|
${directionTag(s.direction_tag)}
|
|
|
|
<td>${formatPrice(s.close_price)}</td>
|
|
|
|
</div>
|
|
|
|
<td>${formatChange(s.change_pct)}</td>
|
|
|
|
<div class="watch-mid">
|
|
|
|
<td><div class="table-score-bar-wrap">${scoreBar(s.composite_score)}<span class="table-score-num">${s.composite_score}</span></div></td>
|
|
|
|
${scoreBar(s.composite_score)}
|
|
|
|
<td>${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</td>
|
|
|
|
</div>
|
|
|
|
<td>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</td>
|
|
|
|
<div class="watch-right">
|
|
|
|
<td>${directionTag(s.direction_tag)}</td>
|
|
|
|
<span class="watch-score">${s.composite_score}</span>
|
|
|
|
<td class="data-date-cell">${s.data_date || '-'}</td>
|
|
|
|
${formatChange(s.change_pct)}
|
|
|
|
</tr>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`).join('');
|
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
|
|
|
el.innerHTML = `
|
|
|
|
|
|
|
|
<div class="section-title yellow-title">🟡 关注列表(黄色区)</div>
|
|
|
|
|
|
|
|
<div class="watch-list">${items}</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
showSection('yellow-section');
|
|
|
|
showSection('yellow-section');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 红色风险区 ====================
|
|
|
|
// ==================== 红色风险区 ====================
|
|
|
|
|
|
|
|
|
|
|
|
function renderRedSection(scores) {
|
|
|
|
function renderRedSection(scores) {
|
|
|
|
const el = document.getElementById('red-section');
|
|
|
|
const tbody = document.getElementById('red-list-body');
|
|
|
|
if (!el) return;
|
|
|
|
if (!tbody) return;
|
|
|
|
|
|
|
|
|
|
|
|
const redItems = (scores || []).filter(s => s.category === 'red');
|
|
|
|
const redItems = (scores || []).filter(s => s.category === 'red');
|
|
|
|
|
|
|
|
|
|
|
|
if (redItems.length === 0) {
|
|
|
|
if (redItems.length === 0) {
|
|
|
|
el.innerHTML = `<div class="section-title red-title">🔴 规避列表(红色区)</div><div class="empty-hint">暂无规避品种</div>`;
|
|
|
|
tbody.innerHTML = `<tr><td colspan="9" class="empty-hint">暂无规避品种</td></tr>`;
|
|
|
|
showSection('red-section');
|
|
|
|
showSection('red-section');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const items = redItems.map(s => `
|
|
|
|
tbody.innerHTML = redItems.map((s, i) => `
|
|
|
|
<div class="watch-item red-item">
|
|
|
|
<tr onclick="showDetail('${s.symbol}','${(s.name || '').replace(/'/g, "\\'")}')" style="cursor:pointer">
|
|
|
|
<div class="watch-left">
|
|
|
|
<td>${i + 1}</td>
|
|
|
|
<a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${s.symbol}','${s.name || ''}')">${s.symbol} ${s.name || ''}</a>
|
|
|
|
<td><span class="symbol-link">${s.symbol} ${s.name || ''}</span></td>
|
|
|
|
${directionTag(s.direction_tag)}
|
|
|
|
<td>${formatPrice(s.close_price)}</td>
|
|
|
|
</div>
|
|
|
|
<td>${formatChange(s.change_pct)}</td>
|
|
|
|
<div class="watch-mid">
|
|
|
|
<td><div class="table-score-bar-wrap">${scoreBar(s.composite_score)}<span class="table-score-num">${s.composite_score}</span></div></td>
|
|
|
|
${scoreBar(s.composite_score)}
|
|
|
|
<td>${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</td>
|
|
|
|
</div>
|
|
|
|
<td>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</td>
|
|
|
|
<div class="watch-right">
|
|
|
|
<td>${directionTag(s.direction_tag)}</td>
|
|
|
|
<span class="watch-score">${s.composite_score}</span>
|
|
|
|
<td class="data-date-cell">${s.data_date || '-'}</td>
|
|
|
|
${formatChange(s.change_pct)}
|
|
|
|
</tr>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`).join('');
|
|
|
|
`).join('');
|
|
|
|
|
|
|
|
|
|
|
|
el.innerHTML = `
|
|
|
|
|
|
|
|
<div class="section-title red-title">🔴 规避列表(红色区)</div>
|
|
|
|
|
|
|
|
<div class="watch-list">${items}</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
showSection('red-section');
|
|
|
|
showSection('red-section');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 排名表格 ====================
|
|
|
|
// ==================== 排名表格(支持多列排序) ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let currentSortField = 'composite_score';
|
|
|
|
|
|
|
|
let currentSortOrder = 'desc';
|
|
|
|
|
|
|
|
|
|
|
|
function renderRankingTable(scores) {
|
|
|
|
function renderRankingTable(scores) {
|
|
|
|
const el = document.getElementById('ranking-table');
|
|
|
|
const table = document.getElementById('ranking-table');
|
|
|
|
if (!el) return;
|
|
|
|
if (!table) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const tbody = table.querySelector('tbody');
|
|
|
|
|
|
|
|
if (!tbody) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!scores || scores.length === 0) {
|
|
|
|
if (!scores || scores.length === 0) {
|
|
|
|
el.innerHTML = `<div class="section-title">📊 综合排名</div><div class="empty-hint">暂无排名数据</div>`;
|
|
|
|
tbody.innerHTML = `<tr><td colspan="11" class="empty-hint">暂无排名数据</td></tr>`;
|
|
|
|
showSection('ranking-table');
|
|
|
|
showSection('table-section');
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const rows = scores.map((s, i) => {
|
|
|
|
// 排序
|
|
|
|
const rank = s.rank || (i + 1);
|
|
|
|
const sorted = [...scores].sort((a, b) => {
|
|
|
|
|
|
|
|
let va = a[currentSortField];
|
|
|
|
|
|
|
|
let vb = b[currentSortField];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 方向排序特殊处理
|
|
|
|
|
|
|
|
if (currentSortField === 'direction_tag') {
|
|
|
|
|
|
|
|
const dirOrder = { '强多': 1, '偏多': 2, '多头共振': 1, '震荡': 3, '偏空': 4, '强空': 5, '空头共振': 5 };
|
|
|
|
|
|
|
|
va = dirOrder[va] || 3;
|
|
|
|
|
|
|
|
vb = dirOrder[vb] || 3;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (va == null) va = -Infinity;
|
|
|
|
|
|
|
|
if (vb == null) vb = -Infinity;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (currentSortOrder === 'asc') return va > vb ? 1 : va < vb ? -1 : 0;
|
|
|
|
|
|
|
|
return va < vb ? 1 : va > vb ? -1 : 0;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tbody.innerHTML = sorted.map((s, i) => {
|
|
|
|
|
|
|
|
const rank = i + 1;
|
|
|
|
const rankClass = rank <= 3 ? 'rank-top' : '';
|
|
|
|
const rankClass = rank <= 3 ? 'rank-top' : '';
|
|
|
|
return `
|
|
|
|
return `
|
|
|
|
<tr>
|
|
|
|
<tr onclick="showDetail('${s.symbol}','${(s.name || '').replace(/'/g, "\\'")}')" style="cursor:pointer">
|
|
|
|
<td class="${rankClass}">${rank}</td>
|
|
|
|
<td class="${rankClass}">${rank}</td>
|
|
|
|
<td><a href="javascript:void(0)" class="symbol-link" onclick="showDetail('${s.symbol}','${s.name || ''}')">${s.symbol}</a></td>
|
|
|
|
<td><span class="symbol-link">${s.symbol} ${s.name || ''}</span></td>
|
|
|
|
<td>${s.name || ''}</td>
|
|
|
|
|
|
|
|
<td>${formatPrice(s.close_price)}</td>
|
|
|
|
<td>${formatPrice(s.close_price)}</td>
|
|
|
|
|
|
|
|
<td>${formatChange(s.change_pct)}</td>
|
|
|
|
<td><div class="table-score-bar-wrap">${scoreBar(s.composite_score)}<span class="table-score-num">${s.composite_score}</span></div></td>
|
|
|
|
<td><div class="table-score-bar-wrap">${scoreBar(s.composite_score)}<span class="table-score-num">${s.composite_score}</span></div></td>
|
|
|
|
<td>${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</td>
|
|
|
|
<td>${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</td>
|
|
|
|
<td>${formatChange(s.change_pct)}</td>
|
|
|
|
|
|
|
|
<td>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</td>
|
|
|
|
<td>${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</td>
|
|
|
|
<td>${s.trend_score != null ? s.trend_score : '-'}</td>
|
|
|
|
<td>${s.trend_score != null ? s.trend_score : '-'}</td>
|
|
|
|
<td>${directionTag(s.direction_tag)}</td>
|
|
|
|
<td>${directionTag(s.direction_tag)}</td>
|
|
|
|
<td>${s.activity_score != null ? s.activity_score : '-'}</td>
|
|
|
|
<td>${s.activity_score != null ? s.activity_score : '-'}</td>
|
|
|
|
|
|
|
|
<td class="data-date-cell">${s.data_date || '-'}</td>
|
|
|
|
</tr>`;
|
|
|
|
</tr>`;
|
|
|
|
}).join('');
|
|
|
|
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
el.innerHTML = `
|
|
|
|
showSection('table-section');
|
|
|
|
<div class="section-title">📊 综合排名</div>
|
|
|
|
}
|
|
|
|
<div class="table-wrap">
|
|
|
|
|
|
|
|
<table class="ranking-tbl">
|
|
|
|
function setupSortButtons() {
|
|
|
|
<thead>
|
|
|
|
const controls = document.getElementById('sort-controls');
|
|
|
|
<tr>
|
|
|
|
if (!controls) return;
|
|
|
|
<th>#</th>
|
|
|
|
|
|
|
|
<th>代码</th>
|
|
|
|
controls.addEventListener('click', function (e) {
|
|
|
|
<th>名称</th>
|
|
|
|
const btn = e.target.closest('.sort-btn');
|
|
|
|
<th>收盘价</th>
|
|
|
|
if (!btn) return;
|
|
|
|
<th>综合评分</th>
|
|
|
|
|
|
|
|
<th>振幅%</th>
|
|
|
|
const field = btn.dataset.sort;
|
|
|
|
<th>涨跌幅</th>
|
|
|
|
const defaultOrder = btn.dataset.order || 'desc';
|
|
|
|
<th>量比</th>
|
|
|
|
|
|
|
|
<th>趋势</th>
|
|
|
|
// 切换排序方向
|
|
|
|
<th>方向</th>
|
|
|
|
if (currentSortField === field) {
|
|
|
|
<th>活跃度</th>
|
|
|
|
currentSortOrder = currentSortOrder === 'desc' ? 'asc' : 'desc';
|
|
|
|
</tr>
|
|
|
|
} else {
|
|
|
|
</thead>
|
|
|
|
currentSortField = field;
|
|
|
|
<tbody>${rows}</tbody>
|
|
|
|
currentSortOrder = defaultOrder;
|
|
|
|
</table>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
`;
|
|
|
|
// 更新按钮状态
|
|
|
|
showSection('ranking-table');
|
|
|
|
controls.querySelectorAll('.sort-btn').forEach(b => {
|
|
|
|
|
|
|
|
b.classList.remove('active');
|
|
|
|
|
|
|
|
b.textContent = b.textContent.replace(/ [▲▼]$/, '');
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
btn.classList.add('active');
|
|
|
|
|
|
|
|
const arrow = currentSortOrder === 'desc' ? ' ▼' : ' ▲';
|
|
|
|
|
|
|
|
btn.textContent = btn.textContent + arrow;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染
|
|
|
|
|
|
|
|
if (currentPlanData) {
|
|
|
|
|
|
|
|
renderRankingTable(currentPlanData.scores);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 品种详情卡片 ====================
|
|
|
|
// ==================== 品种详情卡片 ====================
|
|
|
|
@ -471,7 +506,7 @@ function renderDetails(scores) {
|
|
|
|
<span class="detail-score">${s.composite_score}</span>
|
|
|
|
<span class="detail-score">${s.composite_score}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="detail-metrics">
|
|
|
|
<div class="detail-metrics">
|
|
|
|
<div class="detail-metric"><span class="dm-label">收盘价</span><span class="dm-val">${formatPrice(s.close_price)}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">收盘价</span><span class="dm-val">${formatPrice(s.close_price)}${s.data_date ? ' <span class="data-date-tag" title="数据日期">' + s.data_date + '</span>' : ''}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">振幅</span><span class="dm-val">${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">振幅</span><span class="dm-val">${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">涨跌幅</span><span class="dm-val">${formatChange(s.change_pct)}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">涨跌幅</span><span class="dm-val">${formatChange(s.change_pct)}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">量比</span><span class="dm-val">${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
|
|
|
|
<div class="detail-metric"><span class="dm-label">量比</span><span class="dm-val">${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
|
|
|
|
@ -543,20 +578,51 @@ function renderSectors(sectors) {
|
|
|
|
|
|
|
|
|
|
|
|
function showDetail(symbol, name) {
|
|
|
|
function showDetail(symbol, name) {
|
|
|
|
const scores = currentPlanData ? currentPlanData.scores : [];
|
|
|
|
const scores = currentPlanData ? currentPlanData.scores : [];
|
|
|
|
|
|
|
|
const plans = currentPlanData ? currentPlanData.plans : [];
|
|
|
|
const s = scores.find(x => x.symbol === symbol);
|
|
|
|
const s = scores.find(x => x.symbol === symbol);
|
|
|
|
if (!s) return;
|
|
|
|
if (!s) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找对应的交易计划
|
|
|
|
|
|
|
|
const plan = plans.find(p => p.symbol === symbol);
|
|
|
|
|
|
|
|
|
|
|
|
const modal = document.getElementById('detail-modal');
|
|
|
|
const modal = document.getElementById('detail-modal');
|
|
|
|
if (!modal) return;
|
|
|
|
if (!modal) return;
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('modal-title').textContent = `${symbol} ${name || s.name || ''}`;
|
|
|
|
document.getElementById('modal-title').textContent = `${symbol} ${name || s.name || ''}`;
|
|
|
|
|
|
|
|
|
|
|
|
const body = document.getElementById('modal-body');
|
|
|
|
const body = document.getElementById('modal-body');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 交易计划部分(仅绿色品种有)
|
|
|
|
|
|
|
|
let planHtml = '';
|
|
|
|
|
|
|
|
if (plan) {
|
|
|
|
|
|
|
|
const dirClass = plan.direction === 'long' ? 'dir-long' : 'dir-short';
|
|
|
|
|
|
|
|
const dirText = plan.direction === 'long' ? '做多' : '做空';
|
|
|
|
|
|
|
|
planHtml = `
|
|
|
|
|
|
|
|
<div class="modal-section">
|
|
|
|
|
|
|
|
<div class="modal-section-title">交易计划</div>
|
|
|
|
|
|
|
|
<div class="modal-plan-box">
|
|
|
|
|
|
|
|
<div class="modal-plan-row"><span class="mp-label">方向</span><span class="${dirClass} mp-val">${dirText}</span></div>
|
|
|
|
|
|
|
|
<div class="modal-plan-row"><span class="mp-label">入场区间</span><span class="mp-val">${formatPrice(plan.entry_low)} - ${formatPrice(plan.entry_high)}</span></div>
|
|
|
|
|
|
|
|
<div class="modal-plan-row"><span class="mp-label">止损</span><span class="mp-val stop">${formatPrice(plan.stop_loss)}</span></div>
|
|
|
|
|
|
|
|
<div class="modal-plan-row"><span class="mp-label">目标1</span><span class="mp-val target">${formatPrice(plan.target1)}</span></div>
|
|
|
|
|
|
|
|
<div class="modal-plan-row"><span class="mp-label">目标2</span><span class="mp-val target">${formatPrice(plan.target2)}</span></div>
|
|
|
|
|
|
|
|
<div class="modal-plan-row"><span class="mp-label">触发条件</span><span class="mp-val">${plan.trigger || '-'}</span></div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const pivotTags = [];
|
|
|
|
|
|
|
|
if (s.pivot != null) pivotTags.push(`<span class="pivot-tag pivot">Pivot ${formatPrice(s.pivot)}</span>`);
|
|
|
|
|
|
|
|
if (s.r1 != null) pivotTags.push(`<span class="pivot-tag r1">R1 ${formatPrice(s.r1)}</span>`);
|
|
|
|
|
|
|
|
if (s.r2 != null) pivotTags.push(`<span class="pivot-tag r2">R2 ${formatPrice(s.r2)}</span>`);
|
|
|
|
|
|
|
|
if (s.s1 != null) pivotTags.push(`<span class="pivot-tag s1">S1 ${formatPrice(s.s1)}</span>`);
|
|
|
|
|
|
|
|
if (s.s2 != null) pivotTags.push(`<span class="pivot-tag s2">S2 ${formatPrice(s.s2)}</span>`);
|
|
|
|
|
|
|
|
|
|
|
|
body.innerHTML = `
|
|
|
|
body.innerHTML = `
|
|
|
|
<div class="modal-section">
|
|
|
|
<div class="modal-section">
|
|
|
|
<div class="modal-section-title">基本信息</div>
|
|
|
|
<div class="modal-section-title">基本信息</div>
|
|
|
|
<div class="modal-metrics-grid">
|
|
|
|
<div class="modal-metrics-grid">
|
|
|
|
<div class="modal-metric"><span class="mm-label">收盘价</span><span class="mm-val">${formatPrice(s.close_price)}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">收盘价</span><span class="mm-val">${formatPrice(s.close_price)}${s.data_date ? ' <span class="data-date-tag">' + s.data_date + '</span>' : ''}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">振幅</span><span class="mm-val">${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">振幅</span><span class="mm-val">${s.amplitude_pct != null ? s.amplitude_pct.toFixed(2) + '%' : '-'}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">涨跌幅</span><span class="mm-val">${formatChange(s.change_pct)}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">涨跌幅</span><span class="mm-val">${formatChange(s.change_pct)}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">量比</span><span class="mm-val">${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">量比</span><span class="mm-val">${s.volume_ratio != null ? s.volume_ratio.toFixed(2) : '-'}</span></div>
|
|
|
|
@ -564,6 +630,7 @@ function showDetail(symbol, name) {
|
|
|
|
<div class="modal-metric"><span class="mm-label">排名</span><span class="mm-val">#${s.rank || '-'}</span></div>
|
|
|
|
<div class="modal-metric"><span class="mm-label">排名</span><span class="mm-val">#${s.rank || '-'}</span></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
${planHtml}
|
|
|
|
<div class="modal-section">
|
|
|
|
<div class="modal-section">
|
|
|
|
<div class="modal-section-title">评分明细</div>
|
|
|
|
<div class="modal-section-title">评分明细</div>
|
|
|
|
<div class="modal-metrics-grid">
|
|
|
|
<div class="modal-metrics-grid">
|
|
|
|
@ -584,13 +651,7 @@ function showDetail(symbol, name) {
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="modal-section">
|
|
|
|
<div class="modal-section">
|
|
|
|
<div class="modal-section-title">枢轴点位</div>
|
|
|
|
<div class="modal-section-title">枢轴点位</div>
|
|
|
|
<div class="modal-pivots">
|
|
|
|
<div class="modal-pivots">${pivotTags.join('')}</div>
|
|
|
|
${s.r2 != null ? `<span class="pivot-tag r2">R2 ${formatPrice(s.r2)}</span>` : ''}
|
|
|
|
|
|
|
|
${s.r1 != null ? `<span class="pivot-tag r1">R1 ${formatPrice(s.r1)}</span>` : ''}
|
|
|
|
|
|
|
|
${s.pivot != null ? `<span class="pivot-tag pivot">Pivot ${formatPrice(s.pivot)}</span>` : ''}
|
|
|
|
|
|
|
|
${s.s1 != null ? `<span class="pivot-tag s1">S1 ${formatPrice(s.s1)}</span>` : ''}
|
|
|
|
|
|
|
|
${s.s2 != null ? `<span class="pivot-tag s2">S2 ${formatPrice(s.s2)}</span>` : ''}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
@ -657,7 +718,7 @@ function hideAllSections() {
|
|
|
|
const ids = [
|
|
|
|
const ids = [
|
|
|
|
'hero-section', 'stats-grid', 'risk-banner',
|
|
|
|
'hero-section', 'stats-grid', 'risk-banner',
|
|
|
|
'green-section', 'yellow-section', 'red-section',
|
|
|
|
'green-section', 'yellow-section', 'red-section',
|
|
|
|
'ranking-table', 'details-section', 'sectors-section',
|
|
|
|
'table-section', 'details-section', 'sectors-section',
|
|
|
|
'summary-content'
|
|
|
|
'summary-content'
|
|
|
|
];
|
|
|
|
];
|
|
|
|
ids.forEach(id => hideSection(id));
|
|
|
|
ids.forEach(id => hideSection(id));
|
|
|
|
|