// ==================== 主渲染函数 ====================
function renderResultBoard(reportData, viewMode = 'B', params = {}) {
console.log('[最终版] 开始渲染');
// 强制依赖 params 对象,不再提供后备默认值
if (!params || typeof params !== 'object') {
var div = document.createElement('div');
div.className = 'no-data error-message';
div.textContent = '❌ 参数缺失,无法渲染看板。请刷新页面或重新获取数据。';
return div;
}
// 从 params 动态读取阈值(提供后备默认值,与配置文件一致)
var MACRO_ADX_THRESHOLD = params.MACRO_ADX_THRESHOLD;
var DROP_THRESHOLD_PCT = params.DROP_THRESHOLD_PCT;
var L1_ADX_THRESHOLD = params.L1_ADX_THRESHOLD;
var RSI_L2_THRESHOLD = params.RSI_L2_THRESHOLD;
var L1_PASS_SCORE = params.L1_PASS_SCORE;
var L2_PASS_SCORE = params.L2_PASS_SCORE;
var RSI_PRELIM_THRESHOLD = params.RSI_PRELIM_THRESHOLD;
// 可选检查:如果任意必要参数缺失,返回错误
if (MACRO_ADX_THRESHOLD === undefined || DROP_THRESHOLD_PCT === undefined ||
L1_ADX_THRESHOLD === undefined || RSI_L2_THRESHOLD === undefined ||
L1_PASS_SCORE === undefined || L2_PASS_SCORE === undefined ||
RSI_PRELIM_THRESHOLD === undefined) {
var div = document.createElement('div');
div.className = 'no-data error-message';
div.textContent = '❌ 参数不完整,无法渲染看板。请检查参数加载是否成功。';
return div;
}
if (!reportData || reportData.error || !reportData.coins || Object.keys(reportData.coins).length === 0) {
var div = document.createElement('div');
div.className = 'no-data';
div.textContent = '【结果看板】暂无有效数据,请先获取K线数据并确保后端评分计算正常。';
return div;
}
// 辅助函数已由 08.05a 提供(在全局作用域中)
// 此处直接使用 safeNum, icon, formatCooldown, getSubColorClass, getGroupColorClass,
// createSpacer, formatValueHtml, addStageAndGroupRow, addRowOnlySubValue, addSingleRow
function getTotalScore(coin) {
var summary = coin.summary || {};
var bl = summary.bl_score ?? 0;
var r = summary.r_score ?? 0;
var l1 = summary.l1_score ?? 0;
var l2 = summary.l2_score ?? 0;
return bl * (r + l1 + l2);
}
function buildCoinTable(coin, displaySymbol, globalRC, macroGo, macroAdx) {
if (!coin) return null;
var rc = globalRC;
var rcGo = rc.go;
var groupResultText = rcGo ? 'GO ✅' : 'NOTGO ❌';
var tableWrapper = document.createElement('div');
tableWrapper.className = 'board-wrapper';
var caption = document.createElement('div');
caption.className = 'board-caption';
var totalScore = getTotalScore(coin);
if (totalScore !== 0) {
caption.innerHTML = '📊 ' + displaySymbol + ' 评分详情 ' + totalScore + '';
} else {
caption.textContent = '📊 ' + displaySymbol + ' 评分详情';
}
var table = document.createElement('table');
table.className = 'result-board-table';
var thead = document.createElement('thead');
var secondHeader = (viewMode === 'A') ? '组结果' : '组';
var theadHtml = '
| 阶段 | ' + secondHeader + ' | 子结果 | 数值/条件 |
';
thead.innerHTML = theadHtml;
table.appendChild(thead);
var tbody = document.createElement('tbody');
// 宏观风控
var macroStageHtml = '宏观风控 BTC
2pm-2:30pm
需满足全部条件';
var bl01Sub = 'BL01 ' + (rc.bl01 ? '❌' : '✅');
var bl01Val = '禁止:4H价格 > EMA200
必需:4H价格 ≤ EMA200
当前:4H价格 ' + safeNum(rc.current?.price_4h) + ' | EMA200 ' + safeNum(rc.current?.ema200_4h);
addStageAndGroupRow(tbody, macroStageHtml, groupResultText, bl01Sub, bl01Val, 3, 3, 'macro', rcGo);
var bl03Sub = 'BL03 ' + (rc.bl03 ? '❌' : '✅');
var bl03Val = '禁止:1H MACD 0轴上死叉
必需:MACD ≤0 且 无死叉
当前:MACD ' + safeNum(rc.current?.macd_line_1h) + ' | Signal ' + safeNum(rc.current?.macd_signal_1h);
addRowOnlySubValue(tbody, bl03Sub, bl03Val, 'macro');
var bl02Sub = 'BL02 ' + (rc.bl02 ? '❌' : '✅');
var bl02Val = '禁止:出现 15m HL(Higher Low)
当前:HL ' + (rc.bl02 ? '已形成' : '未形成');
addRowOnlySubValue(tbody, bl02Sub, bl02Val, 'macro');
tbody.appendChild(createSpacer());
// 限时否决
var bigGreenNow = coin.cooldown?.big_green_now || false;
var remainingSec = coin.cooldown?.remaining_sec || 0;
var isCooldownPass = (!bigGreenNow && remainingSec <= 0);
var cooldownGroupResult = isCooldownPass ? 'GO ✅' : 'NOTGO ❌';
var cooldownStageHtml = '限时否决
(30分钟)';
var bl05Sub = 'BL05 ' + (isCooldownPass ? '✅' : '❌');
var statusText = bigGreenNow ? '大阳线形成' : '大阳线未形成';
var cooldownDisplay = formatCooldown(remainingSec);
var bl05Val = '禁止:出现一根大阳线
当前:' + statusText + '【禁空 ' + cooldownDisplay + '】';
addSingleRow(tbody, cooldownStageHtml, cooldownGroupResult, bl05Sub, bl05Val, 'cooldown', isCooldownPass);
tbody.appendChild(createSpacer());
// 宏观定调(使用动态阈值 MACRO_ADX_THRESHOLD)
var macroCallStageHtml = '宏观定调 BTC
2pm-2:30pm';
var adxSub = 'ADX ' + (macroGo ? '✅' : '❌');
var adxVal = '必需:ADX > ' + MACRO_ADX_THRESHOLD + '
当前:' + safeNum(macroAdx);
addSingleRow(tbody, macroCallStageHtml, macroGo ? 'GO ✅' : 'NOTGO ❌', adxSub, adxVal, 'call', macroGo);
tbody.appendChild(createSpacer());
// 初步筛选(使用动态阈值 DROP_THRESHOLD_PCT)
var prelim = coin.prelim || {};
var prelimGo = prelim.go || false;
var prelimStageHtml = '初步筛选
3:30pm-4:30pm
需满足全部条件';
var dropSub = '跌幅 ' + (prelim.drop_ok ? '✅' : '❌');
var dropVal = '必需:跌 > ' + DROP_THRESHOLD_PCT + '%
当前:' + safeNum(prelim.drop_val) + '%';
addStageAndGroupRow(tbody, prelimStageHtml, prelimGo ? 'GO ✅' : 'NOTGO ❌', dropSub, dropVal, 3, 3, 'prelim', prelimGo);
var turnoverSub = '成交额 ' + (prelim.turnover_ok ? '✅' : '❌');
var turnoverVal = '必需:成交额 > 阈值M USDT
当前:' + safeNum(prelim.turnover_val) + 'M';
addRowOnlySubValue(tbody, turnoverSub, turnoverVal, 'prelim');
var rsiSub = 'RSI ' + (prelim.rsi_ok ? '✅' : '❌');
var rsiVal = '必需:RSI < ' + RSI_PRELIM_THRESHOLD + '
当前:' + safeNum(prelim.rsi_val);
addRowOnlySubValue(tbody, rsiSub, rsiVal, 'prelim');
tbody.appendChild(createSpacer());
// L1 趋势层(使用动态 L1_PASS_SCORE 和 L1_ADX_THRESHOLD)
var l1 = coin.l1 || {};
var l1d = l1.details || {};
if (Object.keys(l1d).length > 0) {
var l1Current = l1.total ?? 0;
var l1Pass = L1_PASS_SCORE;
var l1Max = l1.max_score ?? 37;
var l1StageHtml = 'L1 趋势层指标
当前:' + l1Current + '
合格:' + l1Pass + '
总分:' + l1Max + '';
var groupHtml = (l1.go ? 'GO ✅' : 'NOTGO ❌') + '
(' + l1Current + '/' + l1Max + ')';
var mtfSub = icon(l1d.mtf_bool) + ' ' + (l1d.mtf ?? 0);
var mtfVal = '必需:4H空头 且 1H空头
当前:4H多头,1H多头';
addStageAndGroupRow(tbody, l1StageHtml, groupHtml, mtfSub, mtfVal, 6, 6, 'l1', l1.go);
var alignSub = icon(l1d.alignment_bool) + ' ' + (l1d.alignment ?? 0);
var alignVal = '必需:EMA20 < EMA50 < EMA100(空头排列)
当前:' + (l1d.alignment_bool ? '空头排列' : '多头排列');
addRowOnlySubValue(tbody, alignSub, alignVal, 'l1');
var slopeSub = icon(l1d.slope_bool) + ' ' + (l1d.slope ?? 0);
var slopeVal = '必需:三条均线均向下
当前:' + (l1d.slope_bool ? '三线皆下行' : '不满足');
addRowOnlySubValue(tbody, slopeSub, slopeVal, 'l1');
var priceSub = icon(l1d.price_pos_bool) + ' ' + (l1d.price_pos ?? 0);
var priceVal = '必需:收盘价 < 所有均线
当前:' + (l1d.price_pos_bool ? '价格在所有EMA下方' : '价格在所有EMA上方');
addRowOnlySubValue(tbody, priceSub, priceVal, 'l1');
var adxSubL1 = icon(l1d.adx_bool) + ' ' + (l1d.adx ?? 0);
var adxValL1 = '必需:ADX > ' + L1_ADX_THRESHOLD + ' 且 上升 且 价格下跌
当前:ADX ' + safeNum(l1d.adx_val) + ',' + (l1d.adx_rising ? '上升' : '下降') + ',' + (l1d.price_dropping ? '价格下跌' : '价格上涨');
addRowOnlySubValue(tbody, adxSubL1, adxValL1, 'l1');
var volSub = icon(l1d.volume_bool) + ' ' + (l1d.volume ?? 0);
var volVal = '必需:放量下跌(量 > SMA20 且 阴线)
当前:量 ' + safeNum(l1d.volume_val) + ' | SMA20 ' + safeNum(l1d.vol_sma_val) + ' | ' + (l1d.volume_bool ? '阴线' : '阳线');
addRowOnlySubValue(tbody, volSub, volVal, 'l1');
tbody.appendChild(createSpacer());
}
// L2 执行层(使用动态 L2_PASS_SCORE 和 RSI_L2_THRESHOLD)
var l2 = coin.l2 || {};
var l2d = l2.details || {};
if (Object.keys(l2d).length > 0) {
var l2Current = l2.total ?? 0;
var l2Pass = L2_PASS_SCORE;
var l2Max = l2.max_score ?? 25;
var l2StageHtml = 'L2 执行层
当前:' + l2Current + '
合格:' + l2Pass + '
总分:' + l2Max + '';
var groupHtml = (l2.go ? 'GO ✅' : 'NOTGO ❌') + '
(' + l2Current + '/' + l2Max + ')';
var mfSub = icon(l2d.mf) + ' ' + (l2d.mf ? 5 : 0);
var mfVal = '必需:RSI < ' + RSI_L2_THRESHOLD + ' 且 MACD柱为负且扩大
当前:RSI ' + safeNum(l2d.rsi_val) + ' | MACD柱 ' + safeNum(l2d.macd_hist_val);
addStageAndGroupRow(tbody, l2StageHtml, groupHtml, mfSub, mfVal, 4, 4, 'l2', l2.go);
var a3Sub = icon(l2d.a3) + ' ' + (l2d.a3 ? 5 : 0);
var a3Val = '必需:跌破近期低点 + 弱回抽(RSI 52-55,缩量)
当前:跌破=' + (l2d.a3 ? '满足' : '不满足') + ' | RSI ' + safeNum(l2d.rsi_val) + ' | 量