// ==================== 复制相关函数 ==================== function getParametersTextFromData() { if (!cachedParams) return '【参数清单】未加载'; let text = ''; text += '阈值区清单 (B1)\n'; const thresholdKeys = [ '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' ]; for (let key of thresholdKeys) { if (cachedParams[key] !== undefined) { let val = cachedParams[key]; let display = val; if (key === 'TURNOVER_THRESHOLDS') { let str = ''; for (let [sym, thr] of Object.entries(val)) { str += `${sym}=${(thr/1e6).toFixed(1)}M `; } display = str; } else 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 = ' 倍'; display = val + unit; } text += `${key}: ${display}\n`; } } text += '\n共用参数清单 (B2)\n'; const commonKeys = ['EMA_PERIODS', 'MACRO_BASE', 'CURRENT_COINS']; for (let key of commonKeys) { if (cachedParams[key] !== undefined) { let val = cachedParams[key]; if (key === 'CURRENT_COINS') { text += `CURRENT_COINS: \n`; for (let coin of val) { text += `${coin}\n`; } } else if (key === 'EMA_PERIODS') { text += `EMA_PERIODS: ${val.join(', ')}\n`; } else { text += `${key}: ${val}\n`; } } } text += '\n评分区逻辑清单 (B3)\n'; if (cachedParams['L1_PASS_SCORE'] !== undefined) text += `L1_PASS_SCORE: ${cachedParams['L1_PASS_SCORE']}\n`; if (cachedParams['L2_PASS_SCORE'] !== undefined) text += `L2_PASS_SCORE: ${cachedParams['L2_PASS_SCORE']}\n`; if (cachedParams['L1_MAX_SCORE'] !== undefined) text += `L1_MAX_SCORE: ${cachedParams['L1_MAX_SCORE']}\n`; if (cachedParams['L2_MAX_SCORE'] !== undefined) text += `L2_MAX_SCORE: ${cachedParams['L2_MAX_SCORE']}\n`; text += '\n\n\n'; if (cachedDynamicMap && Object.keys(cachedDynamicMap).length > 0) { text += '📈 实时指标 (基于最新K线)\n'; for (let symbol of Object.keys(cachedDynamicMap)) { const dynamic = cachedDynamicMap[symbol]; if (!dynamic || dynamic.error) { text += `\n【${symbol}】指标加载失败: ${dynamic?.error || '未知错误'}\n`; continue; } text += `\n【${symbol}】\n`; for (let item of MACRO_ITEMS) { let val = dynamic[item[0]]; if (val !== undefined && val !== null) { let display = (typeof val === 'number') ? val.toFixed(4) : val; if (item[0] === 'macro_drop_4h_pct' && typeof val === 'number') display += '%'; text += `${item[1]}: ${display}\n`; } } for (let item of CURRENT_ITEMS) { let val = dynamic[item[0]]; if (val !== undefined && val !== null) { let display = (typeof val === 'number') ? val.toFixed(4) : (typeof val === 'boolean' ? (val ? '是' : '否') : val); if (item[0] === 'current_drop_4h_pct' && typeof val === 'number') display += '%'; text += `${item[1]}: ${display}\n`; } } } } else { text += '⚠️ 动态指标:基于已获取的K线数据,请先点击MEXC或Binance按钮获取K线数据\n'; } return text.trim(); } // 获取当前数据(5m最新1条)的纯文本(供复制使用) function getCurrentSingle5mTextFromData() { if (!cachedData || !activeSource) return '【当前数据表格】未获取数据'; const symbols = sortSymbols(Object.keys(cachedData)); const rowsData = []; for (let sym of symbols) { let intervals = cachedData[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) return '【当前数据表格】无有效数据'; let text = '开盘时间\t币种\t开盘价\t最高价\t最低价\t收盘价\t成交量\t收盘时间\t成交额\n'; for (let row of rowsData) { text += `${formatTimestamp(row.openTime)}\t${row.symbol}\t${formatPrice(row.open)}\t${formatPrice(row.high)}\t${formatPrice(row.low)}\t${formatPrice(row.close)}\t${formatPrice(row.volume)}\t${formatTimestamp(row.closeTime)}\t${formatPrice(row.quoteVolume)}\n`; } return text.trim(); } // 复制当前表格(5m最新1条) function copyCurrentSingle5m() { const tableText = getCurrentSingle5mTextFromData(); copyToClipboard(tableText, '当前表格已复制到剪贴板'); } // 获取200条数据(全部K线)的纯文本 function get200KLineFromData() { if (!cachedData || !activeSource) return '【200条数据】未获取数据'; const symbols = sortSymbols(Object.keys(cachedData)); let text = ''; for (let sym of symbols) { const intervals = cachedData[sym]; if (!intervals) continue; for (let period of ['5m', '15m', '1h', '4h']) { const kdata = intervals[period]; if (!kdata || kdata.error || !Array.isArray(kdata) || kdata.length===0) continue; // 按时间升序输出(原始顺序为升序) for (let k of kdata) { if (!Array.isArray(k) || k.length<8) continue; text += `${formatTimestamp(k[0])}\t${sym}\t${period}\t${formatPrice(k[1])}\t${formatPrice(k[2])}\t${formatPrice(k[3])}\t${formatPrice(k[4])}\t${formatPrice(k[5])}\t${formatTimestamp(k[6])}\t${formatPrice(k[7])}\n`; } } } if (text === '') return '【200条数据】无有效数据'; return text.trim(); } // 复制200条数据 function copy200KLine() { const fullText = get200KLineFromData(); copyToClipboard(fullText, '200条数据已复制到剪贴板'); } // 一键复制(参数清单 + 当前表格) function copyAllData() { const paramText = getParametersTextFromData(); const tableText = getCurrentSingle5mTextFromData(); const fullText = paramText + '\n\n\n' + tableText; copyToClipboard(fullText, '已复制全部内容(参数 + 当前表格)'); } // ==================== 新增:保存数据到服务器(静默) ==================== async function saveAllDataToServer() { if (!cachedData || !activeSource) { console.warn('[自动保存] 无数据,跳过保存'); return; } const paramText = getParametersTextFromData(); const tableText = getCurrentSingle5mTextFromData(); const fullText = paramText + '\n\n\n' + tableText; try { const response = await fetch('save_export.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: fullText }) }); const result = await response.json(); if (result.status === 'ok') { console.log('[自动保存] 数据已保存到服务器:', result.path); } else { console.warn('[自动保存] 保存失败:', result.message); } } catch (err) { console.error('[自动保存] 请求错误:', err); } }