// ==================== 结果报告渲染 ==================== function renderResultReport(data, params = null) { if (!data || data.error) { const errDiv = document.createElement('div'); errDiv.className = 'no-data'; errDiv.textContent = '【结果数据】暂无数据,请先点击MEXC或Binance按钮获取K线数据。'; return errDiv; } // 必须提供完整参数对象 if (!params || typeof params !== 'object') { const errDiv = document.createElement('div'); errDiv.className = 'no-data error-message'; errDiv.textContent = '❌ 参数缺失,无法渲染报告。请刷新页面或重新获取数据。'; return errDiv; } // 解构所需阈值(直接使用,缺失时会导致 undefined,后续计算将显示异常) const { DROP_THRESHOLD_PCT, MACRO_ADX_THRESHOLD, L1_ADX_THRESHOLD, RSI_PRELIM_THRESHOLD, RSI_L2_THRESHOLD, L1_PASS_SCORE, L2_PASS_SCORE } = params; // 可选检查:如果任意必要参数缺失,返回错误(不影响已有逻辑) if (DROP_THRESHOLD_PCT === undefined || MACRO_ADX_THRESHOLD === undefined || L1_ADX_THRESHOLD === undefined || RSI_PRELIM_THRESHOLD === undefined || RSI_L2_THRESHOLD === undefined || L1_PASS_SCORE === undefined || L2_PASS_SCORE === undefined) { const errDiv = document.createElement('div'); errDiv.className = 'no-data error-message'; errDiv.textContent = '❌ 参数不完整,无法渲染报告。请检查参数加载是否成功。'; return errDiv; } const container = document.createElement('div'); container.className = 'result-report-container'; const headerBar = document.createElement('div'); headerBar.className = 'report-header-bar'; const copyBtn = document.createElement('button'); copyBtn.textContent = '📋 复制报告'; copyBtn.className = 'api-btn report-copy-btn'; copyBtn.onclick = function() { const textContent = container.querySelector('.result-report').innerText; navigator.clipboard.writeText(textContent).then(() => { const originalText = copyBtn.textContent; copyBtn.textContent = '✅ 已复制!'; setTimeout(() => { copyBtn.textContent = originalText; }, 1500); }).catch(err => { console.error('复制失败:', err); alert('复制失败,请手动复制'); }); }; headerBar.appendChild(copyBtn); container.appendChild(headerBar); const reportDiv = document.createElement('div'); reportDiv.className = 'result-report'; let html = ''; const symbols = sortSymbols(Object.keys(data.coins)); const rc = data.risk_control; const macroGo = data.macro_go; const macroAdx = data.macro_adx; // 全局宏观部分(使用动态阈值) function renderGlobalMacro() { let section = ''; section += "============================================================================\n"; section += "【全局宏观风控 - BTC】\n"; section += "============================================================================\n\n"; section += "阶段\n宏观风控 BTC\n2pm-2:30pm\n需满足全部条件\n\n"; section += "组结果\n" + (rc.go ? "GO ✅" : "NOTGO ❌") + "\nA1\n\n"; section += "子结果\t\t数值/条件\n"; section += `BL01 ${icon(!rc.bl01)}\t\t禁止:4H价格 > EMA200\n\t\t必需:4H价格 ≤ EMA200\n\t\t当前:4H价格 ${safeNum(rc.current?.price_4h)} | EMA200 ${safeNum(rc.current?.ema200_4h)}\n\n`; section += `BL03 ${icon(!rc.bl03)}\t\t禁止:1H MACD 0轴上死叉\n\t\t必需:MACD ≤0 且 无死叉\n\t\t当前:MACD ${safeNum(rc.current?.macd_line_1h)} | Signal ${safeNum(rc.current?.macd_signal_1h)}\n\n`; section += `BL02 ${icon(!rc.bl02)}\t\t禁止:出现 15m HL(Higher Low)\n\t\t当前:HL ${rc.bl02 ? '已形成' : '未形成'}\t\n\n`; section += "--------------------------------------------------------------------\n\n"; section += "阶段\n宏观定调 BTC\n2pm-2:30pm\n\n"; section += "组结果\n" + (macroGo ? "GO ✅" : "NOTGO ❌") + "\nB1\n\n"; section += "子结果\t\t数值/条件\n"; section += `ADX ${icon(macroGo)}\t\t必需:ADX > ${MACRO_ADX_THRESHOLD}\n\t\t当前:${safeNum(macroAdx, 1)}\n\n`; section += "--------------------------------------------------------------------\n\n"; return section; } function safeRender(fn, fallback = '') { try { const result = fn(); return (result !== undefined && result !== null) ? result : fallback; } catch (e) { console.error('渲染错误:', e); return fallback; } } function renderCooldown(symbol, coin) { if (!coin.cooldown) return ''; const cd = coin.cooldown; const remaining = cd.remaining_sec ?? 0; const bigGreenNow = cd.big_green_now ?? false; let section = ''; section += `阶段\n限时否决 ${symbol}\n(30分钟)\t\n\n`; section += "组结果\n禁空时间\t" + formatCooldown(remaining) + "\n\n"; section += "子结果\t\t数值/条件\n"; section += `BL05 ${icon(!bigGreenNow)}\t\t禁止:出现第一根大阳线\n\t\t当前:大阳线${bigGreenNow ? '已形成' : '未形成'}\n\n`; return section; } function renderPrelim(symbol, coin) { if (!coin.prelim) return ''; const p = coin.prelim; // 从 params 中获取成交额阈值(单位:USDT,需转换为 M) const turnoverThresholds = (typeof params !== 'undefined' && params.TURNOVER_THRESHOLDS) ? params.TURNOVER_THRESHOLDS : {}; let turnoverThresholdM = 8.0; // 默认 8M(与配置文件中 SOLUSDT 一致) if (turnoverThresholds[symbol] !== undefined) { turnoverThresholdM = turnoverThresholds[symbol] / 1e6; } let section = ''; section += `阶段\n初步筛选 ${symbol}\n3:30pm-4:30pm\n需满足全部条件\n\n`; section += "组结果\n" + (p.go ? "GO ✅" : "NOTGO ❌") + "\nC1\n\n"; section += "子结果\t\t数值/条件\n"; section += `跌幅 ${icon(p.drop_ok)}\t\t必需:跌 > ${DROP_THRESHOLD_PCT}%\n\t\t当前:跌 ${safeNum(p.drop_val)}%\n`; section += `成交额 ${icon(p.turnover_ok)}\t\t必需:成交额 > ${turnoverThresholdM}M USDT\n\t\t当前:${safeNum(p.turnover_val)}M\t\n`; section += `RSI ${icon(p.rsi_ok)}\t\t必需:RSI < ${RSI_PRELIM_THRESHOLD}\n\t\t当前:${safeNum(p.rsi_val)}\t\n`; return section; } function renderL1(symbol, coin) { if (!coin.l1) return ''; const l1 = coin.l1; const d = l1.details || {}; let section = ''; section += `阶段\nL1 趋势层指标 ${symbol}\n当前:${l1.total}\n合格:${L1_PASS_SCORE}\n总分:${l1.max_score}\n\n`; section += "组结果\n" + (l1.go ? "GO ✅" : "NOTGO ❌") + "\n" + l1.total + "/" + l1.max_score + "\n\n"; section += "子结果\t\t\t数值/条件\n"; section += `${icon(d.mtf_bool)} ${d.mtf}\t\t\tMTF Alignment\n\t\t\t必需:4H空头 且 1H空头\n\t\t\t当前:4H多头,1H多头\n`; section += `${icon(d.alignment_bool)} ${d.alignment}\t\t\t均线排列 \n\t\t\t必需:EMA20 < EMA50 < EMA100(空头排列)\n\t\t\t当前:${d.alignment_bool ? '空头排列' : '多头排列'} ${icon(d.alignment_bool)}\t\n`; section += `${icon(d.slope_bool)} ${d.slope}\t\t\t均线斜率\n\t\t\t必需:三条均线均向下\n\t\t\t当前:${d.slope_bool ? '三线皆下行' : '不满足'} ${icon(d.slope_bool)}\t\n`; section += `${icon(d.price_pos_bool)} ${d.price_pos}\t\t\t价格相对位置\n\t\t\t必需:收盘价 < 所有均线\n\t\t\t当前:${d.price_pos_bool ? '价格在所有EMA下方' : '价格在所有EMA上方'} ${icon(d.price_pos_bool)}\t\n`; section += `${icon(d.adx_bool)} ${d.adx}\t\t\tADX趋势强度\n\t\t\t必需:ADX > ${L1_ADX_THRESHOLD} 且 上升 且 价格下跌\n\t\t\t当前:ADX ${safeNum(d.adx_val)} > ${L1_ADX_THRESHOLD},${d.adx_rising ? '上升' : '下降'},${d.price_dropping ? '价格下跌' : '价格上涨'}\t\n`; section += `${icon(d.volume_bool)} ${d.volume}\t\t\t成交量强度 \n\t\t\t必需:放量下跌(量 > SMA20 且 阴线)\n\t\t\t当前:量 ${safeNum(d.volume_val)} | SMA20 ${safeNum(d.vol_sma_val)} | ${d.volume_bool ? '阴线' : '阳线'}\n`; return section; } function renderL2(symbol, coin) { if (!coin.l2) return ''; const l2 = coin.l2; const d2 = l2.details || {}; let section = ''; section += `阶段\nL2 执行层 ${symbol}\n当前:${l2.total}\n合格:${L2_PASS_SCORE}\n总分:${l2.max_score}\n\n`; section += "组结果\n" + (l2.go ? "GO ✅" : "NOTGO ❌") + "\n" + l2.total + "/" + l2.max_score + "\n\n"; section += "子结果\t\t数值/条件\n"; section += `${icon(d2.mf)} ${d2.mf ? 5 : 0}\t\tMF动能衰退 (LH + MF 同时出现另加5分)\n\t\t必需:RSI < ${RSI_L2_THRESHOLD} 且 MACD柱为负且扩大\n\t\t当前:RSI ${safeNum(d2.rsi_val)} | MACD柱 ${safeNum(d2.macd_hist_val)} ${d2.mf ? '满足' : '不满足'}\n`; section += `${icon(d2.a3)} ${d2.a3 ? 5 : 0}\t\tA3 跌破+弱回抽 \t\n\t\t必需:跌破近期低点 + 弱回抽(RSI 52-55,缩量)\n\t\t当前:跌破=${d2.a3 ? '满足' : '不满足'} | RSI ${safeNum(d2.rsi_val)} | 量 renderL1(symbol, coin), ''); html += "--------------------------------------------------------------------\n\n"; html += "\n============================================================================\n"; html += `${symbol}\n`; html += "============================================================================\n\n"; html += safeRender(() => renderCooldown(symbol, coin), ''); html += "--------------------------------------------------------------------\n\n"; html += safeRender(() => renderPrelim(symbol, coin), ''); html += "--------------------------------------------------------------------\n\n"; html += safeRender(() => renderL1(symbol, coin), ''); html += "--------------------------------------------------------------------\n\n"; html += safeRender(() => renderL2(symbol, coin), ''); html += "--------------------------------------------------------------------\n\n"; html += safeRender(() => renderSummary(symbol, coin), ''); html += "\n"; } reportDiv.innerHTML = html; container.appendChild(reportDiv); return container; }