// ==================== 渲染函数 ====================
function renderFullData(apiResponse) {
if (!apiResponse) {
const div = document.createElement('div');
div.className = 'no-data';
div.textContent = '暂无数据,请点击上方按钮获取';
return div;
}
// 使用公共排序函数
const symbols = sortSymbols(Object.keys(apiResponse));
const cardTemplate = document.getElementById('template-symbol-card');
const periodTemplate = document.getElementById('template-period-table');
if (!cardTemplate || !periodTemplate) {
const errorDiv = document.createElement('div');
errorDiv.className = 'no-data';
errorDiv.textContent = '❌ 渲染模板缺失,请检查 HTML 中的 template 元素';
return errorDiv;
}
const fragment = document.createDocumentFragment();
for (let sym of symbols) {
const intervals = apiResponse[sym];
if (!intervals) continue;
const card = cardTemplate.content.cloneNode(true);
card.querySelector('.symbol-name').textContent = sym;
card.querySelector('.symbol-badge').textContent = activeSource === 'mexc' ? 'MEXC REST API' : 'Binance Public API';
const periodsContainer = card.querySelector('.periods-container');
for (let period of ['5m','1h','4h','15m']) {
const kdata = intervals[period];
if (!kdata || kdata.error) {
const errMsg = kdata?.error || '无数据';
const errorDiv = document.createElement('div');
errorDiv.className = 'error-msg';
errorDiv.textContent = `⚠️ ${period} 错误: ${errMsg}`;
periodsContainer.appendChild(errorDiv);
continue;
}
if (!Array.isArray(kdata) || kdata.length===0) {
const emptyDiv = document.createElement('div');
emptyDiv.className = 'error-msg';
emptyDiv.textContent = `📭 ${period} 空数据`;
periodsContainer.appendChild(emptyDiv);
continue;
}
const sorted = [...kdata].reverse();
const periodClone = periodTemplate.content.cloneNode(true);
const tbody = periodClone.querySelector('tbody');
for (let k of sorted) {
if (!Array.isArray(k) || k.length<8) continue;
const row = document.createElement('tr');
row.innerHTML = `
${formatTimestamp(k[0])} |
${formatPrice(k[1])} |
${formatPrice(k[2])} |
${formatPrice(k[3])} |
${formatPrice(k[4])} |
${formatPrice(k[5])} |
${formatTimestamp(k[6])} |
${formatPrice(k[7])} |
`;
tbody.appendChild(row);
}
const footer = periodClone.querySelector('.period-footer');
footer.textContent = `📍 共 ${sorted.length} 条K线 (最新在上) | 周期: ${period} | limit=200`;
periodsContainer.appendChild(periodClone);
}
fragment.appendChild(card);
}
const container = document.createElement('div');
container.appendChild(fragment);
return container;
}
function renderCurrentData(apiResponse) {
if (!apiResponse) {
const div = document.createElement('div');
div.className = 'no-data';
div.textContent = '暂无数据,请先点击获取';
return div;
}
// 使用公共排序函数
const symbols = sortSymbols(Object.keys(apiResponse));
const rowsData = [];
for (let sym of symbols) {
let intervals = apiResponse[sym];
if (!intervals) continue;
const period = '5m';
let kdata = intervals[period];
if (!kdata || kdata.error || !Array.isArray(kdata) || kdata.length===0) continue;
const latest = kdata[kdata.length-1];
if (!latest || latest.length<8) continue;
rowsData.push({
symbol: sym,
openTime: latest[0],
open: latest[1],
high: latest[2],
low: latest[3],
close: latest[4],
volume: latest[5],
closeTime: latest[6],
quoteVolume: latest[7]
});
}
if (rowsData.length === 0) {
const div = document.createElement('div');
div.className = 'no-data';
div.textContent = '没有有效的5m最新数据';
return div;
}
const sourceName = activeSource === 'mexc' ? 'MEXC REST API' : 'Binance Public API';
const template = document.getElementById('template-current-table');
if (!template) {
const errorDiv = document.createElement('div');
errorDiv.className = 'no-data';
errorDiv.textContent = '❌ 当前数据模板缺失';
return errorDiv;
}
const clone = template.content.cloneNode(true);
clone.querySelector('.table-caption').textContent = `${sourceName} · 5m (最新1条)`;
const tbody = clone.querySelector('tbody');
for (let row of rowsData) {
const tr = document.createElement('tr');
tr.innerHTML = `
${formatTimestamp(row.openTime)} |
${row.symbol} |
${formatPrice(row.open)} |
${formatPrice(row.high)} |
${formatPrice(row.low)} |
${formatPrice(row.close)} |
${formatPrice(row.volume)} |
${formatTimestamp(row.closeTime)} |
${formatPrice(row.quoteVolume)} |
`;
tbody.appendChild(tr);
}
const container = document.createElement('div');
container.appendChild(clone);
const copyBtn = container.querySelector('.copy-table-btn');
if (copyBtn) {
// 移除原有内联事件绑定,设置唯一 id
copyBtn.id = 'copyCurrentTableBtn';
}
return container;
}
function renderParameterList(params) {
if (!params) {
const errorDiv = document.createElement('div');
errorDiv.className = 'no-data';
errorDiv.textContent = '❌ 参数数据为空,无法渲染';
return errorDiv;
}
const template = document.getElementById('template-param-container');
if (!template) {
const errorDiv = document.createElement('div');
errorDiv.className = 'no-data';
errorDiv.textContent = '❌ 参数模板 (template-param-container) 不存在,请检查 HTML';
return errorDiv;
}
const clone = template.content.cloneNode(true);
const b1Grid = clone.querySelector('[data-section="B1"] .param-grid');
const b2Grid = clone.querySelector('[data-section="B2"] .param-grid');
const b3Grid = clone.querySelector('[data-section="B3"] .param-grid');
if (!b1Grid || !b2Grid || !b3Grid) {
const errorDiv = document.createElement('div');
errorDiv.className = 'no-data';
errorDiv.textContent = '❌ 参数模板结构错误,缺少 .param-grid 容器';
return errorDiv;
}
b1Grid.innerHTML = '';
b2Grid.innerHTML = '';
b3Grid.innerHTML = '';
function formatValue(key, val) {
if (key === 'TURNOVER_THRESHOLDS') {
let str = '';
for (let [sym, thr] of Object.entries(val)) {
str += `${sym}=${(thr/1e6).toFixed(1)}M `;
}
return str;
}
if (key === 'EMA_PERIODS') return val.join(', ');
if (key === 'CURRENT_COINS' || key === 'BINANCE_COINS' || key === 'MEXC_COINS') {
return val.join(', ');
}
if (typeof val === 'number') {
let unit = '';
if (key.includes('PCT') || (key.includes('THRESHOLD') && val < 10)) unit = ' %';
else if (key.includes('SEC')) unit = ' 秒';
else if (key.includes('MULTIPLIER')) unit = ' 倍';
return val + unit;
}
return val;
}
const macroB1Keys = ['MACRO_ADX_THRESHOLD', 'MACRO_ADX_LEN'];
const allB1Keys = [
'DROP_THRESHOLD_PCT', 'MACRO_ADX_THRESHOLD', 'MACRO_ADX_LEN',
'L1_ADX_THRESHOLD', 'L1_ADX_LEN', 'RSI_PRELIM_THRESHOLD', 'RSI_PRELIM_LEN',
'RSI_L2_THRESHOLD', 'RSI_L2_LEN', 'BIG_GREEN_MULTIPLIER', 'BIG_GREEN_COOLDOWN_SEC',
'FLASH_MOVE_THRESHOLD_PCT', 'MACD_FAST', 'MACD_SLOW', 'MACD_SIGNAL',
'ATR_LEN', 'ATR_SMA_LEN', 'RECENT_LOW_PERIOD', 'PIVOT_LEN', 'TURNOVER_THRESHOLDS'
];
const macroDiv = document.createElement('div');
macroDiv.innerHTML = '[MACRO] 宏观阈值
';
const currentDiv = document.createElement('div');
currentDiv.innerHTML = '[CURRENT] 交易币种阈值
';
b1Grid.appendChild(macroDiv);
b1Grid.appendChild(currentDiv);
for (let key of allB1Keys) {
if (params[key] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `${key}: ${formatValue(key, params[key])}`;
if (macroB1Keys.includes(key)) macroDiv.appendChild(itemDiv);
else currentDiv.appendChild(itemDiv);
}
}
const macroDiv2 = document.createElement('div');
macroDiv2.innerHTML = '[MACRO] 宏观共用参数
';
const currentDiv2 = document.createElement('div');
currentDiv2.innerHTML = '[CURRENT] 交易币种列表
';
b2Grid.appendChild(macroDiv2);
b2Grid.appendChild(currentDiv2);
if (params['MACRO_BASE'] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `MACRO_BASE: ${params['MACRO_BASE']}`;
macroDiv2.appendChild(itemDiv);
}
if (params['EMA_PERIODS'] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `EMA_PERIODS: ${params['EMA_PERIODS'].join(', ')}`;
macroDiv2.appendChild(itemDiv);
}
if (params['MEXC_COINS'] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `MEXC_COINS (4币种): ${params['MEXC_COINS'].join(', ')}`;
currentDiv2.appendChild(itemDiv);
}
if (params['BINANCE_COINS'] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `BINANCE_COINS (7币种): ${params['BINANCE_COINS'].join(', ')}`;
currentDiv2.appendChild(itemDiv);
}
if (params['CURRENT_COINS'] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `CURRENT_COINS: ${params['CURRENT_COINS'].join(', ')}`;
currentDiv2.appendChild(itemDiv);
}
const scoringKeys = ['L1_PASS_SCORE', 'L2_PASS_SCORE', 'L1_MAX_SCORE', 'L2_MAX_SCORE'];
const scoringDiv = document.createElement('div');
scoringDiv.innerHTML = '[CURRENT] 评分阈值
';
b3Grid.appendChild(scoringDiv);
for (let key of scoringKeys) {
if (params[key] !== undefined) {
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `${key}: ${params[key]}`;
scoringDiv.appendChild(itemDiv);
}
}
return clone;
}
// ==================== 动态指标卡片渲染 ====================
function renderDynamicIndicatorCard(symbol, data) {
if (!data || data.error) {
const errDiv = document.createElement('div');
errDiv.className = 'no-data';
errDiv.style.marginBottom = '20px';
errDiv.textContent = `⚠️ ${symbol} 指标加载失败: ${data?.error || '未知错误'}`;
return errDiv;
}
const container = document.createElement('div');
container.style.cssText = 'margin-bottom: 30px; background: #1a1a2a; border-radius: 20px; border-left: 4px solid #ffaa66; padding: 15px 20px;';
const title = document.createElement('h3');
title.style.cssText = 'color: #ffaa66; margin-top: 0; margin-bottom: 15px;';
title.textContent = `📈 ${symbol} 实时指标 (基于最新K线)`;
container.appendChild(title);
const grid = document.createElement('div');
grid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 8px;';
function addItem(key, label, unit, isBool) {
let val = data[key];
if (val === undefined || val === null) return;
let display;
if (isBool) {
display = val ? '是' : '否';
} else if (typeof val === 'number') {
display = val.toFixed(2);
if (unit) display += unit;
} else {
display = val;
}
const div = document.createElement('div');
div.innerHTML = `${label}: ${display}`;
grid.appendChild(div);
}
for (let item of MACRO_ITEMS) addItem(item[0], item[1], item[2], false);
for (let item of CURRENT_ITEMS) addItem(item[0], item[1], item[2], item[3] === 'bool');
container.appendChild(grid);
return container;
}
function renderAllDynamicIndicators(dynamicDataMap) {
const container = document.createElement('div');
for (let sym of Object.keys(dynamicDataMap)) {
const data = dynamicDataMap[sym];
if (!data) continue;
const card = renderDynamicIndicatorCard(sym, data);
container.appendChild(card);
}
if (container.children.length === 0) {
const div = document.createElement('div');
div.className = 'no-data';
div.textContent = '⚠️ 动态指标:基于已获取的K线数据,请先点击MEXC或Binance按钮获取K线数据';
return div;
}
return container;
}