// ==================== 渲染函数 ==================== 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; }