策略源码
{"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\":\"交易执行触发器\"}}"}