工作流资金费率套利策略


创建日期: 2026-01-29 10:06:38 最后修改: 2026-02-13 17:54:46
复制: 8 点击次数: 65
avatar of ianzeng123 ianzeng123
2
关注
387
关注者
策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 交易所初始化设置 ==========\\n\\n// 获取交易所列表\\nconst exchangeList = $vars.Exchange;\\nconst EXCHANGE_MAP = {};\\n\\nLog(`🚀 开始初始化交易所...`);\\n\\nif (Array.isArray(exchangeList)) {\\n    exchangeList.forEach((exchange, index) => {\\n        EXCHANGE_MAP[exchange.toLowerCase()] = index;\\n    });\\n} else if (typeof exchangeList === 'string') {\\n    exchangeList.split(',').forEach((exchange, index) => {\\n        const exName = exchange.trim();\\n        EXCHANGE_MAP[exName.toLowerCase()] = index;\\n    });\\n}\\n\\nLog(`📋 交易所映射: ${JSON.stringify(EXCHANGE_MAP)}`);\\nLog(`✅ 交易所初始化完成\\\\n`);\\n\\n// 返回结果\\nreturn { exchangeMap: EXCHANGE_MAP };\",\"notice\":\"\"},\"id\":\"880c2ca1-57ed-4fc7-a917-f347cab528a1\",\"name\":\"交易所设置\",\"position\":[-1120,352],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 根据 $vars.Exchange 动态生成交易所映射\\nconst EXCHANGE_MAP = {};\\nconst exchangeList = $vars.Exchange;\\n\\nif (Array.isArray(exchangeList)) {\\n    exchangeList.forEach((exchange, index) => {\\n        EXCHANGE_MAP[exchange.toLowerCase()] = index;\\n    });\\n} else if (typeof exchangeList === 'string') {\\n    exchangeList.split(',').forEach((exchange, index) => {\\n        const exName = exchange.trim();\\n        EXCHANGE_MAP[exName.toLowerCase()] = index;\\n    });\\n}\\n\\n// 获取置信度筛选配置\\nconst CONFIDENCE_FILTER = $vars.Confidence || ['high', 'medium', 'low'];\\nconst ALLOWED_CONFIDENCE = Array.isArray(CONFIDENCE_FILTER) \\n    ? CONFIDENCE_FILTER.map(c => c.toLowerCase())\\n    : [CONFIDENCE_FILTER.toLowerCase()];\\n\\n// ✅ 修复:正确获取数据格式\\nconst RAW_DATA_OBJ = $node['平仓检测'].json.data || {};\\n\\n// ✅ 修复:将对象格式转换为数组格式\\nconst RAW_DATA = Object.entries(RAW_DATA_OBJ).map(([baseAsset, info]) => ({\\n    symbol: `${baseAsset}-PERP`,\\n    baseAsset: baseAsset,\\n    quoteAsset: 'USD',\\n    spread: info.spread,\\n    arbitrageOpportunity: {\\n        longExchange: info.longExchange,\\n        shortExchange: info.shortExchange,\\n        estimatedApr: info.estimatedApr,\\n        confidence: info.confidence\\n    }\\n}));\\n\\n// 直接筛选出符合置信度的数据\\nconst ARBITRAGE_DATA = RAW_DATA.filter(item => {\\n    if (!item.arbitrageOpportunity || !item.arbitrageOpportunity.confidence) {\\n        return false;\\n    }\\n    return ALLOWED_CONFIDENCE.includes(item.arbitrageOpportunity.confidence.toLowerCase());\\n});\\n\\nLog(`📋 原始数据: ${RAW_DATA.length}个 | 置信度筛选[${ALLOWED_CONFIDENCE.join(',')}]: ${ARBITRAGE_DATA.length}个`);\\n\\nfunction getContractSymbols(baseAsset, exchangeName) {\\n    const exName = exchangeName?.toLowerCase() || '';\\n    \\n    // Hyperliquid 使用 USD 计价\\n    if (exName === 'hyperliquid') {\\n        return [`${baseAsset}_USD.swap`];\\n    }\\n    \\n    // Lighter 使用 USDC 计价\\n    if (exName === 'lighter') {\\n        return [`${baseAsset}_USDC.swap`];\\n    }\\n    \\n    // 其他交易所尝试 USDT 和 USDC\\n    return [`${baseAsset}_USDT.swap`, `${baseAsset}_USDC.swap`];\\n}\\n\\n// 检查指定交易所是否有该币种的仓位\\nfunction checkPositionExists(exIndex, baseAsset) {\\n    try {\\n        const EX = exchanges[exIndex];\\n        if (!EX) return { hasPosition: false, error: '交易所不存在' };\\n        \\n        const positions = EX.GetPositions();\\n        if (positions && Array.isArray(positions)) {\\n            for (const pos of positions) {\\n                if (pos.Amount !== 0) {\\n                    const match = pos.Symbol?.match(/^([A-Z0-9]+)_/);\\n                    if (match && match[1] === baseAsset) {\\n                        return {\\n                            hasPosition: true,\\n                            amount: pos.Amount,\\n                            symbol: pos.Symbol\\n                        };\\n                    }\\n                }\\n            }\\n        }\\n        return { hasPosition: false };\\n    } catch (e) {\\n        return { hasPosition: false, error: e.message };\\n    }\\n}\\n\\n// 检查两个交易所是否已有该币种仓位\\nfunction hasExistingPosition(baseAsset, longExchangeName, shortExchangeName) {\\n    const longExName = longExchangeName.toLowerCase();\\n    const shortExName = shortExchangeName.toLowerCase();\\n    \\n    const longExIndex = EXCHANGE_MAP[longExName];\\n    const shortExIndex = EXCHANGE_MAP[shortExName];\\n    \\n    if (longExIndex === undefined || shortExIndex === undefined) {\\n        return { hasPosition: false, error: '交易所映射错误' };\\n    }\\n    \\n    const longCheck = checkPositionExists(longExIndex, baseAsset);\\n    const shortCheck = checkPositionExists(shortExIndex, baseAsset);\\n    \\n    if (longCheck.hasPosition || shortCheck.hasPosition) {\\n        const details = [];\\n        if (longCheck.hasPosition) {\\n            details.push(`${longExName}有${baseAsset}仓位(${longCheck.amount})`);\\n        }\\n        if (shortCheck.hasPosition) {\\n            details.push(`${shortExName}有${baseAsset}仓位(${shortCheck.amount})`);\\n        }\\n        \\n        return {\\n            hasPosition: true,\\n            longPosition: longCheck,\\n            shortPosition: shortCheck,\\n            details: details.join(', ')\\n        };\\n    }\\n    \\n    return { hasPosition: false };\\n}\\n\\nfunction tryGetData(exIndex, baseAsset, dataType, exchangeName) {\\n    const EX = exchanges[exIndex];\\n    if (!EX) return { data: null, symbol: null, success: false };\\n    \\n    const symbols = getContractSymbols(baseAsset, exchangeName);\\n    \\n    for (const symbol of symbols) {\\n        try {\\n            let data = null;\\n            switch (dataType) {\\n                case 'records':\\n                   \\n                    data = EX.GetRecords(symbol, PERIOD_M1, 240);\\n                    break;\\n                case 'ticker':\\n                    \\n                    data = EX.GetTicker(symbol);\\n                    break;\\n            }\\n            if (data && (Array.isArray(data) ? data.length > 0 : data.Last > 0)) {\\n                return { data, symbol, success: true };\\n            }\\n        } catch (e) {}\\n    }\\n    return { data: null, symbol: null, success: false };\\n}\\n\\nfunction checkPriceSpread(longExIndex, shortExIndex, baseAsset, longExchangeName, shortExchangeName) {\\n    const recordsLong = tryGetData(longExIndex, baseAsset, 'records', longExchangeName);\\n    const recordsShort = tryGetData(shortExIndex, baseAsset, 'records', shortExchangeName);\\n    \\n    if (!recordsLong.success || !recordsShort.success) {\\n        return { \\n            success: false, \\n            error: 'K线数据获取失败', \\n            symbolLong: recordsLong.symbol, \\n            symbolShort: recordsShort.symbol \\n        };\\n    }\\n    \\n    const priceMapLong = {}, priceMapShort = {};\\n    for (const bar of recordsLong.data) priceMapLong[bar.Time] = bar.Close;\\n    for (const bar of recordsShort.data) priceMapShort[bar.Time] = bar.Close;\\n    \\n    const commonTimes = Object.keys(priceMapLong).filter(t => priceMapShort[t] !== undefined).map(t => parseInt(t)).sort((a, b) => a - b);\\n    \\n    if (commonTimes.length < 30) {\\n        return { \\n            success: false, \\n            error: `数据不足:${commonTimes.length}条`, \\n            symbolLong: recordsLong.symbol, \\n            symbolShort: recordsShort.symbol \\n        };\\n    }\\n    \\n    const recentTimes = commonTimes.slice(-240);\\n    const spreads = recentTimes.map(t => {\\n        const midPrice = (priceMapLong[t] + priceMapShort[t]) / 2;\\n        return (priceMapLong[t] - priceMapShort[t]) / midPrice;\\n    });\\n    \\n    const avg = spreads.reduce((a, b) => a + b, 0) / spreads.length;\\n    const max = Math.max(...spreads);\\n    const min = Math.min(...spreads);\\n    const range = max - min;\\n    const stdDev = Math.sqrt(spreads.reduce((sum, s) => sum + Math.pow(s - avg, 2), 0) / spreads.length);\\n    \\n    return {\\n        success: true,\\n        symbolLong: recordsLong.symbol,\\n        symbolShort: recordsShort.symbol,\\n        dataPoints: recentTimes.length,\\n        avgSpread: avg,\\n        maxSpread: max,\\n        minSpread: min,\\n        range: range,\\n        stdDev: stdDev\\n    };\\n}\\n\\nfunction checkLiquidity(longExIndex, shortExIndex, baseAsset, longExchangeName, shortExchangeName) {\\n    const tickerLong = tryGetData(longExIndex, baseAsset, 'ticker', longExchangeName);\\n    const tickerShort = tryGetData(shortExIndex, baseAsset, 'ticker', shortExchangeName);\\n    \\n    if (!tickerLong.success || !tickerShort.success) {\\n        return { \\n            success: false, \\n            error: 'Ticker获取失败', \\n            symbolLong: tickerLong.symbol, \\n            symbolShort: tickerShort.symbol \\n        };\\n    }\\n    \\n    const tL = tickerLong.data, tS = tickerShort.data;\\n    const spreadLong = (tL.Sell - tL.Buy) / tL.Last;\\n    const spreadShort = (tS.Sell - tS.Buy) / tS.Last;\\n    const midPriceLong = (tL.Buy + tL.Sell) / 2;\\n    const midPriceShort = (tS.Buy + tS.Sell) / 2;\\n    const midPrice = (midPriceLong + midPriceShort) / 2;\\n    const realTimePremium = (midPriceLong - midPriceShort) / midPrice;\\n    const openCost = (tL.Sell - tS.Buy) / midPrice;\\n    const closeCost = (tS.Sell - tL.Buy) / midPrice;\\n    const volumeLong = tL.Volume * tL.Last;\\n    const volumeShort = tS.Volume * tS.Last;\\n    \\n    return {\\n        success: true,\\n        symbolLong: tickerLong.symbol,\\n        symbolShort: tickerShort.symbol,\\n        longPrice: tL.Last,\\n        shortPrice: tS.Last,\\n        longSpread: spreadLong,\\n        shortSpread: spreadShort,\\n        longVolume24h: volumeLong,\\n        shortVolume24h: volumeShort,\\n        realTimePremium: realTimePremium,\\n        openCost: openCost,\\n        closeCost: closeCost,\\n        roundTripCost: openCost + closeCost\\n    };\\n}\\n\\nfunction checkSingleAsset(item) {\\n    const { symbol, baseAsset, quoteAsset, spread, arbitrageOpportunity } = item;\\n    const { longExchange, shortExchange, estimatedApr, confidence } = arbitrageOpportunity;\\n    \\n    if (quoteAsset !== 'USD') {\\n        return { \\n            symbol, \\n            baseAsset, \\n            skipped: true,\\n            skipType: 'other',\\n            reason: '非USD计价'\\n        };\\n    }\\n    \\n    const longExName = longExchange.toLowerCase();\\n    const shortExName = shortExchange.toLowerCase();\\n    \\n    // 实时检查是否已有持仓\\n    const positionCheck = hasExistingPosition(baseAsset, longExName, shortExName);\\n    if (positionCheck.hasPosition) {\\n        return {\\n            symbol,\\n            baseAsset,\\n            skipped: true,\\n            skipType: 'position',\\n            reason: positionCheck.details\\n        };\\n    }\\n    \\n    const longExIndex = EXCHANGE_MAP[longExName];\\n    const shortExIndex = EXCHANGE_MAP[shortExName];\\n    \\n    if (longExIndex === undefined || shortExIndex === undefined) {\\n        return { \\n            symbol, \\n            baseAsset, \\n            longExchange, \\n            shortExchange, \\n            error: '交易所映射错误'\\n        };\\n    }\\n    \\n    const priceSpreadResult = checkPriceSpread(longExIndex, shortExIndex, baseAsset, longExName, shortExName);\\n    const liquidityResult = checkLiquidity(longExIndex, shortExIndex, baseAsset, longExName, shortExName);\\n    \\n    return {\\n        symbol,\\n        baseAsset,\\n        longExchange,\\n        shortExchange,\\n        rateSpread: spread,\\n        estimatedApr: estimatedApr,\\n        confidence: confidence,\\n        priceSpread: priceSpreadResult,\\n        liquidity: liquidityResult\\n    };\\n}\\n\\nfunction formatPct(value, decimals = 4) {\\n    return (value * 100).toFixed(decimals) + '%';\\n}\\n\\nfunction formatVolume(value) {\\n    if (value >= 1000000) {\\n        return '$' + (value / 1000000).toFixed(2) + 'M';\\n    } else if (value >= 1000) {\\n        return '$' + (value / 1000).toFixed(1) + 'K';\\n    }\\n    return '$' + value.toFixed(0);\\n}\\n\\nfunction checkAllAssets(data) {\\n    Log(`🚀 资金费率套利检测 | ${new Date().toISOString()}`);\\n    \\n    const usdAssets = data.filter(item => item.quoteAsset === 'USD');\\n    Log(`📋 待检测: ${usdAssets.length}个\\\\n`);\\n    \\n    const validResults = {};\\n    let successCount = 0;\\n    let failCount = 0;\\n    let skippedByPosition = 0;\\n    let skippedOther = 0;\\n    \\n    for (const item of usdAssets) {\\n        try {\\n            const result = checkSingleAsset(item);\\n            \\n            if (result.skipped) {\\n                if (result.skipType === 'position') {\\n                    Log(`🔒 [${result.symbol}] 已有仓位: ${result.reason}\\\\n`);\\n                    skippedByPosition++;\\n                } else {\\n                    Log(`⏭️ [${result.symbol}] 跳过: ${result.reason}\\\\n`);\\n                    skippedOther++;\\n                }\\n                continue;\\n            }\\n            \\n            if (result.error) {\\n                Log(`❌ [${result.symbol}] 错误: ${result.error}\\\\n`);\\n                failCount++;\\n                continue;\\n            }\\n            \\n            Log(`📌 [${result.symbol}] ${result.baseAsset}`);\\n            Log(`   方向: Long@${result.longExchange} | Short@${result.shortExchange}`);\\n            Log(`   费率: 价差${formatPct(result.rateSpread, 2)} | 年化${result.estimatedApr.toFixed(1)}% | ${result.confidence}`);\\n            \\n            const ps = result.priceSpread;\\n            if (ps.success) {\\n                Log(`   价差稳定性: 均值${formatPct(ps.avgSpread)} | 范围${formatPct(ps.range)} | 标准差${formatPct(ps.stdDev)}`);\\n            } else {\\n                Log(`   价差稳定性: ❌ ${ps.error}`);\\n            }\\n            \\n            const lq = result.liquidity;\\n            if (lq.success) {\\n                Log(`   流动性: 开仓${formatPct(lq.openCost)} | 溢价${formatPct(lq.realTimePremium)} | L:${formatVolume(lq.longVolume24h)} S:${formatVolume(lq.shortVolume24h)}`);\\n            } else {\\n                Log(`   流动性: ❌ ${lq.error}`);\\n            }\\n            \\n            validResults[result.symbol] = {\\n                status: 'success',\\n                baseAsset: result.baseAsset,\\n                direction: {\\n                    long: result.longExchange,\\n                    short: result.shortExchange\\n                },\\n                funding: {\\n                    rateSpread: result.rateSpread,\\n                    estimatedApr: result.estimatedApr,\\n                    confidence: result.confidence\\n                },\\n                priceSpread: ps.success ? {\\n                    status: 'success',\\n                    symbolLong: ps.symbolLong,\\n                    symbolShort: ps.symbolShort,\\n                    dataPoints: ps.dataPoints,\\n                    avgSpread: ps.avgSpread,\\n                    maxSpread: ps.maxSpread,\\n                    minSpread: ps.minSpread,\\n                    range: ps.range,\\n                    stdDev: ps.stdDev\\n                } : {\\n                    status: 'failed',\\n                    error: ps.error\\n                },\\n                liquidity: lq.success ? {\\n                    status: 'success',\\n                    symbolLong: lq.symbolLong,\\n                    symbolShort: lq.symbolShort,\\n                    longPrice: lq.longPrice,\\n                    shortPrice: lq.shortPrice,\\n                    longSpread: lq.longSpread,\\n                    shortSpread: lq.shortSpread,\\n                    longVolume24h: lq.longVolume24h,\\n                    shortVolume24h: lq.shortVolume24h,\\n                    realTimePremium: lq.realTimePremium,\\n                    openCost: lq.openCost,\\n                    closeCost: lq.closeCost,\\n                    roundTripCost: lq.roundTripCost\\n                } : {\\n                    status: 'failed',\\n                    error: lq.error\\n                }\\n            };\\n            \\n            successCount++;\\n            \\n        } catch (e) {\\n            Log(`❌ [${item.symbol}] 异常: ${e.message}\\\\n`);\\n            failCount++;\\n        }\\n        Sleep(300);\\n    }\\n    \\n    const totalChecked = successCount + failCount + skippedByPosition + skippedOther;\\n    \\n    Log(`\\\\n📊 检测完成:`);\\n    Log(`   总数: ${totalChecked} | ✅成功: ${successCount} | ❌失败: ${failCount} | 🔒仓位: ${skippedByPosition} | ⏭️跳过: ${skippedOther}`);\\n    \\n    if (successCount === 0) {\\n        Log(`\\\\n⚠️ 无可用套利机会`);\\n    }\\n    \\n    return {\\n        timestamp: new Date().toISOString(),\\n        summary: {\\n            total: totalChecked,\\n            success: successCount,\\n            failed: failCount,\\n            skippedByPosition: skippedByPosition,\\n            skippedOther: skippedOther\\n        },\\n        data: validResults\\n    };\\n}\\n\\nconst results = checkAllAssets(ARBITRAGE_DATA);\\n\\nLog(\\\"\\\\n结果:\\\", results);\\n\\nreturn results;\\n\",\"notice\":\"\"},\"id\":\"fa80b981-a73a-4d40-8092-e2762687489d\",\"name\":\"费率数据验证\",\"position\":[-448,352],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 获取输入数据\\nconst inputData = $input.all();\\nconst data = inputData[0].json;\\n\\n// 检查数据结构是否存在\\nif (!data || !data.filteredMarkets) {\\n  return { data: {} };\\n}\\n\\n// 过滤并整理数据\\nconst result = {};\\n\\ndata.filteredMarkets.forEach(market => {\\n  // 筛选条件:quoteAsset 是 USD 且有 arbitrageOpportunity\\n  if (market.quoteAsset !== 'USD' || !market.arbitrageOpportunity) {\\n    return;\\n  }\\n\\n  const arb = market.arbitrageOpportunity;\\n  const baseAsset = market.baseAsset;\\n\\n  // 找到 long 和 short 交易所对应的 rate\\n  const longExchange = arb.longExchange;\\n  const shortExchange = arb.shortExchange;\\n\\n  // 从 comparisons 中找到对应交易所的 rate\\n  let longRate = null;\\n  let shortRate = null;\\n\\n  if (market.comparisons) {\\n    market.comparisons.forEach(comp => {\\n      if (comp.exchange === longExchange) {\\n        longRate = comp.rate;\\n      }\\n      if (comp.exchange === shortExchange) {\\n        shortRate = comp.rate;\\n      }\\n    });\\n  }\\n\\n  // 构建结果对象\\n  result[baseAsset] = {\\n    longExchange: longExchange,\\n    shortExchange: shortExchange,\\n    longRate: longRate,\\n    shortRate: shortRate,\\n    spread: arb.spread,\\n    estimatedApr: arb.estimatedApr,\\n    confidence: arb.confidence\\n  };\\n});\\n\\nreturn { data: result };\\n\",\"notice\":\"\"},\"id\":\"65c74347-c2c9-4a2c-83be-551a8604db50\",\"name\":\"费率数据筛选\",\"position\":[-672,816],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 将交易所列表转换为逗号分隔的字符串\\nconst exchangeList = $vars.Exchange;\\nconst exchangeString = exchangeList.join(',');\\n\\nconst inputData = $input.all();\\nconst data = inputData[0].json;  // 获取第一个输入项的 json 数据\\n\\n// 过滤出 bestRate 和 worstRate 都在 $vars.Exchange 中的条目\\nconst filteredMarkets = data.data.markets.filter(market => {\\n    const bestExchange = market.bestRate?.exchange;\\n    const worstExchange = market.worstRate?.exchange;\\n    \\n    // 检查两个交易所是否都在 exchangeList 中\\n    return exchangeList.includes(bestExchange) && exchangeList.includes(worstExchange);\\n});\\n\\n// 返回结果\\nreturn {\\n    exchangeString: exchangeString,\\n    totalMarkets: data.data.markets.length,\\n    filteredCount: filteredMarkets.length,\\n    filteredMarkets: filteredMarkets\\n};\\n\",\"notice\":\"\"},\"id\":\"eaeb520b-bded-4236-af26-db76864b886c\",\"name\":\"费率数据请求\",\"position\":[-896,816],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"method\":\"GET\",\"url\":\"=https://varfunding.xyz/api/funding?exchanges={{ $vars.Exchange.join(',') }}\",\"authentication\":\"none\",\"sendQuery\":false,\"sendHeaders\":false,\"sendBody\":false,\"options\":{},\"infoMessage\":\"\"},\"id\":\"fd2b5b79-0dcf-4b1c-851a-c5c0fbbe8753\",\"name\":\"套利机会请求\",\"position\":[-1120,816],\"type\":\"n8n-nodes-base.httpRequest\",\"typeVersion\":4.2},{\"parameters\":{\"text\":\"=## 角色定义\\n你是一个专业的加密货币资金费率套利分析师,需要根据提供的检测数据,判断每个交易对是否适合开仓套利。\\n\\n## 套利原理说明\\n\\n资金费率套利是指:当两个交易所的永续合约资金费率存在差异时,在费率较高的交易所做空(收取费率),在费率较低的交易所做多(支付较少费率),通过费率差获利,同时对冲价格风险。\\n\\n**核心理解**:这是一个对冲策略,两边仓位方向相反,价格涨跌的盈亏会相互抵消。真正的风险不是价格波动本身,而是两个交易所之间的价差变化。\\n\\n## 数据字段详解\\n\\n### 1. 基础信息\\n| 字段 | 说明 |\\n|------|------|\\n| `symbol` | 交易对名称 |\\n| `baseAsset` | 基础资产(如 BTC、ETH) |\\n| `direction.long` | 做多的交易所(费率较低方) |\\n| `direction.short` | 做空的交易所(费率较高方) |\\n\\n### 2. 费率信息 (funding)\\n| 字段 | 说明 |\\n|------|------|\\n| `rateSpread` | 两交易所费率差值(正数=有套利空间) |\\n| `estimatedApr` | 预估年化收益率(%) |\\n| `confidence` | 费率数据置信度 |\\n\\n### 3. 价差稳定性 (priceSpread)\\n用于评估两个交易所之间价格走势的一致性。\\n\\n| 字段 | 说明 |\\n|------|------|\\n| `avgSpread` | 平均价差(正=Long交易所价格更高,负=Long交易所价格更低) |\\n| `range` | 价差波动范围(最大-最小) |\\n| `stdDev` | 价差标准差,反映波动离散程度 |\\n| `dataPoints` | 统计数据点数量 |\\n\\n**重要理解**:\\n- `avgSpread` 为负且 Long 在该交易所 → 开仓时买入价更低,**有利**\\n- `range` 反映的是历史波动,不代表未来一定会亏损\\n- 价差可能收敛(有利)也可能发散(不利),需要综合判断\\n\\n### 4. 流动性检测 (liquidity)\\n| 字段 | 说明 |\\n|------|------|\\n| `longSpread` | Long交易所买卖价差 |\\n| `shortSpread` | Short交易所买卖价差 |\\n| `longVolume24h` | Long交易所24h交易量(USD) |\\n| `shortVolume24h` | Short交易所24h交易量(USD) |\\n| `realTimePremium` | 实时价格溢价(Long所相对Short所) |\\n| `openCost` | 开仓价格偏离成本 |\\n| `closeCost` | 平仓价格偏离成本 |\\n| `roundTripCost` | 往返总成本(开仓+平仓) |\\n\\n## 风险评估原则\\n\\n### ⚠️ 重要:避免硬性指标陷阱\\n\\n不要使用简单的硬性阈值来判断,而是要**综合权衡收益与风险的关系**。\\n\\n❌ 错误思路:`range > 3%` 就直接拒绝\\n✅ 正确思路:`range` 相对于 `rateSpread` 是否可接受\\n\\n### 核心判断逻辑\\n\\n**1. 收益风险比是关键**\\n\\n```\\n如果 rateSpread(每8小时收益)> range × 0.3\\n说明即使价差波动到极端情况,1-2个结算周期就能覆盖风险\\n→ 可以考虑开仓\\n```\\n\\n**2. 价差方向是否有利**\\n\\n```\\n如果 avgSpread < 0 且 Long 在价格低的交易所:\\n- 开仓时:在便宜的地方买入,贵的地方卖出 → 有利\\n- 这种情况下,即使 range 较大,开仓本身就有优势\\n```\\n\\n**3. 成本是否可控**\\n\\n```\\nroundTripCost < rateSpread × 2\\n意味着 2 个结算周期(16小时)就能覆盖交易成本\\n```\\n\\n### 必须谨慎/拒绝的情况\\n\\n以下情况需要谨慎或拒绝,但仍需结合其他因素综合判断:\\n\\n1. **收益太低无法覆盖风险**:`rateSpread < 0.05%` 且 `estimatedApr < 20%`\\n2. **成本吃掉大部分收益**:`roundTripCost > rateSpread × 1.5`\\n3. **流动性严重不足**:任一交易所 `volume24h < 100,000` 或买卖价差 > 0.3%\\n4. **数据不可用**:`priceSpread.status = \\\"failed\\\"` 且 `liquidity.status = \\\"failed\\\"`\\n5. **极端价差波动**:`stdDev > 3%` 且 `rateSpread < stdDev`\\n\\n### 评分指导(满分100分)\\n\\n采用**动态权重**,根据收益水平调整风险容忍度:\\n\\n| 维度 | 基础权重 | 评估要点 |\\n|------|---------|---------|\\n| 费率收益 | 35% | APR 越高,可容忍的风险越大 |\\n| 收益风险比 | 30% | `rateSpread / range` 和 `rateSpread / stdDev` 的比值 |\\n| 流动性 | 20% | 两边 volume 是否足够,买卖价差是否合理 |\\n| 成本效率 | 15% | `roundTripCost / rateSpread` 越小越好 |\\n\\n**动态调整**:\\n- 当 `estimatedApr > 200%` 时,风险指标的扣分减半\\n- 当 `avgSpread` 方向有利时,额外加 5-10 分\\n- 当 `rateSpread > range` 时,价差波动的扣分减半\\n\\n## 输出格式\\n\\n**必须严格按照以下 JSON 数组格式输出,不要输出任何其他内容:**\\n\\n```json\\n[\\n  {\\n    \\\"symbol\\\": \\\"XXX-PERP\\\",\\n    \\\"proceed\\\": true,\\n    \\\"score\\\": 75,\\n    \\\"longExchange\\\": \\\"binance\\\",\\n    \\\"shortExchange\\\": \\\"okx\\\",\\n    \\\"suggestedAmount\\\": 5000,\\n    \\\"rateSpread\\\": \\\"0.85%\\\",\\n    \\\"estimatedDailyProfit\\\": \\\"42.5\\\",\\n    \\\"riskLevel\\\": \\\"medium\\\",\\n    \\\"reason\\\": \\\"费率3.01%远超价差波动风险,开仓方向有利\\\"\\n  },\\n  {\\n    \\\"symbol\\\": \\\"YYY-PERP\\\",\\n    \\\"proceed\\\": false,\\n    \\\"score\\\": 25,\\n    \\\"longExchange\\\": \\\"-\\\",\\n    \\\"shortExchange\\\": \\\"-\\\",\\n    \\\"suggestedAmount\\\": \\\"-\\\",\\n    \\\"rateSpread\\\": \\\"-\\\",\\n    \\\"estimatedDailyProfit\\\": \\\"-\\\",\\n    \\\"riskLevel\\\": \\\"high\\\",\\n    \\\"reason\\\": \\\"费率收益无法覆盖高波动风险和交易成本\\\"\\n  }\\n]\\n```\\n\\n### 字段说明\\n| 字段 | 说明 |\\n|------|------|\\n| symbol | 交易对名称 |\\n| proceed | 是否建议开仓(true/false) |\\n| score | 综合评分(0-100) |\\n| longExchange | 做多交易所(proceed=false时返回\\\"-\\\") |\\n| shortExchange | 做空交易所(proceed=false时返回\\\"-\\\") |\\n| suggestedAmount | 建议金额USDT(proceed=false时返回\\\"-\\\") |\\n| rateSpread | 费率价差百分比(proceed=false时返回\\\"-\\\") |\\n| estimatedDailyProfit | 预估日收益USDT(proceed=false时返回\\\"-\\\") |\\n| riskLevel | 风险等级:low/medium/high |\\n| reason | 分析理由(不超过30字,需说明关键的收益风险权衡) |\\n\\n### suggestedAmount 建议逻辑\\n\\n根据评分和风险等级动态调整:\\n\\n- 评分 ≥ 80 且 riskLevel=low:{{$vars.Amount * 1.0}} USDT\\n- 评分 70-79 或 riskLevel=medium:{{$vars.Amount * 0.8}} USDT  \\n- 评分 60-69:{{$vars.Amount * 0.6}} USDT\\n- 评分 50-59 且收益风险比良好:{{$vars.Amount * 0.4}} USDT\\n- 评分 < 50:不建议开仓(proceed=false)\\n\\n### riskLevel 判定\\n\\n综合判断,不使用硬性阈值:\\n\\n- **low**:收益风险比优秀(rateSpread > range),流动性充足,成本可控\\n- **medium**:收益风险比可接受,存在一定波动但可覆盖\\n- **high**:收益风险比较差,或流动性/成本存在明显问题\\n\\n---\\n\\n## 待分析数据\\n\\n{{JSON.stringify($json)}}\\n\\n**请直接输出 JSON 数组,不要包含任何解释或 markdown 标记。**\",\"options\":{}},\"id\":\"341dd7be-52e8-4e33-9192-328fdb93a649\",\"name\":\"AI 智能体\",\"position\":[0,496],\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1},{\"parameters\":{\"model\":{\"__rl\":true,\"cachedResultName\":\"deepseek/deepseek-v3.2\",\"mode\":\"list\",\"value\":\"deepseek/deepseek-v3.2\"}},\"id\":\"dbf2b7dc-4599-4d0b-9513-1df7f1262706\",\"name\":\"OpenAI 模型\",\"position\":[96,672],\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"conditions\":{\"combinator\":\"and\",\"conditions\":[{\"id\":\"4d7a9873-cd57-4290-ae15-a6969ada40b6\",\"leftValue\":\"={{ $json.data }}\",\"operator\":{\"operation\":\"empty\",\"singleValue\":true,\"type\":\"object\"},\"rightValue\":\"\"}],\"options\":{\"caseSensitive\":true,\"leftValue\":\"\",\"typeValidation\":\"strict\",\"version\":2}},\"looseTypeValidation\":false,\"options\":{}},\"id\":\"ae99e948-2223-4759-88ef-2584d85e4ab7\",\"name\":\"条件判断\",\"position\":[-224,352],\"type\":\"n8n-nodes-base.if\",\"typeVersion\":2.2},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 资金费率套利执行系统 ==========\\n\\nconst CONFIG = {\\n    DEFAULT_LEVERAGE: 10,\\n    MAX_CLOSE_WAIT: 10,\\n    CLOSE_CHECK_INTERVAL: 1000,\\n    MAX_MARKET_RETRY: 20,\\n    MARKET_RETRY_INTERVAL: 500,\\n    ORDER_CHECK_INTERVAL: 500,      // 订单状态检查间隔\\n    ORDER_CHECK_MAX_RETRY: 20,      // 最大检查次数\\n    ORDER_TIMEOUT: 15000            // 订单超时时间 (ms)\\n};\\n\\n// ========== 交易所映射 ==========\\n\\nconst EXCHANGE_MAP = {};\\nconst exchangeList = $vars.Exchange;\\n\\nif (Array.isArray(exchangeList)) {\\n    exchangeList.forEach((exchange, index) => {\\n        EXCHANGE_MAP[exchange.toLowerCase()] = index;\\n    });\\n} else if (typeof exchangeList === 'string') {\\n    exchangeList.split(',').forEach((exchange, index) => {\\n        const exName = exchange.trim();\\n        EXCHANGE_MAP[exName.toLowerCase()] = index;\\n    });\\n}\\n\\n// 使用 USDC 的交易所列表\\nconst USDC_EXCHANGES = ['hyperliquid', 'lighter'];\\n\\n// ========== 工具函数 ==========\\n\\nfunction getExchange(exchangeName) {\\n    const name = exchangeName.toLowerCase();\\n    const index = EXCHANGE_MAP[name];\\n    if (index === undefined) {\\n        Log(`❌ 未找到交易所: ${exchangeName}`);\\n        return null;\\n    }\\n    return exchanges[index];\\n}\\n\\nfunction getSymbol(baseAsset, exchangeName) {\\n    const exName = exchangeName.toLowerCase();\\n    \\n    // Hyperliquid 使用 USD 计价\\n    if (exName === 'hyperliquid') {\\n        return `${baseAsset}_USD.swap`;\\n    }\\n    \\n    // Lighter 使用 USDC 计价\\n    if (exName === 'lighter') {\\n        return `${baseAsset}_USDC.swap`;\\n    }\\n    \\n    // 其他交易所使用 USDT\\n    return `${baseAsset}_USDT.swap`;\\n}\\n\\nfunction parseAIOutput(output) {\\n    try {\\n        let parsed;\\n        if (typeof output === 'string') {\\n            let cleaned = output.replace(/```[a-z]*\\\\n?/gi, '').trim();\\n            parsed = JSON.parse(cleaned);\\n        } else {\\n            parsed = output;\\n        }\\n        \\n        if (!Array.isArray(parsed)) return [];\\n        \\n        const signals = parsed.filter(item => item.proceed === true);\\n        Log(`✅ 解析 ${parsed.length} 个AI信号,其中 ${signals.length} 个建议开仓`);\\n        return signals;\\n    } catch (e) {\\n        Log(`❌ parseAIOutput失败: ${e.message}`);\\n        return [];\\n    }\\n}\\n\\nfunction getBaseAsset(symbol) {\\n    return symbol.replace(/-PERP$/i, '').replace(/_PERP$/i, '').toUpperCase();\\n}\\n\\nfunction getMarketInfo(ex, symbol) {\\n    for (let i = 0; i < CONFIG.MAX_MARKET_RETRY; i++) {\\n        try {\\n            const markets = ex.GetMarkets();\\n            if (markets && markets[symbol]) {\\n                return markets[symbol];\\n            }\\n        } catch (e) {}\\n        Sleep(CONFIG.MARKET_RETRY_INTERVAL);\\n    }\\n    return null;\\n}\\n\\nfunction getCurrentPrice(ex, symbol) {\\n    try {\\n        const ticker = ex.GetTicker(symbol);\\n        return ticker ? ticker.Last : 0;\\n    } catch (e) {\\n        Log(`❌ 获取价格失败: ${symbol} - ${e.message}`);\\n        return 0;\\n    }\\n}\\n\\nfunction getValidLeverage(ex, symbol, desiredLeverage, exchangeName) {\\n    const leveragesToTry = [desiredLeverage, 5, 3, 2, 1];\\n    \\n    Log(`🔧 ${symbol}@${exchangeName}: 尝试设置杠杆 [${leveragesToTry.join(', ')}]`);\\n    \\n    for (let i = 0; i < leveragesToTry.length; i++) {\\n        let lev = leveragesToTry[i];\\n        if (lev > desiredLeverage) continue;\\n        \\n        let result = null;\\n        let success = false;\\n        \\n        try {\\n            result = ex.SetMarginLevel(symbol, lev);\\n            \\n            if (result === true || result === lev || (result && !result.error && !result.code)) {\\n                success = true;\\n            } else if (result === null || result === false) {\\n                success = false;\\n                Log(`⚠️ ${symbol}: 杠杆${lev}x返回 ${result}`);\\n            } else if (typeof result === 'object' && (result.code || result.error)) {\\n                success = false;\\n                Log(`⚠️ ${symbol}: 杠杆${lev}x返回错误 - ${JSON.stringify(result)}`);\\n            } else {\\n                success = true;\\n            }\\n        } catch (e) {\\n            success = false;\\n            Log(`⚠️ ${symbol}: 杠杆${lev}x不支持,尝试下一个...`);\\n        }\\n        \\n        if (success) {\\n            Log(`✅ ${symbol}: 成功设置杠杆 ${lev}x`);\\n            return lev;\\n        }\\n        \\n        Sleep(200);\\n    }\\n    \\n    Log(`⚠️ ${symbol}: 所有杠杆设置都失败,使用默认1x`);\\n    return 1;\\n}\\n\\nfunction calculateContractAmount(amountUSD, currentPrice, market) {\\n    if (!currentPrice || currentPrice <= 0) {\\n        Log(`⚠️ 价格无效: ${currentPrice}`);\\n        return 0;\\n    }\\n    \\n    let ctVal = 1;\\n    if (market && market.CtVal && market.CtVal > 0) {\\n        ctVal = market.CtVal;\\n    }\\n    \\n    let amount = amountUSD / currentPrice / ctVal;\\n    \\n    Log(`📊 计算: $${amountUSD} / $${currentPrice} / ${ctVal} = ${amount}`);\\n    \\n    if (market && market.AmountPrecision !== undefined) {\\n        amount = _N(amount, market.AmountPrecision);\\n    }\\n    \\n    if (market && market.MinQty && market.MinQty > 0 && amount < market.MinQty) {\\n        Log(`⚠️ 计算数量 ${amount} < 最小 ${market.MinQty}`);\\n        return 0;\\n    }\\n    \\n    if (market && market.MaxQty && market.MaxQty > 0 && amount > market.MaxQty) {\\n        Log(`⚠️ 计算数量 ${amount} > 最大 ${market.MaxQty},使用最大值`);\\n        amount = market.MaxQty;\\n    }\\n    \\n    Log(`📊 最终数量: ${amount}`);\\n    return amount;\\n}\\n\\nfunction hasPosition(ex, symbol) {\\n    try {\\n        const positions = ex.GetPositions(symbol);\\n        return positions && positions.some(p => Math.abs(p.Amount) > 0);\\n    } catch (e) {\\n        return false;\\n    }\\n}\\n\\nfunction getPosition(ex, symbol) {\\n    try {\\n        const positions = ex.GetPositions(symbol);\\n        return positions && positions.find(p => Math.abs(p.Amount) > 0);\\n    } catch (e) {\\n        return null;\\n    }\\n}\\n\\n// ========== 订单状态检查函数 ==========\\n\\n/**\\n * 获取订单状态描述\\n */\\nfunction getOrderStatusName(status) {\\n    if (status === ORDER_STATE_PENDING) return 'PENDING';\\n    if (status === ORDER_STATE_CLOSED) return 'CLOSED';\\n    if (status === ORDER_STATE_CANCELED) return 'CANCELED';\\n    if (status === ORDER_STATE_UNKNOWN) return 'UNKNOWN';\\n    return `UNKNOWN(${status})`;\\n}\\n\\n/**\\n * 检查订单状态\\n * @param {Object} ex - 交易所对象\\n * @param {string} orderId - 订单ID\\n * @returns {Object} - { status, order, filled, remaining }\\n */\\nfunction checkOrderStatus(ex, orderId) {\\n    try {\\n        const order = ex.GetOrder(orderId);\\n        if (!order) {\\n            Log(`⚠️ 无法获取订单信息: ${orderId}`);\\n            return { status: null, order: null, filled: 0, remaining: 0 };\\n        }\\n        \\n        Log(`📋 订单状态: ${getOrderStatusName(order.Status)} | 成交:${order.DealAmount}/${order.Amount} | 均价:${order.AvgPrice}`);\\n        \\n        return {\\n            status: order.Status,\\n            order: order,\\n            filled: order.DealAmount || 0,\\n            remaining: (order.Amount || 0) - (order.DealAmount || 0),\\n            avgPrice: order.AvgPrice || order.Price || 0\\n        };\\n    } catch (e) {\\n        Log(`❌ 检查订单状态异常: ${e.message}`);\\n        return { status: null, order: null, filled: 0, remaining: 0 };\\n    }\\n}\\n\\n/**\\n * 等待订单成交\\n * @param {Object} ex - 交易所对象\\n * @param {string} orderId - 订单ID\\n * @param {string} symbol - 交易对\\n * @param {string} exchangeName - 交易所名称\\n * @returns {Object} - { success, filled, avgPrice, cancelled }\\n */\\nfunction waitForOrderFill(ex, orderId, symbol, exchangeName) {\\n    const startTime = Date.now();\\n    let lastResult = null;\\n    \\n    Log(`⏳ ${exchangeName}/${symbol}: 等待订单 ${orderId} 成交...`);\\n    \\n    for (let i = 0; i < CONFIG.ORDER_CHECK_MAX_RETRY; i++) {\\n        const elapsed = Date.now() - startTime;\\n        \\n        // 超时检查\\n        if (elapsed > CONFIG.ORDER_TIMEOUT) {\\n            Log(`⚠️ ${exchangeName}/${symbol}: 订单等待超时 (${elapsed}ms)`);\\n            break;\\n        }\\n        \\n        const result = checkOrderStatus(ex, orderId);\\n        lastResult = result;\\n        \\n        // 完全成交\\n        if (result.status === ORDER_STATE_CLOSED) {\\n            Log(`✅ ${exchangeName}/${symbol}: 订单完全成交 | 成交量:${result.filled} | 均价:${result.avgPrice}`);\\n            return {\\n                success: true,\\n                filled: result.filled,\\n                avgPrice: result.avgPrice,\\n                cancelled: false\\n            };\\n        }\\n        \\n        // 已取消\\n        if (result.status === ORDER_STATE_CANCELED) {\\n            Log(`⚠️ ${exchangeName}/${symbol}: 订单已被取消 | 已成交:${result.filled}`);\\n            return {\\n                success: result.filled > 0,\\n                filled: result.filled,\\n                avgPrice: result.avgPrice,\\n                cancelled: true\\n            };\\n        }\\n        \\n        // 未知状态\\n        if (result.status === ORDER_STATE_UNKNOWN) {\\n            Log(`⚠️ ${exchangeName}/${symbol}: 订单状态未知,继续等待...`);\\n        }\\n        \\n        // 未完成,继续等待\\n        if (result.status === ORDER_STATE_PENDING) {\\n            if (result.filled > 0) {\\n                Log(`⏳ ${exchangeName}/${symbol}: 部分成交 ${result.filled}/${result.filled + result.remaining}`);\\n            }\\n        }\\n        \\n        Sleep(CONFIG.ORDER_CHECK_INTERVAL);\\n    }\\n    \\n    // 超时或达到最大重试次数,检查最终状态\\n    Log(`⚠️ ${exchangeName}/${symbol}: 达到最大检查次数,检查最终状态...`);\\n    \\n    const finalResult = checkOrderStatus(ex, orderId);\\n    \\n    if (finalResult.status === ORDER_STATE_CLOSED) {\\n        Log(`✅ ${exchangeName}/${symbol}: 订单最终成交 | 成交量:${finalResult.filled}`);\\n        return {\\n            success: true,\\n            filled: finalResult.filled,\\n            avgPrice: finalResult.avgPrice,\\n            cancelled: false\\n        };\\n    }\\n    \\n    // 订单未完全成交,需要处理\\n    if (finalResult.status === ORDER_STATE_PENDING) {\\n        Log(`⚠️ ${exchangeName}/${symbol}: 订单未完全成交 (已成交:${finalResult.filled}),尝试取消...`);\\n        \\n        // 尝试取消未成交部分\\n        try {\\n            const cancelResult = ex.CancelOrder(orderId);\\n            Log(`📋 取消订单结果: ${cancelResult}`);\\n            Sleep(500);\\n            \\n            // 再次检查\\n            const afterCancel = checkOrderStatus(ex, orderId);\\n            Log(`📊 ${exchangeName}/${symbol}: 取消后 - 状态:${getOrderStatusName(afterCancel.status)} 已成交:${afterCancel.filled}`);\\n            \\n            return {\\n                success: afterCancel.filled > 0,\\n                filled: afterCancel.filled,\\n                avgPrice: afterCancel.avgPrice,\\n                cancelled: true,\\n                partialFill: afterCancel.filled > 0 && afterCancel.remaining > 0\\n            };\\n        } catch (e) {\\n            Log(`❌ ${exchangeName}/${symbol}: 取消订单失败 - ${e.message}`);\\n        }\\n    }\\n    \\n    return {\\n        success: finalResult.filled > 0,\\n        filled: finalResult.filled || 0,\\n        avgPrice: finalResult.avgPrice || 0,\\n        cancelled: false,\\n        timeout: true\\n    };\\n}\\n\\n// ========== 开仓执行函数 (带订单确认) ==========\\n\\nfunction executeOpen(ex, symbol, isLong, amountUSD, exchangeName) {\\n    try {\\n        const market = getMarketInfo(ex, symbol);\\n        if (!market) {\\n            Log(`❌ ${exchangeName}/${symbol}: 无法获取市场信息`);\\n            return { success: false, error: '无法获取市场信息' };\\n        }\\n        \\n        const currentPrice = getCurrentPrice(ex, symbol);\\n        if (currentPrice <= 0) {\\n            Log(`❌ ${exchangeName}/${symbol}: 无法获取价格`);\\n            return { success: false, error: '无法获取价格' };\\n        }\\n        \\n        const actualLeverage = getValidLeverage(ex, symbol, CONFIG.DEFAULT_LEVERAGE, exchangeName);\\n        \\n        const contractAmount = calculateContractAmount(amountUSD, currentPrice, market);\\n        \\n        if (contractAmount <= 0) {\\n            Log(`❌ ${exchangeName}/${symbol}: 计算合约数量为0`);\\n            return { success: false, error: '合约数量为0' };\\n        }\\n        \\n        Log(`📥 ${exchangeName}/${symbol}: 开${isLong ? '多' : '空'} $${amountUSD} @$${currentPrice} ${contractAmount}张 ${actualLeverage}x`);\\n        \\n        // 下单\\n        const orderId = isLong \\n            ? ex.CreateOrder(symbol, 'buy', -1, contractAmount) \\n            : ex.CreateOrder(symbol, 'sell', -1, contractAmount);\\n        \\n        if (!orderId) {\\n            Log(`❌ ${exchangeName}/${symbol}: 下单返回空`);\\n            return { success: false, error: '下单返回空' };\\n        }\\n        \\n        Log(`📝 ${exchangeName}/${symbol}: 订单已提交 OrderID=${orderId}`);\\n        \\n        // 等待订单成交并验证\\n        const fillResult = waitForOrderFill(ex, orderId, symbol, exchangeName);\\n        \\n        if (!fillResult.success) {\\n            Log(`❌ ${exchangeName}/${symbol}: 订单未成交`);\\n            return { \\n                success: false, \\n                error: fillResult.timeout ? '订单超时未成交' : '订单未成交',\\n                orderId,\\n                filled: fillResult.filled\\n            };\\n        }\\n        \\n        // 检查实际持仓\\n        Sleep(300);\\n        const position = getPosition(ex, symbol);\\n        if (!position || Math.abs(position.Amount) === 0) {\\n            Log(`⚠️ ${exchangeName}/${symbol}: 订单显示成交但未检测到持仓,再次确认...`);\\n            Sleep(500);\\n            const position2 = getPosition(ex, symbol);\\n            if (!position2 || Math.abs(position2.Amount) === 0) {\\n                Log(`❌ ${exchangeName}/${symbol}: 确认无持仓,开仓失败`);\\n                return { \\n                    success: false, \\n                    error: '订单成交但无持仓',\\n                    orderId,\\n                    filled: fillResult.filled\\n                };\\n            }\\n        }\\n        \\n        Log(`✅ ${exchangeName}/${symbol}: 开${isLong ? '多' : '空'}成功 | 成交量:${fillResult.filled} | 均价:${fillResult.avgPrice}`);\\n        \\n        return { \\n            success: true, \\n            orderId, \\n            price: fillResult.avgPrice || currentPrice, \\n            amount: fillResult.filled,\\n            leverage: actualLeverage,\\n            requestedAmount: contractAmount\\n        };\\n        \\n    } catch (e) {\\n        Log(`❌ ${exchangeName}/${symbol}: 开仓异常 - ${e.message}`);\\n        return { success: false, error: e.message };\\n    }\\n}\\n\\n// ========== 平仓函数 (带订单确认) ==========\\n\\nfunction executeClose(ex, symbol, isLong, amount, exchangeName) {\\n    try {\\n        Log(`📤 ${exchangeName}/${symbol}: 平${isLong ? '多' : '空'} ${amount}张`);\\n        \\n        const orderId = isLong\\n            ? ex.CreateOrder(symbol, \\\"closebuy\\\", -1, amount)\\n            : ex.CreateOrder(symbol, \\\"closesell\\\", -1, amount);\\n        \\n        if (!orderId) {\\n            Log(`❌ ${exchangeName}/${symbol}: 平仓下单返回空`);\\n            return { success: false, error: '平仓下单返回空' };\\n        }\\n        \\n        Log(`📝 ${exchangeName}/${symbol}: 平仓订单已提交 OrderID=${orderId}`);\\n        \\n        // 等待平仓订单成交\\n        const fillResult = waitForOrderFill(ex, orderId, symbol, exchangeName);\\n        \\n        if (!fillResult.success) {\\n            Log(`⚠️ ${exchangeName}/${symbol}: 平仓订单未完全成交,已成交:${fillResult.filled}`);\\n            return {\\n                success: false,\\n                partial: fillResult.filled > 0,\\n                filled: fillResult.filled,\\n                orderId\\n            };\\n        }\\n        \\n        Log(`✅ ${exchangeName}/${symbol}: 平仓成功 | 成交量:${fillResult.filled} | 均价:${fillResult.avgPrice}`);\\n        \\n        return {\\n            success: true,\\n            orderId,\\n            filled: fillResult.filled,\\n            avgPrice: fillResult.avgPrice\\n        };\\n        \\n    } catch (e) {\\n        Log(`❌ ${exchangeName}/${symbol}: 平仓异常 - ${e.message}`);\\n        return { success: false, error: e.message };\\n    }\\n}\\n\\n// ========== 套利开仓(同时开两边) ==========\\n\\nfunction executeArbitrageOpen(signal) {\\n    const { symbol, longExchange, shortExchange, suggestedAmount, rateSpread, score, riskLevel, reason } = signal;\\n    \\n    const baseAsset = getBaseAsset(symbol);\\n    const amountPerSide = suggestedAmount; \\n    \\n    Log(`\\\\n${'='.repeat(60)}`);\\n    Log(`🎯 套利开仓: ${symbol} | 评分:${score} | 风险:${riskLevel}`);\\n    Log(`   Long@${longExchange} | Short@${shortExchange} | 费率差:${rateSpread}`);\\n    Log(`   建议金额: $${suggestedAmount} (每边$${amountPerSide})`);\\n    Log(`   理由: ${reason}`);\\n    \\n    // 获取交易所实例\\n    const longEx = getExchange(longExchange);\\n    const shortEx = getExchange(shortExchange);\\n    \\n    if (!longEx) {\\n        Log(`❌ 无法获取做多交易所: ${longExchange}`);\\n        return { success: false, error: `无法获取交易所: ${longExchange}` };\\n    }\\n    if (!shortEx) {\\n        Log(`❌ 无法获取做空交易所: ${shortExchange}`);\\n        return { success: false, error: `无法获取交易所: ${shortExchange}` };\\n    }\\n    \\n    // 获取交易对符号\\n    const longSymbol = getSymbol(baseAsset, longExchange);\\n    const shortSymbol = getSymbol(baseAsset, shortExchange);\\n    \\n    Log(`   Long符号: ${longSymbol} | Short符号: ${shortSymbol}`);\\n    \\n    // 检查是否已有持仓\\n    if (hasPosition(longEx, longSymbol)) {\\n        Log(`⚠️ ${longExchange}/${longSymbol} 已有持仓,跳过`);\\n        return { success: false, error: 'Long交易所已有持仓' };\\n    }\\n    if (hasPosition(shortEx, shortSymbol)) {\\n        Log(`⚠️ ${shortExchange}/${shortSymbol} 已有持仓,跳过`);\\n        return { success: false, error: 'Short交易所已有持仓' };\\n    }\\n    \\n    // 执行开仓\\n    const results = {\\n        long: null,\\n        short: null\\n    };\\n    \\n    // 开多仓\\n    Log(`\\\\n📈 开多仓 @${longExchange}...`);\\n    results.long = executeOpen(longEx, longSymbol, true, amountPerSide, longExchange);\\n    \\n    if (!results.long.success) {\\n        Log(`❌ 开多仓失败,取消套利`);\\n        return { \\n            success: false, \\n            error: `开多失败: ${results.long.error}`,\\n            results \\n        };\\n    }\\n    \\n    Sleep(500);\\n    \\n    // 开空仓\\n    Log(`\\\\n📉 开空仓 @${shortExchange}...`);\\n    results.short = executeOpen(shortEx, shortSymbol, false, amountPerSide, shortExchange);\\n    \\n    if (!results.short.success) {\\n        Log(`❌ 开空仓失败,需要平掉多仓!`);\\n        \\n        // 尝试平掉多仓\\n        const pos = getPosition(longEx, longSymbol);\\n        if (pos && Math.abs(pos.Amount) > 0) {\\n            Log(`⚠️ 尝试平掉多仓: ${pos.Amount}张`);\\n            const closeResult = executeClose(longEx, longSymbol, true, Math.abs(pos.Amount), longExchange);\\n            \\n            if (closeResult.success) {\\n                Log(`✅ 多仓已平掉`);\\n            } else {\\n                Log(`❌ 平多仓失败: ${closeResult.error},请手动处理!`);\\n            }\\n        }\\n        \\n        return { \\n            success: false, \\n            error: `开空失败: ${results.short.error}`,\\n            results \\n        };\\n    }\\n    \\n    // 两边都成功,验证持仓\\n    Log(`\\\\n🔍 验证套利持仓...`);\\n    \\n    Sleep(500);\\n    const longPos = getPosition(longEx, longSymbol);\\n    const shortPos = getPosition(shortEx, shortSymbol);\\n    \\n    if (!longPos || Math.abs(longPos.Amount) === 0) {\\n        Log(`❌ 验证失败: 多仓不存在`);\\n        // 如果空仓存在,需要平掉\\n        if (shortPos && Math.abs(shortPos.Amount) > 0) {\\n            executeClose(shortEx, shortSymbol, false, Math.abs(shortPos.Amount), shortExchange);\\n        }\\n        return { success: false, error: '多仓验证失败' };\\n    }\\n    \\n    if (!shortPos || Math.abs(shortPos.Amount) === 0) {\\n        Log(`❌ 验证失败: 空仓不存在`);\\n        // 平掉多仓\\n        executeClose(longEx, longSymbol, true, Math.abs(longPos.Amount), longExchange);\\n        return { success: false, error: '空仓验证失败' };\\n    }\\n    \\n    Log(`✅ 持仓验证通过:`);\\n    Log(`   多仓: ${Math.abs(longPos.Amount)}张 @${longPos.Price}`);\\n    Log(`   空仓: ${Math.abs(shortPos.Amount)}张 @${shortPos.Price}`);\\n    \\n    // 保存套利持仓信息\\n    const positionKey = `arb_${baseAsset}`;\\n    _G(positionKey, {\\n        symbol,\\n        baseAsset,\\n        longExchange,\\n        shortExchange,\\n        longSymbol,\\n        shortSymbol,\\n        longPrice: results.long.price,\\n        shortPrice: results.short.price,\\n        longAmount: results.long.amount,\\n        shortAmount: results.short.amount,\\n        actualLongAmount: Math.abs(longPos.Amount),\\n        actualShortAmount: Math.abs(shortPos.Amount),\\n        rateSpread,\\n        suggestedAmount,\\n        score,\\n        riskLevel,\\n        openTime: Date.now(),\\n        verified: true\\n    });\\n    \\n    Log(`\\\\n✅ 套利开仓成功并已验证!`);\\n    \\n    return {\\n        success: true,\\n        results,\\n        positionKey,\\n        verified: true\\n    };\\n}\\n\\n// ========== 主函数 ==========\\n\\nfunction main() {\\n    Log(`\\\\n🚀 资金费率套利系统启动`);\\n    Log(`📋 交易所映射: ${JSON.stringify(EXCHANGE_MAP)}`);\\n    \\n    const aiOutput = $input.first().json.output;\\n    Log('AI分析建议:', aiOutput);\\n    const signals = parseAIOutput(aiOutput);\\n    \\n    if (!signals || signals.length === 0) {\\n        Log('⚠️ 无有效开仓信号');\\n        return { json: { processed: false, reason: 'No valid signals' } };\\n    }\\n    \\n    Log(`\\\\n🤖 待执行: ${signals.length} 个套利信号`);\\n    \\n    const executionResults = {};\\n    let successCount = 0;\\n    let failCount = 0;\\n    \\n    for (let signal of signals) {\\n        const result = executeArbitrageOpen(signal);\\n        \\n        executionResults[signal.symbol] = {\\n            ...signal,\\n            executed: result.success,\\n            verified: result.verified || false,\\n            error: result.error || null,\\n            results: result.results || null,\\n            timestamp: Date.now()\\n        };\\n        \\n        if (result.success) {\\n            successCount++;\\n        } else {\\n            failCount++;\\n        }\\n        \\n        Sleep(1000);\\n    }\\n    \\n    // 保存执行结果\\n    _G('latestArbitrageResults', {\\n        results: executionResults,\\n        timestamp: Date.now(),\\n        totalSignals: signals.length,\\n        successCount,\\n        failCount\\n    });\\n    \\n    Log(`\\\\n${'='.repeat(60)}`);\\n    Log(`📊 执行总结: ${signals.length}信号 | ✅${successCount}成功 | ❌${failCount}失败`);\\n    \\n    return { \\n        json: { \\n            processed: true, \\n            totalSignals: signals.length, \\n            successCount,\\n            failCount,\\n            results: executionResults \\n        } \\n    };\\n}\\n\\nreturn main();\\n\",\"notice\":\"\"},\"id\":\"39f91834-87e7-45f4-8bac-b133b84e39ce\",\"name\":\"开仓执行\",\"position\":[400,496],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"logAll\":false,\"output\":\"🍃:未检测到新的套利机会!111\"},\"id\":\"ef04604e-61a8-4af9-ab73-0370ffe4aa15\",\"name\":\"日志输出\",\"position\":[96,192],\"type\":\"n8n-nodes-base.log\",\"typeVersion\":1},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 资金费率套利可视化仪表板 ==========\\nLog(exchanges[0].GetPositions())\\n\\n// ========== 配置参数 ==========\\nconst CONFIG = {\\n    CHECK_INTERVAL: 500\\n};\\n\\n// ========== 交易所映射 ==========\\nconst EXCHANGE_MAP = {};\\nconst exchangeList = $vars.Exchange;\\n\\nif (Array.isArray(exchangeList)) {\\n    exchangeList.forEach((exchange, index) => {\\n        EXCHANGE_MAP[exchange.toLowerCase()] = index;\\n    });\\n} else if (typeof exchangeList === 'string') {\\n    exchangeList.split(',').forEach((exchange, index) => {\\n        const exName = exchange.trim();\\n        EXCHANGE_MAP[exName.toLowerCase()] = index;\\n    });\\n}\\n\\n// 使用 USDC 的交易所列表\\nconst USDC_EXCHANGES = ['hyperliquid', 'lighter'];\\n\\n// ========== 工具函数 ==========\\n\\nfunction getExchange(exchangeName) {\\n    const name = exchangeName.toLowerCase();\\n    const index = EXCHANGE_MAP[name];\\n    if (index === undefined) {\\n        return null;\\n    }\\n    return exchanges[index];\\n}\\n\\nfunction formatPct(value, decimals = 2) {\\n    if (value === null || value === undefined) return '-';\\n    return (value * 100).toFixed(decimals) + '%';\\n}\\n\\nfunction formatVolume(value) {\\n    if (!value) return '-';\\n    if (value >= 1000000) {\\n        return '$' + (value / 1000000).toFixed(2) + 'M';\\n    } else if (value >= 1000) {\\n        return '$' + (value / 1000).toFixed(1) + 'K';\\n    }\\n    return '$' + value.toFixed(0);\\n}\\n\\nfunction formatTime(timestamp) {\\n    if (!timestamp) return '-';\\n    return new Date(timestamp).toLocaleString('zh-CN', {\\n        month: '2-digit',\\n        day: '2-digit',\\n        hour: '2-digit',\\n        minute: '2-digit'\\n    });\\n}\\n\\n// ========== 获取所有交易所账户信息 ==========\\n\\nfunction getAllAccountsInfo() {\\n    const accounts = {};\\n    let totalEquity = 0;\\n    \\n    for (const [exName, exIndex] of Object.entries(EXCHANGE_MAP)) {\\n        try {\\n            const EX = exchanges[exIndex];\\n            if (!EX) continue;\\n            \\n            const account = EX.GetAccount();\\n            if (account) {\\n                accounts[exName] = {\\n                    balance: account.Balance || 0,\\n                    frozenBalance: account.FrozenBalance || 0,\\n                    equity: account.Equity || account.Balance || 0\\n                };\\n                totalEquity += accounts[exName].equity;\\n            }\\n        } catch (e) {\\n            accounts[exName] = { error: e.message };\\n        }\\n    }\\n    \\n    return { accounts, totalEquity };\\n}\\n\\n// ========== 获取所有仓位信息 ==========\\n\\nfunction getAllPositions() {\\n    const allPositions = {};\\n    \\n    for (const [exName, exIndex] of Object.entries(EXCHANGE_MAP)) {\\n        try {\\n            const EX = exchanges[exIndex];\\n            if (!EX) continue;\\n            \\n            const positions = EX.GetPositions();\\n            if (!positions || !Array.isArray(positions)) continue;\\n            \\n            for (const pos of positions) {\\n                if (Math.abs(pos.Amount) < 0.0000001) continue;\\n                \\n                const match = pos.Symbol?.match(/^([A-Z0-9]+)_/);\\n                if (!match) continue;\\n                \\n                const baseAsset = match[1];\\n                const isLong = pos.Type == 0;\\n                \\n                if (!allPositions[baseAsset]) {\\n                    allPositions[baseAsset] = {\\n                        longExchange: null,\\n                        shortExchange: null,\\n                        longPosition: null,\\n                        shortPosition: null\\n                    };\\n                }\\n                \\n                if (isLong) {\\n                    allPositions[baseAsset].longExchange = exName;\\n                    allPositions[baseAsset].longPosition = {\\n                        symbol: pos.Symbol,\\n                        amount: Math.abs(pos.Amount),\\n                        price: pos.Price,\\n                        profit: pos.Profit || 0,\\n                        margin: pos.Margin || 0,\\n                        exchangeIndex: exIndex\\n                    };\\n                } else {\\n                    allPositions[baseAsset].shortExchange = exName;\\n                    allPositions[baseAsset].shortPosition = {\\n                        symbol: pos.Symbol,\\n                        amount: Math.abs(pos.Amount),\\n                        price: pos.Price,\\n                        profit: pos.Profit || 0,\\n                        margin: pos.Margin || 0,\\n                        exchangeIndex: exIndex\\n                    };\\n                }\\n            }\\n        } catch (e) {\\n            Log(`⚠️ ${exName} 获取仓位失败: ${e.message}`);\\n        }\\n    }\\n    \\n    return allPositions;\\n}\\n\\n// ========== 创建账户概览表 ==========\\n\\nfunction createAccountOverviewTable() {\\n    const accountTable = {\\n        type: \\\"table\\\",\\n        title: \\\"💰 多交易所账户概览\\\",\\n        cols: [\\\"交易所\\\", \\\"💵 可用余额\\\", \\\"🔒 冻结金额\\\", \\\"💰 总权益\\\", \\\"📊 状态\\\"],\\n        rows: []\\n    };\\n    \\n    try {\\n        const { accounts, totalEquity } = getAllAccountsInfo();\\n        const initMoney = _G('initmoney') || totalEquity;\\n        \\n        // 如果没有初始资金记录,保存当前作为初始\\n        if (_G('initmoney') === null) {\\n            _G('initmoney', totalEquity);\\n        }\\n        \\n        for (const [exName, info] of Object.entries(accounts)) {\\n            if (info.error) {\\n                accountTable.rows.push([\\n                    `🏦 ${exName.toUpperCase()}`,\\n                    \\\"❌ 错误\\\",\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    info.error\\n                ]);\\n            } else {\\n                accountTable.rows.push([\\n                    `🏦 ${exName.toUpperCase()}`,\\n                    `$${_N(info.balance, 2)}`,\\n                    `$${_N(info.frozenBalance, 2)}`,\\n                    `$${_N(info.equity, 2)}`,\\n                    info.equity > 0 ? \\\"✅ 正常\\\" : \\\"⚠️ 空\\\"\\n                ]);\\n            }\\n        }\\n        \\n        // 汇总行\\n        const totalProfit = totalEquity - initMoney;\\n        const profitPercent = ((totalEquity - initMoney) / initMoney * 100);\\n        \\n        accountTable.rows.push([\\n            \\\"📊 汇总\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            `$${_N(totalEquity, 2)}`,\\n            profitPercent >= 0 ? \\n                `🟢 +${_N(profitPercent, 2)}% (+$${_N(totalProfit, 2)})` : \\n                `🔴 ${_N(profitPercent, 2)}% ($${_N(totalProfit, 2)})`\\n        ]);\\n        \\n        // 记录收益\\n        LogProfit(totalProfit, \\\"&\\\");\\n        \\n    } catch (e) {\\n        Log(`❌ 创建账户概览表失败: ${e.message}`);\\n        accountTable.rows.push([\\n            \\\"❌ 错误\\\",\\n            \\\"获取账户失败\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            e.message\\n        ]);\\n    }\\n    \\n    return accountTable;\\n}\\n\\n// ========== 创建资金费率套利信号表 ==========\\n\\nfunction createFundingRateTable() {\\n    const fundingTable = {\\n        type: \\\"table\\\",\\n        title: \\\"📈 资金费率套利信号\\\",\\n        cols: [\\\"币种\\\", \\\"做多交易所\\\", \\\"做空交易所\\\", \\\"费率差\\\", \\\"预估年化\\\", \\\"置信度\\\", \\\"开仓原因\\\"],\\n        rows: []\\n    };\\n    \\n    try {\\n        // 从_G获取最新套利执行结果\\n        const arbitrageData = _G('latestArbitrageResults') || { results: {} };\\n        const results = arbitrageData.results || {};\\n        \\n        if (Object.keys(results).length === 0) {\\n            fundingTable.rows.push([\\n                \\\"📭 暂无信号\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"等待套利机会...\\\"\\n            ]);\\n        } else {\\n            // 按是否执行成功分组\\n            const executed = [];\\n            const pending = [];\\n            \\n            Object.entries(results).forEach(([symbol, result]) => {\\n                if (result.executed) {\\n                    executed.push([symbol, result]);\\n                } else {\\n                    pending.push([symbol, result]);\\n                }\\n            });\\n            \\n            // 先显示已执行的\\n            executed.forEach(([symbol, result]) => {\\n                const rateSpread = result.rateSpread || '-';\\n                const estimatedApr = result.estimatedDailyProfit ? \\n                    `${(parseFloat(result.estimatedDailyProfit) * 365 / parseFloat(result.suggestedAmount || 1) * 100).toFixed(1)}%` : '-';\\n                \\n                fundingTable.rows.push([\\n                    `✅ ${symbol}`,\\n                    `📈 ${result.longExchange || '-'}`,\\n                    `📉 ${result.shortExchange || '-'}`,\\n                    rateSpread,\\n                    result.estimatedDailyProfit ? `$${result.estimatedDailyProfit}/天` : '-',\\n                    result.riskLevel === 'low' ? '🟢 低' : \\n                    result.riskLevel === 'medium' ? '🟡 中' : '🔴 高',\\n                    result.reason || '-'\\n                ]);\\n            });\\n            \\n            // 再显示未执行的\\n            pending.forEach(([symbol, result]) => {\\n                fundingTable.rows.push([\\n                    `⏸️ ${symbol}`,\\n                    result.longExchange || '-',\\n                    result.shortExchange || '-',\\n                    result.rateSpread || '-',\\n                    '-',\\n                    result.riskLevel === 'low' ? '🟢 低' : \\n                    result.riskLevel === 'medium' ? '🟡 中' : '🔴 高',\\n                    result.error || result.reason || '未执行'\\n                ]);\\n            });\\n            \\n            // 汇总行\\n            if (arbitrageData.timestamp) {\\n                fundingTable.rows.push([\\n                    `📊 汇总`,\\n                    `✅ ${arbitrageData.successCount || 0}成功`,\\n                    `❌ ${arbitrageData.failCount || 0}失败`,\\n                    `共${arbitrageData.totalSignals || 0}信号`,\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    `更新: ${formatTime(arbitrageData.timestamp)}`\\n                ]);\\n            }\\n        }\\n        \\n    } catch (e) {\\n        Log(`❌ 创建资金费率表失败: ${e.message}`);\\n        fundingTable.rows.push([\\n            \\\"❌ 错误\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            e.message\\n        ]);\\n    }\\n    \\n    return fundingTable;\\n}\\n\\n// ========== 创建实时套利仓位表 ==========\\n\\nfunction createArbitragePositionTable() {\\n    const positionTable = {\\n        type: \\\"table\\\",\\n        title: \\\"💼 实时套利仓位\\\",\\n        cols: [\\\"币种\\\", \\\"多仓交易所\\\", \\\"空仓交易所\\\", \\\"多仓盈亏\\\", \\\"空仓盈亏\\\", \\\"总盈亏\\\", \\\"持仓时长\\\", \\\"状态\\\"],\\n        rows: []\\n    };\\n    \\n    try {\\n        const allPositions = getAllPositions();\\n        let totalProfit = 0;\\n        let positionCount = 0;\\n        \\n        if (Object.keys(allPositions).length === 0) {\\n            positionTable.rows.push([\\n                \\\"📭 空仓\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"等待开仓信号\\\"\\n            ]);\\n        } else {\\n            for (const [baseAsset, posInfo] of Object.entries(allPositions)) {\\n                const hasLong = posInfo.longExchange && posInfo.longPosition;\\n                const hasShort = posInfo.shortExchange && posInfo.shortPosition;\\n                \\n                if (!hasLong && !hasShort) continue;\\n                \\n                // 获取保存的开仓信息\\n                const savedInfo = _G(`arb_${baseAsset}`) || {};\\n                const openTime = savedInfo.openTime;\\n                const holdingDuration = openTime ? \\n                    Math.floor((Date.now() - openTime) / 1000 / 60) + '分钟' : '-';\\n                \\n                // 计算盈亏\\n                const longProfit = hasLong ? posInfo.longPosition.profit : 0;\\n                const shortProfit = hasShort ? posInfo.shortPosition.profit : 0;\\n                const combinedProfit = longProfit + shortProfit;\\n                totalProfit += combinedProfit;\\n                \\n                // 盈亏显示\\n                const longProfitDisplay = hasLong ? \\n                    (longProfit >= 0 ? `🟢 +$${_N(longProfit, 2)}` : `🔴 $${_N(longProfit, 2)}`) : '-';\\n                const shortProfitDisplay = hasShort ? \\n                    (shortProfit >= 0 ? `🟢 +$${_N(shortProfit, 2)}` : `🔴 $${_N(shortProfit, 2)}`) : '-';\\n                const totalProfitDisplay = combinedProfit >= 0 ? \\n                    `🟢 +$${_N(combinedProfit, 2)}` : `🔴 $${_N(combinedProfit, 2)}`;\\n                \\n                // 状态判断\\n                let statusDisplay = \\\"✅ 套利中\\\";\\n                if (!hasLong || !hasShort) {\\n                    statusDisplay = \\\"⚠️ 单边仓位\\\";\\n                } else if (combinedProfit < -50) {\\n                    statusDisplay = \\\"🔴 亏损警告\\\";\\n                }\\n                \\n                positionTable.rows.push([\\n                    `💎 ${baseAsset}`,\\n                    hasLong ? `📈 ${posInfo.longExchange.toUpperCase()}` : '-',\\n                    hasShort ? `📉 ${posInfo.shortExchange.toUpperCase()}` : '-',\\n                    longProfitDisplay,\\n                    shortProfitDisplay,\\n                    totalProfitDisplay,\\n                    holdingDuration,\\n                    statusDisplay\\n                ]);\\n                \\n                positionCount++;\\n            }\\n            \\n            // 汇总行\\n            if (positionCount > 0) {\\n                positionTable.rows.push([\\n                    `📊 汇总`,\\n                    `共${positionCount}对`,\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    totalProfit >= 0 ? `🟢 +$${_N(totalProfit, 2)}` : `🔴 $${_N(totalProfit, 2)}`,\\n                    \\\"-\\\",\\n                    \\\"实时监控中\\\"\\n                ]);\\n            }\\n        }\\n        \\n    } catch (e) {\\n        Log(`❌ 创建仓位表失败: ${e.message}`);\\n        positionTable.rows.push([\\n            \\\"❌ 错误\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            e.message\\n        ]);\\n    }\\n    \\n    return positionTable;\\n}\\n\\n// ========== 创建资金费率历史图表数据 ==========\\n\\nfunction createFundingRateChart() {\\n    const chartConfig = {\\n        type: \\\"table\\\",\\n        title: \\\"📊 套利收益统计\\\",\\n        cols: [\\\"统计项\\\", \\\"今日\\\", \\\"本周\\\", \\\"本月\\\", \\\"累计\\\"],\\n        rows: []\\n    };\\n    \\n    try {\\n        // 从_G获取历史收益数据\\n        const dailyProfit = _G('dailyProfit') || 0;\\n        const weeklyProfit = _G('weeklyProfit') || 0;\\n        const monthlyProfit = _G('monthlyProfit') || 0;\\n        const totalProfit = _G('totalArbitrageProfit') || 0;\\n        \\n        const initMoney = _G('initmoney') || 10000;\\n        \\n        chartConfig.rows.push([\\n            \\\"💰 套利收益\\\",\\n            dailyProfit >= 0 ? `🟢 +$${_N(dailyProfit, 2)}` : `🔴 $${_N(dailyProfit, 2)}`,\\n            weeklyProfit >= 0 ? `🟢 +$${_N(weeklyProfit, 2)}` : `🔴 $${_N(weeklyProfit, 2)}`,\\n            monthlyProfit >= 0 ? `🟢 +$${_N(monthlyProfit, 2)}` : `🔴 $${_N(monthlyProfit, 2)}`,\\n            totalProfit >= 0 ? `🟢 +$${_N(totalProfit, 2)}` : `🔴 $${_N(totalProfit, 2)}`\\n        ]);\\n        \\n        chartConfig.rows.push([\\n            \\\"📈 收益率\\\",\\n            `${_N(dailyProfit / initMoney * 100, 2)}%`,\\n            `${_N(weeklyProfit / initMoney * 100, 2)}%`,\\n            `${_N(monthlyProfit / initMoney * 100, 2)}%`,\\n            `${_N(totalProfit / initMoney * 100, 2)}%`\\n        ]);\\n        \\n        // 统计套利次数\\n        const dailyTrades = _G('dailyTrades') || 0;\\n        const weeklyTrades = _G('weeklyTrades') || 0;\\n        const monthlyTrades = _G('monthlyTrades') || 0;\\n        const totalTrades = _G('totalTrades') || 0;\\n        \\n        chartConfig.rows.push([\\n            \\\"🔄 套利次数\\\",\\n            `${dailyTrades}次`,\\n            `${weeklyTrades}次`,\\n            `${monthlyTrades}次`,\\n            `${totalTrades}次`\\n        ]);\\n        \\n    } catch (e) {\\n        Log(`❌ 创建收益统计表失败: ${e.message}`);\\n        chartConfig.rows.push([\\n            \\\"❌ 错误\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            e.message\\n        ]);\\n    }\\n    \\n    return chartConfig;\\n}\\n\\n// ========== 创建开仓原因详情表 ==========\\n\\nfunction createOpenReasonTable() {\\n    const reasonTable = {\\n        type: \\\"table\\\",\\n        title: \\\"📋 开仓决策详情\\\",\\n        cols: [\\\"币种\\\", \\\"评分\\\", \\\"费率差\\\", \\\"价差稳定性\\\", \\\"流动性评估\\\", \\\"风险等级\\\", \\\"AI判断理由\\\"],\\n        rows: []\\n    };\\n    \\n    try {\\n        const arbitrageData = _G('latestArbitrageResults') || { results: {} };\\n        const results = arbitrageData.results || {};\\n        \\n        if (Object.keys(results).length === 0) {\\n            reasonTable.rows.push([\\n                \\\"📭 暂无\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"-\\\",\\n                \\\"等待AI分析...\\\"\\n            ]);\\n        } else {\\n            // 只显示已执行的\\n            Object.entries(results).forEach(([symbol, result]) => {\\n                if (!result.executed) return;\\n                \\n                // 评分显示\\n                const score = result.score || 0;\\n                let scoreDisplay = `${score}分`;\\n                if (score >= 80) {\\n                    scoreDisplay = `🔥 ${score}分`;\\n                } else if (score >= 60) {\\n                    scoreDisplay = `⚡ ${score}分`;\\n                } else {\\n                    scoreDisplay = `${score}分`;\\n                }\\n                \\n                // 风险等级\\n                let riskDisplay = result.riskLevel === 'low' ? '🟢 低风险' : \\n                    result.riskLevel === 'medium' ? '🟡 中风险' : '🔴 高风险';\\n                \\n                reasonTable.rows.push([\\n                    `💎 ${symbol}`,\\n                    scoreDisplay,\\n                    result.rateSpread || '-',\\n                    result.priceStability || '稳定',\\n                    result.liquidityScore || '良好',\\n                    riskDisplay,\\n                    (result.reason || '-').substring(0, 30)\\n                ]);\\n            });\\n            \\n            if (reasonTable.rows.length === 0) {\\n                reasonTable.rows.push([\\n                    \\\"📭 暂无执行\\\",\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    \\\"-\\\",\\n                    \\\"无符合条件的套利机会\\\"\\n                ]);\\n            }\\n        }\\n        \\n    } catch (e) {\\n        Log(`❌ 创建开仓原因表失败: ${e.message}`);\\n        reasonTable.rows.push([\\n            \\\"❌ 错误\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            \\\"-\\\",\\n            e.message\\n        ]);\\n    }\\n    \\n    return reasonTable;\\n}\\n\\n// ========== 显示完整监控仪表板 ==========\\n\\nfunction displayMonitoringDashboard() {\\n    try {\\n        // 1. 账户概览\\n        const accountTable = createAccountOverviewTable();\\n        \\n        // 2. 资金费率套利信号\\n        const fundingTable = createFundingRateTable();\\n        \\n        // 3. 开仓原因详情\\n        const reasonTable = createOpenReasonTable();\\n        \\n        // 4. 实时套利仓位\\n        const positionTable = createArbitragePositionTable();\\n        \\n        // 5. 收益统计\\n        const statsTable = createFundingRateChart();\\n        \\n        // 组合显示\\n        const dashboardDisplay = \\n            '`' + JSON.stringify(accountTable) + '`\\\\n\\\\n' +\\n            '`' + JSON.stringify(fundingTable) + '`\\\\n\\\\n' +\\n            '`' + JSON.stringify(reasonTable) + '`\\\\n\\\\n' +\\n            '`' + JSON.stringify(positionTable) + '`\\\\n\\\\n' +\\n            '`' + JSON.stringify(statsTable) + '`';\\n        \\n        LogStatus(dashboardDisplay);\\n        \\n    } catch (e) {\\n        Log(`❌ 显示监控仪表板失败: ${e.message}`);\\n        LogStatus('❌ 仪表板显示失败: ' + e.message);\\n    }\\n}\\n\\n// ========== 主函数 ==========\\n\\nfunction main() {\\n    try {\\n        // 显示监控仪表板\\n        displayMonitoringDashboard();\\n        \\n        return {\\n            status: 'success',\\n            timestamp: Date.now()\\n        };\\n        \\n    } catch (e) {\\n        Log(`❌ 主执行流程失败: ${e.message}`);\\n        LogStatus('❌ 系统错误: ' + e.message);\\n        return { error: e.message };\\n    }\\n}\\n\\n// 执行主函数并返回结果\\nreturn main();\\n\",\"notice\":\"\"},\"id\":\"1c9e7060-b12c-4206-9ec0-858aa6018732\",\"name\":\"可视化展示\",\"position\":[-224,816],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 平仓检测与执行代码 ==========\\n\\n// 获取交易所映射\\nconst EXCHANGE_MAP = {};\\nconst exchangeList = $vars.Exchange;\\n\\nif (Array.isArray(exchangeList)) {\\n    exchangeList.forEach((exchange, index) => {\\n        EXCHANGE_MAP[exchange.toLowerCase()] = index;\\n    });\\n} else if (typeof exchangeList === 'string') {\\n    exchangeList.split(',').forEach((exchange, index) => {\\n        const exName = exchange.trim();\\n        EXCHANGE_MAP[exName.toLowerCase()] = index;\\n    });\\n}\\n\\n// ✅ 修复:获取费率统计分析的结果\\nconst inputData = $input.all();\\nconst analysisResult = inputData[0].json;\\n\\n// ✅ 修复:将 high/medium/low 数组转换为 fundingData 格式\\nconst fundingData = {};\\n\\n// 合并所有置信度的数据\\nconst allCoins = [\\n    ...(analysisResult.high || []),\\n    ...(analysisResult.medium || []),\\n    ...(analysisResult.low || [])\\n];\\n\\n// 转换为 { baseAsset: { longExchange, shortExchange, ... } } 格式\\nfor (const coin of allCoins) {\\n    fundingData[coin.baseAsset] = {\\n        longExchange: coin.longExchange,\\n        shortExchange: coin.shortExchange,\\n        spread: coin.spread,\\n        estimatedApr: coin.estimatedApr,\\n        confidence: coin.highCount >= coin.mediumCount && coin.highCount >= coin.lowCount ? 'high' :\\n                    coin.mediumCount >= coin.highCount && coin.mediumCount >= coin.lowCount ? 'medium' : 'low',\\n        score: coin.score\\n    };\\n}\\n\\nLog(`\\\\n${'='.repeat(60)}`);\\nLog(`🔍 平仓检测启动 | ${new Date().toISOString()}`);\\nLog(`📊 当前套利机会: ${Object.keys(fundingData).length} 个`);\\nLog(`   来源: High=${analysisResult.high?.length || 0} Medium=${analysisResult.medium?.length || 0} Low=${analysisResult.low?.length || 0}`);\\nif (Object.keys(fundingData).length > 0) {\\n    Log(`   币种: ${Object.keys(fundingData).join(', ')}`);\\n}\\n\\n// ========== 扫描所有交易所仓位 ==========\\n\\nfunction scanAllPositions() {\\n    const allPositions = {};\\n    \\n    for (const [exName, exIndex] of Object.entries(EXCHANGE_MAP)) {\\n        try {\\n            const EX = exchanges[exIndex];\\n            if (!EX) continue;\\n            \\n            const positions = EX.GetPositions();\\n            if (!positions || !Array.isArray(positions)) continue;\\n            \\n            for (const pos of positions) {\\n                if (Math.abs(pos.Amount) < 0.0000001) continue;\\n                \\n                // 解析币种名称: \\\"BTC_USDT.swap\\\" -> \\\"BTC\\\"\\n                const match = pos.Symbol?.match(/^([A-Z0-9]+)_/);\\n                if (!match) continue;\\n                \\n                const baseAsset = match[1];\\n                const isLong = pos.Amount > 0;\\n                \\n                if (!allPositions[baseAsset]) {\\n                    allPositions[baseAsset] = {\\n                        longExchange: null,\\n                        shortExchange: null,\\n                        longPosition: null,\\n                        shortPosition: null\\n                    };\\n                }\\n                \\n                if (isLong) {\\n                    allPositions[baseAsset].longExchange = exName;\\n                    allPositions[baseAsset].longPosition = {\\n                        symbol: pos.Symbol,\\n                        amount: Math.abs(pos.Amount),\\n                        price: pos.Price,\\n                        profit: pos.Profit || 0,\\n                        exchangeIndex: exIndex\\n                    };\\n                } else {\\n                    allPositions[baseAsset].shortExchange = exName;\\n                    allPositions[baseAsset].shortPosition = {\\n                        symbol: pos.Symbol,\\n                        amount: Math.abs(pos.Amount),\\n                        price: pos.Price,\\n                        profit: pos.Profit || 0,\\n                        exchangeIndex: exIndex\\n                    };\\n                }\\n            }\\n        } catch (e) {\\n            Log(`⚠️ ${exName} 获取仓位失败: ${e.message}`);\\n        }\\n    }\\n    \\n    return allPositions;\\n}\\n\\n// ========== 检查需要平仓的币种 ==========\\n\\nfunction findPositionsToClose(currentPositions, fundingOpportunities) {\\n    const toClose = [];\\n    \\n    for (const [baseAsset, posInfo] of Object.entries(currentPositions)) {\\n        const hasLong = posInfo.longExchange && posInfo.longPosition;\\n        const hasShort = posInfo.shortExchange && posInfo.shortPosition;\\n        \\n        if (!hasLong && !hasShort) continue;\\n        \\n        const opportunity = fundingOpportunities[baseAsset];\\n        \\n        let shouldClose = false;\\n        let closeReason = '';\\n        \\n        if (!opportunity) {\\n            // 没有套利机会了,需要平仓\\n            shouldClose = true;\\n            closeReason = '套利机会已消失';\\n        } else {\\n            const oppLong = opportunity.longExchange?.toLowerCase();\\n            const oppShort = opportunity.shortExchange?.toLowerCase();\\n            const posLong = posInfo.longExchange?.toLowerCase();\\n            const posShort = posInfo.shortExchange?.toLowerCase();\\n            \\n            // 检查方向是否改变\\n            if (oppLong !== posLong || oppShort !== posShort) {\\n                shouldClose = true;\\n                closeReason = `方向改变(${posLong}/${posShort} → ${oppLong}/${oppShort})`;\\n            }\\n        }\\n        \\n        if (shouldClose) {\\n            toClose.push({\\n                baseAsset,\\n                reason: closeReason,\\n                ...posInfo\\n            });\\n        }\\n    }\\n    \\n    return toClose;\\n}\\n\\n// ========== 执行平仓 ==========\\n\\nfunction executeClose(exchangeIndex, symbol, isLong, amount, exchangeName) {\\n    try {\\n        const EX = exchanges[exchangeIndex];\\n        if (!EX) {\\n            return { success: false, error: '交易所不存在' };\\n        }\\n        \\n        const side = isLong ? \\\"closebuy\\\" : \\\"closesell\\\";\\n        \\n        Log(`   📤 ${exchangeName}/${symbol}: ${isLong ? '平多' : '平空'} ${amount}`);\\n        \\n        const orderId = EX.CreateOrder(symbol, side, -1, amount);\\n        \\n        if (orderId) {\\n            Log(`   ✅ 平仓成功 OrderID=${orderId}`);\\n            return { success: true, orderId };\\n        } else {\\n            Log(`   ❌ 平仓失败:下单返回空`);\\n            return { success: false, error: '下单返回空' };\\n        }\\n        \\n    } catch (e) {\\n        Log(`   ❌ 平仓异常: ${e.message}`);\\n        return { success: false, error: e.message };\\n    }\\n}\\n\\nfunction closeArbitragePosition(posInfo) {\\n    const { baseAsset, reason, longExchange, shortExchange, longPosition, shortPosition } = posInfo;\\n    \\n    Log(`\\\\n🔻 平仓 ${baseAsset}: ${reason}`);\\n    \\n    const results = {\\n        baseAsset,\\n        reason,\\n        longClose: null,\\n        shortClose: null,\\n        totalProfit: 0\\n    };\\n    \\n    // 平多仓\\n    if (longPosition) {\\n        Log(`   多仓 @${longExchange}: ${longPosition.symbol} x${longPosition.amount} 盈亏:${longPosition.profit.toFixed(2)}`);\\n        results.longClose = executeClose(\\n            longPosition.exchangeIndex,\\n            longPosition.symbol,\\n            true,\\n            longPosition.amount,\\n            longExchange\\n        );\\n        results.totalProfit += longPosition.profit;\\n        Sleep(500);\\n    }\\n    \\n    // 平空仓\\n    if (shortPosition) {\\n        Log(`   空仓 @${shortExchange}: ${shortPosition.symbol} x${shortPosition.amount} 盈亏:${shortPosition.profit.toFixed(2)}`);\\n        results.shortClose = executeClose(\\n            shortPosition.exchangeIndex,\\n            shortPosition.symbol,\\n            false,\\n            shortPosition.amount,\\n            shortExchange\\n        );\\n        results.totalProfit += shortPosition.profit;\\n    }\\n    \\n    // 清除保存的持仓记录\\n    const positionKey = `arb_${baseAsset}`;\\n    _G(positionKey, null);\\n    \\n    const longOK = !longPosition || results.longClose?.success;\\n    const shortOK = !shortPosition || results.shortClose?.success;\\n    \\n    if (longOK && shortOK) {\\n        Log(`   ✅ ${baseAsset} 平仓完成,总盈亏: ${results.totalProfit.toFixed(2)} USDT`);\\n    } else {\\n        Log(`   ⚠️ ${baseAsset} 部分平仓失败,请检查!`);\\n    }\\n    \\n    return results;\\n}\\n\\n// ========== 主逻辑 ==========\\n\\n// 1. 扫描当前所有仓位\\nconst currentPositions = scanAllPositions();\\n\\nLog(`\\\\n📋 当前持仓:`);\\nif (Object.keys(currentPositions).length === 0) {\\n    Log(`   无持仓`);\\n} else {\\n    for (const [asset, info] of Object.entries(currentPositions)) {\\n        const longInfo = info.longPosition \\n            ? `Long@${info.longExchange}(${info.longPosition.amount.toFixed(4)}) P/L:${info.longPosition.profit.toFixed(2)}` \\n            : '';\\n        const shortInfo = info.shortPosition \\n            ? `Short@${info.shortExchange}(${info.shortPosition.amount.toFixed(4)}) P/L:${info.shortPosition.profit.toFixed(2)}` \\n            : '';\\n        const separator = longInfo && shortInfo ? ' | ' : '';\\n        Log(`   ${asset}: ${longInfo}${separator}${shortInfo}`);\\n    }\\n}\\n\\n// 2. 找出需要平仓的币种\\nconst positionsToClose = findPositionsToClose(currentPositions, fundingData);\\n\\nLog(`\\\\n🎯 需要平仓: ${positionsToClose.length} 个`);\\n\\n// 3. 执行平仓\\nconst closeResults = [];\\nlet totalClosedProfit = 0;\\n\\nif (positionsToClose.length > 0) {\\n    for (const pos of positionsToClose) {\\n        const result = closeArbitragePosition(pos);\\n        closeResults.push(result);\\n        totalClosedProfit += result.totalProfit;\\n        Sleep(1000);\\n    }\\n    \\n    Log(`\\\\n${'='.repeat(60)}`);\\n    Log(`📊 平仓完成: ${closeResults.length} 个,总盈亏: ${totalClosedProfit.toFixed(2)} USDT`);\\n} else {\\n    Log(`   无需平仓`);\\n}\\n\\nLog(`${'='.repeat(60)}\\\\n`);\\n\\n// ✅ 修复:返回正确格式给下游节点\\nreturn {\\n    // 传递给费率数据验证的格式\\n    data: fundingData,\\n    // 原始统计数据\\n    analysisResult: analysisResult,\\n    // 平仓信息\\n    closeInfo: {\\n        currentPositions,\\n        positionsToClose,\\n        closeResults,\\n        totalClosedProfit,\\n        timestamp: Date.now()\\n    }\\n};\\n\",\"notice\":\"\"},\"id\":\"8ffa6584-ac69-4712-9874-71e63376effc\",\"name\":\"平仓检测\",\"position\":[-672,352],\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 节点名称:费率数据保存\\n// 获取筛选后的数据\\nconst inputData = $input.all();\\nconst currentData = inputData[0].json.data || {};\\n\\n// 获取已保存的历史数据\\nlet savedData = _G('savefeedata') || [];\\n\\nconst timestamp = Date.now();\\n\\n// 保存当前数据快照\\nif (Object.keys(currentData).length > 0) {\\n    savedData.push({\\n        timestamp: timestamp,\\n        data: currentData\\n    });\\n    \\n    // 只保留最近4小时的数据\\n    const fourHoursAgo = timestamp - (4 * 60 * 60 * 1000);\\n    savedData = savedData.filter(item => item.timestamp > fourHoursAgo);\\n    \\n    _G('savefeedata', savedData);\\n    \\n    Log(`✅ 费率数据已保存: ${Object.keys(currentData).length} 个币种,历史: ${savedData.length} 条`);\\n} else {\\n    Log(`⚠️ 当前无费率数据`);\\n}\\n\\nreturn { data: currentData };\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-448,816],\"id\":\"379d9b67-5300-4c7c-a442-e0cae8602365\",\"name\":\"费率数据保存\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 节点名称:费率统计分析\\n\\n// ========== 读取保存的费率数据 ==========\\nconst savedData = _G('savefeedata') || [];\\n\\nLog(`\\\\n${'='.repeat(60)}`);\\nLog(`📊 费率统计分析 | ${new Date().toISOString()}`);\\nLog(`📋 历史数据条数: ${savedData.length}`);\\n\\nif (savedData.length === 0) {\\n    Log(`⚠️ 无历史费率数据`);\\n    return { \\n        high: [],\\n        medium: [],\\n        low: [],\\n        hasData: false\\n    };\\n}\\n\\n// ========== 统计各币种在不同置信度中的出现频率 ==========\\nconst stats = {};\\n\\nfor (const snapshot of savedData) {\\n    const data = snapshot.data || {};\\n    \\n    for (const [baseAsset, info] of Object.entries(data)) {\\n        const confidence = (info.confidence || '').toLowerCase();\\n        \\n        if (!stats[baseAsset]) {\\n            stats[baseAsset] = {\\n                high: 0,\\n                medium: 0,\\n                low: 0,\\n                total: 0,\\n                spread: info.spread || 0,\\n                apr: info.estimatedApr || 0,\\n                longEx: info.longExchange || '',\\n                shortEx: info.shortExchange || ''\\n            };\\n        }\\n        \\n        stats[baseAsset].total++;\\n        if (confidence === 'high') stats[baseAsset].high++;\\n        else if (confidence === 'medium') stats[baseAsset].medium++;\\n        else if (confidence === 'low') stats[baseAsset].low++;\\n        \\n        // 更新最新值\\n        stats[baseAsset].spread = info.spread || stats[baseAsset].spread;\\n        stats[baseAsset].apr = info.estimatedApr || stats[baseAsset].apr;\\n        stats[baseAsset].longEx = info.longExchange || stats[baseAsset].longEx;\\n        stats[baseAsset].shortEx = info.shortExchange || stats[baseAsset].shortEx;\\n    }\\n}\\n\\n// ========== 按主要置信度分类 ==========\\nconst high = [];\\nconst medium = [];\\nconst low = [];\\nconst totalSnapshots = savedData.length;\\n\\nfor (const [baseAsset, s] of Object.entries(stats)) {\\n    const coin = {\\n        baseAsset,\\n        symbol: `${baseAsset}-PERP`,\\n        highCount: s.high,\\n        mediumCount: s.medium,\\n        lowCount: s.low,\\n        total: s.total,\\n        spread: s.spread,\\n        estimatedApr: s.apr,\\n        longExchange: s.longEx,\\n        shortExchange: s.shortEx,\\n        // 加权分数\\n        score: (s.high * 3 + s.medium * 2 + s.low) / s.total\\n    };\\n    \\n    // 按出现最多的置信度分类\\n    if (s.high >= s.medium && s.high >= s.low) {\\n        high.push(coin);\\n    } else if (s.medium >= s.high && s.medium >= s.low) {\\n        medium.push(coin);\\n    } else {\\n        low.push(coin);\\n    }\\n}\\n\\n// 按分数排序\\nhigh.sort((a, b) => b.score - a.score);\\nmedium.sort((a, b) => b.score - a.score);\\nlow.sort((a, b) => b.score - a.score);\\n\\n// ========== 输出日志 ==========\\nLog(`\\\\n📈 High (${high.length}个): ${high.map(c => c.baseAsset).join(', ')}`);\\nLog(`📊 Medium (${medium.length}个): ${medium.map(c => c.baseAsset).join(', ')}`);\\nLog(`📉 Low (${low.length}个): ${low.map(c => c.baseAsset).join(', ')}`);\\n\\n// ========== 清空保存的费率数据 ==========\\n_G('savefeedata', null);\\nLog(`\\\\n🗑️ 历史费率数据已清空`);\\nLog(`${'='.repeat(60)}\\\\n`);\\n\\n// ========== 返回三个数组 ==========\\nreturn {\\n    high: high,\\n    medium: medium,\\n    low: low,\\n    totalSnapshots: totalSnapshots,\\n    hasData: true\\n};\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-896,352],\"id\":\"3f320db6-6233-42d3-8f21-2ce2ffcd8668\",\"name\":\"费率数据分析\"},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"hours\",\"hoursInterval\":4,\"triggerAtMinute\":0}]}},\"id\":\"0f08d586-2fcd-4a01-9d88-2564fd317dcc\",\"logLevel\":0,\"name\":\"交易执行触发器\",\"notesInFlow\":false,\"position\":[-1344,352],\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":1}]}},\"id\":\"8da99e23-df6d-419d-a625-0063b117c53a\",\"logLevel\":0,\"name\":\"费率采集触发器\",\"position\":[-1344,816],\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2}],\"pinData\":{},\"connections\":{\"交易所设置\":{\"main\":[[{\"node\":\"费率数据分析\",\"type\":\"main\",\"index\":0}]]},\"费率数据验证\":{\"main\":[[{\"node\":\"条件判断\",\"type\":\"main\",\"index\":0}]]},\"费率数据筛选\":{\"main\":[[{\"node\":\"费率数据保存\",\"type\":\"main\",\"index\":0}]]},\"费率数据请求\":{\"main\":[[{\"node\":\"费率数据筛选\",\"type\":\"main\",\"index\":0}]]},\"套利机会请求\":{\"main\":[[{\"node\":\"费率数据请求\",\"type\":\"main\",\"index\":0}]]},\"AI 智能体\":{\"main\":[[{\"node\":\"开仓执行\",\"type\":\"main\",\"index\":0}]]},\"OpenAI 模型\":{\"ai_languageModel\":[[{\"node\":\"AI 智能体\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"条件判断\":{\"main\":[[{\"node\":\"日志输出\",\"type\":\"main\",\"index\":0}],[{\"node\":\"AI 智能体\",\"type\":\"main\",\"index\":0}]]},\"平仓检测\":{\"main\":[[{\"node\":\"费率数据验证\",\"type\":\"main\",\"index\":0}]]},\"费率数据保存\":{\"main\":[[{\"node\":\"可视化展示\",\"type\":\"main\",\"index\":0}]]},\"费率数据分析\":{\"main\":[[{\"node\":\"平仓检测\",\"type\":\"main\",\"index\":0}]]},\"交易执行触发器\":{\"main\":[[{\"node\":\"交易所设置\",\"type\":\"main\",\"index\":0}]]},\"费率采集触发器\":{\"main\":[[{\"node\":\"套利机会请求\",\"type\":\"main\",\"index\":0}]]}},\"active\":false,\"settings\":{\"timezone\":\"Asia/Shanghai\",\"executionOrder\":\"v1\"},\"tags\":[],\"meta\":{\"templateCredsSetupCompleted\":true},\"credentials\":{},\"id\":\"6343bf37-f19f-4883-89d7-e74f90d5b01e\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"交易执行触发器\"}}"}