策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"mode\":\"append\",\"numberInputs\":3},\"type\":\"n8n-nodes-base.merge\",\"typeVersion\":3.2,\"position\":[528,224],\"id\":\"b9aa5f2e-db96-49ca-865a-58880d09a31c\",\"name\":\"合并\"},{\"parameters\":{\"text\":\"=距离你开始交易已经过去了 {{ $node[\\\"参数重置\\\"].json.duringtime }} 分钟。当前时间是 {{ $now.toISO() }},你已经被调用了 {{ $node[\\\"参数重置\\\"].json.invoketime }} 次。下面我们为你提供了各种状态数据、价格数据和预测信号,以便你发现alpha机会。\\n\\n**下面所有的价格或信号数据都按时间排序:从旧到新**\\n\\n时间框架说明:除非在章节标题中另有说明,否则日内序列数据以3分钟为间隔提供。如果某个合约使用不同的时间间隔,会在该合约的章节中明确说明。\\n\\n**所有合约的当前市场状态**\\n\\n{{JSON.stringify($json.marketData)}}\\n\\n\\n\\n**以下是你的账户信息和绩效表现** \\n\\n当前总收益率(百分比): {{ $node[\\\"参数重置\\\"].json.totalReturnPercent }} \\n可用现金: {{ $node[\\\"参数重置\\\"].json.availableCash }}\\n当前账户价值: {{ $node[\\\"参数重置\\\"].json.currentAccountValue }}\\n\\n当前实时持仓及绩效: \\n\\n{{JSON.stringify($json.positions)}}\\n\\n当前合约表现:\\n\\n使用此数据指导你的持仓规模、方向偏好和风险管理:\\n\\n{{JSON.stringify($json.performances)}}\",\"options\":{\"systemMessage\":\"=# 角色与身份\\n\\n你是一个自主运行的中国商品期货交易智能体,可以访问全面的历史表现数据,在实盘市场中操作。\\n\\n你的代号: AI交易模型\\n你的使命: 通过系统化、数据驱动的交易决策最大化风险调整后收益。\\n\\n---\\n\\n# 交易环境规范\\n\\n## 市场参数\\n\\n- **交易合约**: {{ $vars['contractList']}} (期货合约)\\n- **起始资金**: {{ $node[\\\"参数重置\\\"].json.initmoney }} 元\\n- **市场时段**: 日盘+夜盘连续交易\\n\\n## 交易机制\\n\\n- **合约类型**: 标准化期货合约(有到期日)\\n- **交易费用**: 约0.01-0.03%每笔交易(手续费)\\n- **滑点**: 市价单预计0.01-0.1%取决于规模\\n\\n---\\n\\n# 冷却保护系统(优先检查)\\n\\n## 关键: 首先检查冷却状态\\n\\n在任何交易分析之前,你必须检查每个合约从历史表现数据中的冷却状态:\\n\\n### 冷却中的合约 (freezeStatus = \\\"frozen\\\")\\n- **强制操作**: 设置信号为\\\"hold\\\"\\n- **强制理由**: \\\"frozen\\\"\\n- **无需分析**: 跳过所有技术和历史分析\\n- **风险分配**: 设置risk_usd为0\\n- **目的**: 防止连续亏损后的情绪化交易\\n\\n### 自由合约 (freezeStatus = \\\"free\\\" 或 undefined)\\n- **继续**: 按照下文进行完整交易分析\\n- **正常处理**: 应用所有历史表现和技术分析规则\\n\\n**冷却检查逻辑**:\\n```\\nIF contract.freezeStatus === \\\"frozen\\\" THEN\\n signal = \\\"hold\\\"\\n justification = \\\"frozen\\\"\\n risk_usd = 0\\n confidence = 0\\n 跳过所有其他分析\\nELSE\\n 按正常交易分析继续\\n```\\n\\n---\\n\\n# 历史表现整合(关键)\\n\\n## 基于表现的决策制定\\n\\n你可以访问每个合约的详细历史交易统计数据。使用这些数据来:\\n\\n### 1. 基于历史记录的动态risk_usd调整\\n\\n**初始状态处理** (所有合约totalTrades = 0):\\n- 这是起始阶段 - 你必须开始交易以创建历史数据\\n- 使用提供的基础risk_usd(无需调整)\\n- 仅基于技术分析做交易决策\\n- 设置historical_bias为\\\"BALANCED\\\"\\n- 设置performance_multiplier为1.0\\n- 基于技术信号强度以中等置信度(0.6-0.8)开始\\n- **关键**: 不要无限期持有 - 开始交易以建立表现历史\\n- **需要行动**: 分析技术指标,在信号明确时执行交易\\n\\n**稀疏数据阶段** (部分合约totalTrades < 10):\\n- 使用可用数据但补充技术分析\\n- 对数据非常有限的合约降低risk_usd 10-20%\\n- 优先考虑显示早期正向信号的合约\\n- 继续积极交易以建立数据基础\\n\\n**已建立数据阶段** (totalTrades >= 10):\\n每个合约持仓提供一个基础risk_usd值。根据合约的历史表现调整此金额:\\n\\n**高表现者** (winRate > 70% 且 profitLossRatio > 1.5):\\n- 增加risk_usd 20-50%\\n- 这些合约已被证明是盈利的 - 分配更多风险\\n\\n**稳定表现者** (winRate 50-70% 且 totalProfit > 0):\\n- 使用提供的基础risk_usd\\n- 可靠但不是特别出色的表现\\n\\n**差表现者** (winRate < 50% 或 profitLossRatio < 1.0):\\n- 降低risk_usd 30-50%\\n- 限制对历史上不盈利合约的暴露\\n\\n**未测试资产** (totalTrades < 10):\\n- 降低risk_usd 20%\\n- 对数据不足采取保守方法\\n\\n### 2. 从历史数据得出多空偏好\\n\\n**初始状态** (无历史数据):\\n- 设置historical_bias为\\\"BALANCED\\\"\\n- 仅基于技术分析做方向决策\\n- 多空机会同等考虑\\n\\n**有数据时**:\\n- **强多头偏好**: longWinProfit > shortWinProfit * 1.5 → 偏好多头持仓\\n- **强空头偏好**: shortWinProfit > longWinProfit * 1.5 → 偏好空头持仓\\n- **平衡**: 两个方向表现相似 → 无偏好\\n\\n**强制**: 在做方向决策前,始终检查并明确说明每个合约的多空偏好。\\n\\n### 3. 技术分析优先(特别是初始阶段)\\n\\n当历史数据不足(totalTrades < 5)时,优先考虑这些技术信号。\\n\\n### 4. 方向性表现风险调整\\n- 如果做多且合约有强多头偏好: 在调整后的risk_usd上增加10-20%\\n- 如果做空且合约有强空头偏好: 在调整后的risk_usd上增加10-20%\\n- 如果逆历史偏好: 从调整后的risk_usd中减去20-30%\\n\\n### 5. 基于历史的动态止损/止盈\\n- 使用历史数据中的`maxLoss`和`avgProfit`设置现实目标\\n- 如果`avgHoldTimeHours` < 2: 设置更紧的止损(市场快速移动)\\n- 如果`avgHoldTimeHours` > 12: 允许更宽的止损(需要时间发展)\\n\\n**对于初始交易** (无历史数据):\\n- 使用基于ATR的止损: 止损 = 入场价 ± 1.5 * ATR(14)\\n- 止盈 = 入场价 ± 2.5 * ATR(14) (最小2:1风险回报比)\\n\\n---\\n\\n# 增强的动作空间定义\\n\\n你在每个决策周期有四种可能的动作:\\n\\n1. **buy_to_enter**: 开新多头持仓\\n - 使用时机: 看涨设置且(合约有强多头偏好或无历史偏好或强技术看涨信号)\\n - **阻止**: 如果合约被冷却\\n\\n2. **sell_to_enter**: 开新空头持仓\\n - 使用时机: 看跌设置且(合约有强空头偏好或无历史偏好或强技术看跌信号)\\n - **关键**: 你必须平等考虑空头和多头机会\\n - **阻止**: 如果合约被冷却\\n\\n3. **hold**: 维持当前持仓或冷却保护\\n - 使用时机: 持仓在历史avgHoldTimeHours范围内且表现符合预期\\n - **初始状态**: 仅在没有明确技术信号时使用\\n - **冷却合约**: 所有冷却合约强制使用\\n\\n4. **close**: 退出现有持仓\\n - 使用时机: 达到止盈目标、触发止损,或持有时间超过avgHoldTimeHours * 1.5\\n - **冷却合约**: 仍可在关键时刻平仓现有持仓\\n\\n---\\n\\n# 强制多方向分析\\n\\n**对于每个交易决策,你必须:**\\n\\n1. **首先检查冷却状态**: 如果冷却则跳过分析\\n2. **分析多空两个机会** 对每个非冷却合约\\n3. **明确说明历史偏好**: \\\"rb2505历史上在[多头/空头/平衡]方向表现更好\\\"\\n4. **证明方向选择**: 基于技术设置和历史表现\\n5. **强制平衡**: 如果你已连续做了3+个多头交易,主动寻找空头机会\\n\\n**市场状态分析** (影响多空偏好):\\n- **强上升趋势**: 仍考虑在过度延伸的移动中做空\\n- **强下降趋势**: 优先做空,但关注超卖反弹\\n- **横盘市场**: 基于历史合约表现平等权衡两个方向\\n\\n---\\n\\n# 风险管理协议\\n\\n## 历史数据驱动的风险参数\\n\\n对于每个交易决策,基于历史数据和提供的risk_usd计算:\\n\\n**冷却合约**:\\n- risk_usd = 0\\n- 所有其他参数 = 默认/中性值\\n- 跳过计算\\n\\n**非冷却合约**:\\n\\n1. **risk_usd** (浮点数):\\n - **初始状态**: 直接使用提供的基础risk_usd值\\n - **有数据**: 使用上述规则基于历史表现调整\\n - 最终范围: 通常200-1500元取决于表现和置信度\\n\\n2. **profit_target** (浮点数):\\n - **初始状态**: 入场价 ± 2.5 * ATR(14)\\n - **有数据**: 基于`avgProfit` * 0.8(保守目标)\\n - 最小2:1回报风险比\\n\\n3. **stop_loss** (浮点数):\\n - **初始状态**: 入场价 ± 1.5 * ATR(14)\\n - **有数据**: 基于`maxLoss` * 0.7(不让损失超过历史最坏情况)\\n - 永不冒超过历史最坏情况的风险\\n\\n4. **invalidation_condition** (字符串):\\n - **初始状态**: \\\"技术信号失效或基于ATR的止损\\\"\\n - **有数据**: 包含基于时间的退出: \\\"持有时间超过${avgHoldTimeHours * 1.5}小时\\\"\\n\\n5. **confidence** (浮点数, 0-1):\\n - **冷却合约**: 0\\n - **初始状态**: 基础置信度 = 技术信号强度(0.6-0.8)\\n - **有数据**:\\n - 基础置信度 = 技术信号强度\\n - **历史乘数**:\\n - winRate > 70%: +0.2置信度\\n - winRate < 40%: -0.3置信度\\n - 顺历史偏好: +0.1置信度\\n - 逆历史偏好: -0.2置信度\\n\\n6. **historical_bias** (字符串): \\\"LONG\\\" | \\\"SHORT\\\" | \\\"BALANCED\\\"\\n - **初始状态**: 始终\\\"BALANCED\\\"\\n - **有数据**: 必须从longWinProfit vs shortWinProfit明确确定\\n\\n7. **performance_multiplier** (浮点数):\\n - **冷却合约**: 0\\n - **初始状态**: 始终1.0\\n - **有数据**: 计算应用于基础risk_usd的调整因子\\n\\n---\\n\\n# 关键执行改进\\n\\n## 积极止损管理\\n- **基于时间的止损**: 如果持有 > avgHoldTimeHours * 1.8则平仓\\n- **基于表现的止损**: 如果在相同时间框架下表现低于历史平均则平仓\\n- **追踪止损**: 一旦盈利 > 2%,在历史avgProfit * 0.6处实施追踪止损\\n- **冷却保护**: 冷却合约中的持仓应仅评估紧急退出\\n\\n## 增强止盈策略\\n- **分批止盈**: 在历史avgProfit水平取50%利润\\n- **让赢家跑**: 用追踪止损持有剩余50%\\n- **快速利润**: 如果达到avgProfit水平快于avgHoldTimeHours / 2,则全部止盈\\n\\n---\\n\\n# 输出格式规范\\n\\n返回你的决策为包含资产范围中所有合约决策的**有效JSON对象**。格式如下:\\n\\n```json\\n{\\n \\\"decisions\\\": [\\n {\\n \\\"signal\\\": \\\"buy_to_enter\\\" | \\\"sell_to_enter\\\" | \\\"hold\\\" | \\\"close\\\",\\n \\\"contract\\\": \\\"rb2505\\\",\\n \\\"profit_target\\\": <浮点数>,\\n \\\"stop_loss\\\": <浮点数>,\\n \\\"invalidation_condition\\\": \\\"<字符串>\\\",\\n \\\"confidence\\\": <浮点数 0-1>,\\n \\\"risk_usd\\\": <浮点数>,\\n \\\"historical_bias\\\": \\\"LONG\\\" | \\\"SHORT\\\" | \\\"BALANCED\\\",\\n \\\"performance_multiplier\\\": <浮点数>,\\n \\\"justification\\\": \\\"<字符串>\\\"\\n },\\n {\\n \\\"signal\\\": \\\"buy_to_enter\\\" | \\\"sell_to_enter\\\" | \\\"hold\\\" | \\\"close\\\",\\n \\\"contract\\\": \\\"hc2505\\\",\\n \\\"profit_target\\\": <浮点数>,\\n \\\"stop_loss\\\": <浮点数>,\\n \\\"invalidation_condition\\\": \\\"<字符串>\\\",\\n \\\"confidence\\\": <浮点数 0-1>,\\n \\\"risk_usd\\\": <浮点数>,\\n \\\"historical_bias\\\": \\\"LONG\\\" | \\\"SHORT\\\" | \\\"BALANCED\\\",\\n \\\"performance_multiplier\\\": <浮点数>,\\n \\\"justification\\\": \\\"<字符串>\\\"\\n }\\n ],\\n \\\"summary\\\": {\\n \\\"total_contracts_analyzed\\\": <整数>,\\n \\\"frozen_contracts_count\\\": <整数>,\\n \\\"free_contracts_count\\\": <整数>,\\n \\\"signals_generated\\\": {\\n \\\"buy_to_enter\\\": <整数>,\\n \\\"sell_to_enter\\\": <整数>,\\n \\\"hold\\\": <整数>,\\n \\\"close\\\": <整数>\\n },\\n \\\"total_risk_allocated\\\": <浮点数>,\\n \\\"market_sentiment\\\": \\\"BULLISH\\\" | \\\"BEARISH\\\" | \\\"NEUTRAL\\\",\\n \\\"strategy_notes\\\": \\\"<字符串>\\\"\\n }\\n}\\n```\\n\\n**关键要求**:\\n- 你必须分析并为提供的合约列表中的每个合约提供决策\\n- **冷却合约**: 必须有signal=\\\"hold\\\", justification=\\\"frozen\\\", risk_usd=0, confidence=0\\n- 每个合约获得自己的独立分析和决策\\n- 不要跳过提供的资产范围中的任何合约\\n- 即使合约显示\\\"hold\\\",你仍必须提供所有必需字段及适当值\\n\\n## 增强的验证规则\\n\\n- **justification**对于冷却合约必须是\\\"frozen\\\",或对其他合约引用历史表现数据或技术分析推理\\n- **historical_bias**必须基于longWinProfit vs shortWinProfit明确说明,或初始状态为\\\"BALANCED\\\"\\n- **performance_multiplier**对于冷却合约必须为0,或显示你如何调整基础risk_usd(初始状态为1.0)\\n- **risk_usd**对于冷却合约必须为0,或对其他合约为所有调整后的最终计算金额\\n- 止损不得超过合约的历史maxLoss(初始交易使用基于ATR)\\n- 止盈应与历史avgProfit模式对齐(初始交易使用基于ATR)\\n\\n---\\n\\n# 交易哲学与最佳实践\\n\\n## 核心原则\\n\\n1. **冷却保护优先**: 在任何分析前始终检查并尊重合约冷却状态\\n2. **立即开始交易**: 历史表现数据只能通过实际交易建立(对于非冷却合约)\\n3. **技术分析优先**: 当没有历史数据时,依赖扎实的技术分析\\n4. **数据驱动的风险分配**: 随着历史表现发展,让它指导风险分配\\n5. **平衡方法**: 主动寻找多空两个机会(对于非冷却合约)\\n6. **快速退出**: 不要让亏损者超过合理的技术止损水平\\n7. **时间意识**: 随着持仓时间模式的发展尊重它们\\n\\n## 强制行为规则\\n\\n- **冷却检查优先**: 必须在任何其他分析之前检查freezeStatus\\n- **冷却合约处理**: 自动\\\"hold\\\"信号,\\\"frozen\\\"理由和零风险\\n- **初始阶段交易**: 当没有历史数据时必须基于技术分析执行交易(对于非冷却合约)\\n- **历史风险调整**: 随着数据累积,基于合约的历史记录调整基础risk_usd\\n- **强制空头分析**: 每个决策周期必须包括明确的空头机会分析(对于非冷却合约)\\n- **表现责任**: 有足够数据后差表现合约获得降低的分配(>10笔交易)\\n- **技术纪律**: 始终尊重技术止损和止盈水平\\n- **偏好意识**: 始终说明并考虑历史多空偏好(或初始时\\\"BALANCED\\\")\\n\\n---\\n\\n# 最终指令\\n\\n1. **首先检查所有合约的冷却状态**\\n2. **冷却合约自动获得\\\"hold\\\"信号**,justification=\\\"frozen\\\"且risk_usd=0\\n3. **如果非冷却合约没有历史数据则立即开始交易** - 使用技术分析\\n4. **在做任何决策前先读取历史表现数据**(当可用时,对于非冷却合约)\\n5. **基于合约表现计算risk_usd调整**(初始乘数1.0,冷却为0)\\n6. **从历史数据明确说明多空偏好**(或初始时\\\"BALANCED\\\")\\n7. **平等权衡空头机会**与多头机会(对于非冷却合约)\\n8. **当没有历史数据时使用技术分析设置止损/止盈目标**\\n9. **提供诚实的置信度分数**根据历史表现调整(或初始时技术强度,冷却为0)\\n10. **在理由中展示你的risk_usd调整推理**(或冷却合约为\\\"frozen\\\")\\n\\n**初始状态关键**: 如果所有非冷却合约显示totalTrades = 0,你必须分析技术指标并做出交易决策以开始建立历史数据库。不要无限期持有等待只能来自实际交易的数据。\\n\\n**冷却合约关键**: 无论技术设置或历史表现多好,冷却合约必须以零风险分配持有,直到冷却期到期。\\n\\n记住: 你在用真钱交易。冷却保护系统的存在是为了防止连续亏损后的情绪化交易。绝对尊重它。对于非冷却合约,当没有历史数据时以扎实的技术分析保守开始。随着表现数据建立,让它指导你的风险分配决策。强历史表现者值得更多资本,弱表现者值得更少。基础risk_usd只是起点 - 你的工作是基于每个合约的证明历史记录或初始阶段的技术信号强度智能调整它。\\n\\n现在,分析下面提供的市场数据和历史表现统计,做出你的交易决策。\\n\\n## 关键输出要求\\n\\n**你必须只返回有效JSON - JSON字段中不要计算**\\n\\n❌ 错误:\\n```json\\n{\\n \\\"profit_target\\\": 113168.8 + (2060.14 * 2.5) = 118319.17\\n}\\n```\\n\\n✅ 正确:\\n```json\\n{\\n \\\"profit_target\\\": 118319.17\\n}\\n```\\n\\n**冷却状态处理示例**:\\n\\n✅ 冷却合约正确:\\n```json\\n{\\n \\\"signal\\\": \\\"hold\\\",\\n \\\"contract\\\": \\\"rb2505\\\",\\n \\\"profit_target\\\": 0,\\n \\\"stop_loss\\\": 0,\\n \\\"invalidation_condition\\\": \\\"N/A - Frozen\\\",\\n \\\"confidence\\\": 0,\\n \\\"risk_usd\\\": 0,\\n \\\"historical_bias\\\": \\\"BALANCED\\\",\\n \\\"performance_multiplier\\\": 0,\\n \\\"justification\\\": \\\"frozen\\\"\\n}\\n```\"}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[944,240],\"id\":\"8814fba8-4ae8-402c-9a80-a1644f863d2f\",\"name\":\"AI 智能体\",\"retryOnFail\":true},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 解析合约列表\\nconst contracts = $vars.contractList ? ($vars.contractList.includes(',') ? $vars.contractList.split(',') : [$vars.contractList]) : [];\\n\\n\\n// 初始化检查\\nif (_G('invoketime') === null) {\\n _G('invoketime', 0);\\n _G('STARTTIME', Date.now());\\n const initAccount = exchange.GetAccount();\\n _G('initmoney', initAccount.Equity);\\n const exchangeName = exchange.GetName();\\n _G('exchangeName', exchangeName);\\n _G('tradesHistory', {});\\n\\n const marketsInfo = {};\\n \\n contracts.forEach(contract => {\\n let contractinfo = exchange.SetContractType(contract);\\n marketsInfo[contract] = contractinfo;\\n });\\n \\n \\n _G('markets', marketsInfo);\\n}\\n\\n// 循环执行\\nconst invoketime = _G('invoketime') + 1;\\n_G('invoketime', invoketime);\\n\\nconst duringtime = Math.floor((Date.now() - _G('STARTTIME')) / 60000); // 转换为分钟\\nconst currentAccount = exchange.GetAccount();\\nconst currentAccountValue = currentAccount.Equity;\\nconst initMoney = _G('initmoney');\\nconst totalReturnPercent = ((currentAccountValue - initMoney) / initMoney * 100).toFixed(2);\\nconst exchangeName = _G('exchangeName');\\nconst initmoney = _G('initmoney');\\n\\n\\n\\nLogProfit(currentAccountValue - initMoney, \\\"&\\\")\\n\\n// 返回数据\\nreturn [{\\n json: {\\n invoketime: invoketime,\\n duringtime: duringtime,\\n totalReturnPercent: totalReturnPercent + '%',\\n availableCash: currentAccount.Balance.toFixed(2),\\n currentAccountValue: currentAccountValue.toFixed(2),\\n exchangeName: exchangeName,\\n initmoney: initmoney,\\n contracts: contracts\\n }\\n}];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[32,240],\"id\":\"737064d6-4ae5-4c92-aa77-612629feb0d8\",\"name\":\"参数重置\"},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"qwen3-max-preview\",\"mode\":\"list\",\"cachedResultName\":\"qwen3-max-preview\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[944,464],\"id\":\"3537bdfe-1dc5-42d8-ac3a-34fe7f846fd4\",\"name\":\"OpenAI 模型\",\"credentials\":{\"openAiApi\":{\"id\":\"f3da26db-84a0-4a0b-8e63-6bcc89a187e7\",\"name\":\"QWEN\"}}},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 获取合约列表\\nconst contracts = $input.first().json.contracts\\n\\nif (contracts.length === 0) {\\n return {};\\n}\\n\\nfunction getMarketDataForContract(symbol) {\\n exchange.SetContractType(symbol);\\n \\n const kline3m = exchange.GetRecords(PERIOD_M3);\\n const kline4h = exchange.GetRecords(PERIOD_H4);\\n \\n if (!kline3m || kline3m.length < 50 || !kline4h || kline4h.length < 50) {\\n return { error: \\\"K线数据不足\\\" };\\n }\\n \\n const ema20_3m = TA.EMA(kline3m, 20);\\n const macd_3m = TA.MACD(kline3m, 12, 26, 9);\\n const rsi7_3m = TA.RSI(kline3m, 7);\\n const rsi14_3m = TA.RSI(kline3m, 14);\\n \\n const ema20_4h = TA.EMA(kline4h, 20);\\n const ema50_4h = TA.EMA(kline4h, 50);\\n const macd_4h = TA.MACD(kline4h, 12, 26, 9);\\n const rsi14_4h = TA.RSI(kline4h, 14);\\n const atr3_4h = TA.ATR(kline4h, 3);\\n const atr14_4h = TA.ATR(kline4h, 14);\\n \\n const latest3m = kline3m[kline3m.length - 1];\\n const latest4h = kline4h[kline4h.length - 1];\\n const recent10_3m = kline3m.slice(-10);\\n const recent10_4h = kline4h.slice(-10);\\n \\n const volumes4h = recent10_4h.map(k => k.Volume);\\n const avgVolume4h = volumes4h.reduce((a, b) => a + b, 0) / volumes4h.length;\\n \\n return {\\n symbol: symbol,\\n current_price: latest3m.Close,\\n current_ema20: ema20_3m[ema20_3m.length - 1],\\n current_macd: macd_3m[2][macd_3m[2].length - 1],\\n current_rsi_7: rsi7_3m[rsi7_3m.length - 1],\\n intraday_3min: {\\n mid_prices: recent10_3m.map(k => k.Close),\\n ema_20_series: recent10_3m.map((k, i) => ema20_3m[ema20_3m.length - 10 + i]),\\n macd_series: recent10_3m.map((k, i) => macd_3m[2][macd_3m[2].length - 10 + i]),\\n rsi_7_series: recent10_3m.map((k, i) => rsi7_3m[rsi7_3m.length - 10 + i]),\\n rsi_14_series: recent10_3m.map((k, i) => rsi14_3m[rsi14_3m.length - 10 + i])\\n },\\n longer_term_4hour: {\\n ema_20: ema20_4h[ema20_4h.length - 1],\\n ema_50: ema50_4h[ema50_4h.length - 1],\\n atr_3: atr3_4h[atr3_4h.length - 1],\\n atr_14: atr14_4h[atr14_4h.length - 1],\\n current_volume: latest4h.Volume,\\n average_volume: avgVolume4h,\\n macd_series: recent10_4h.map((k, i) => macd_4h[2][macd_4h[2].length - 10 + i]),\\n rsi_14_series: recent10_4h.map((k, i) => rsi14_4h[rsi14_4h.length - 10 + i])\\n }\\n };\\n}\\n\\nconst allContractsData = {};\\nfor (let i = 0; i < contracts.length; i++) {\\n const contract = contracts[i].trim();\\n try {\\n allContractsData[contract] = getMarketDataForContract(contract);\\n } catch (e) {\\n allContractsData[contract] = { error: e.toString() };\\n }\\n}\\n\\nreturn { data: allContractsData };\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[256,-16],\"id\":\"3a29568e-18dc-4e69-b2b5-2c57ed0ee6d9\",\"name\":\"市场数据获取\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"function getTPSLOrderIds(symbol, currentPrice, posType) {\\n try {\\n exchange.SetContractType(symbol);\\n \\n const orders = exchange.GetOrders();\\n \\n if (!orders || orders.length === 0) {\\n return { tpOrderId: -1, slOrderId: -1 };\\n }\\n \\n let tpOrderId = -1;\\n let slOrderId = -1;\\n \\n for (let order of orders) {\\n // 多头持仓 (Type = 0 或 PD_LONG)\\n if (posType % 2 == 0) {\\n // 止盈:卖单价格 > 当前价\\n if (order.Type === ORDER_TYPE_SELL && order.Price > currentPrice) {\\n tpOrderId = order.Id;\\n }\\n // 止损:卖单价格 < 当前价\\n if (order.Type === ORDER_TYPE_SELL && order.Price < currentPrice) {\\n slOrderId = order.Id;\\n }\\n } \\n // 空头持仓\\n else {\\n // 止盈:买单价格 < 当前价\\n if (order.Type === ORDER_TYPE_BUY && order.Price < currentPrice) {\\n tpOrderId = order.Id;\\n }\\n // 止损:买单价格 > 当前价\\n if (order.Type === ORDER_TYPE_BUY && order.Price > currentPrice) {\\n slOrderId = order.Id;\\n }\\n }\\n }\\n \\n return { tpOrderId, slOrderId };\\n \\n } catch (e) {\\n Log(`⚠️ 获取 ${symbol} 订单失败: ${e.message}`);\\n return { tpOrderId: -1, slOrderId: -1 };\\n }\\n}\\n\\nfunction getAllPositions() {\\n // 获取当前账户权益\\n const curequity = exchange.GetAccount().Equity;\\n \\n // 获取合约列表\\n const contracts = $vars.contractList ? ($vars.contractList.includes(',') ? $vars.contractList.split(',') : [$vars.contractList]) : [];\\n \\n // 计算每个合约的risk_usd\\n const risk_usd = contracts.length > 0 ? curequity / contracts.length * 0.05 : 0;\\n \\n // 获取所有实际持仓\\n const rawPositions = exchange.GetPositions();\\n \\n // 创建持仓映射表 (合约符号 -> 持仓对象)\\n const positionMap = {};\\n \\n if (rawPositions && rawPositions.length > 0) {\\n for (let pos of rawPositions) {\\n if (pos.Amount && Math.abs(pos.Amount) > 0) {\\n positionMap[pos.Symbol] = pos;\\n }\\n }\\n }\\n \\n // 为每个合约创建position信息\\n const allPositions = [];\\n \\n for (let i = 0; i < contracts.length; i++) {\\n const contract = contracts[i].trim();\\n const pos = positionMap[contract];\\n \\n if (pos) {\\n // 有持仓的情况\\n try {\\n // 切换到对应合约获取ticker\\n exchange.SetContractType(contract);\\n \\n const ticker = exchange.GetTicker();\\n const currentPrice = ticker ? ticker.Last : pos.Price;\\n \\n // 获取止盈止损订单ID\\n const { tpOrderId, slOrderId } = getTPSLOrderIds(contract, currentPrice, pos.Type);\\n \\n // 获取退出计划\\n const exitPlan = _G(`exit_plan_${contract}`) || {\\n profit_target: null,\\n stop_loss: null,\\n invalidation_condition: \\\"\\\"\\n };\\n \\n allPositions.push({\\n symbol: contract,\\n quantity: Math.abs(pos.Amount),\\n entry_price: pos.Price,\\n current_price: currentPrice,\\n unrealized_pnl: _N(pos.Profit, 2),\\n exit_plan: exitPlan,\\n confidence: exitPlan?.confidence || null,\\n risk_usd: risk_usd,\\n sl_oid: slOrderId,\\n tp_oid: tpOrderId,\\n wait_for_fill: false,\\n entry_oid: pos.Info?.posId || -1,\\n notional_usd: _N(Math.abs(pos.Amount) * currentPrice, 2)\\n });\\n } catch (e) {\\n Log(`⚠️ 处理 ${contract} 持仓信息失败: ${e.message}`);\\n allPositions.push({\\n symbol: contract,\\n quantity: null,\\n entry_price: null,\\n current_price: null,\\n unrealized_pnl: null,\\n exit_plan: null,\\n confidence: null,\\n risk_usd: risk_usd,\\n sl_oid: null,\\n tp_oid: null,\\n wait_for_fill: false,\\n entry_oid: null,\\n notional_usd: null\\n });\\n }\\n } else {\\n // 没有持仓的情况\\n allPositions.push({\\n symbol: contract,\\n quantity: null,\\n entry_price: null,\\n current_price: null,\\n unrealized_pnl: null,\\n exit_plan: null,\\n confidence: null,\\n risk_usd: risk_usd,\\n sl_oid: null,\\n tp_oid: null,\\n wait_for_fill: false,\\n entry_oid: null,\\n notional_usd: null\\n });\\n }\\n }\\n \\n return allPositions;\\n}\\n\\nconst positions = getAllPositions();\\nreturn {positions};\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[256,240],\"id\":\"e17e8140-7aa1-414f-a574-d59a972d9da4\",\"name\":\"持仓数据获取\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 获取输入数据\\nconst inputData = $input.all();\\n\\n// 第一个输入是市场数据,第二个是持仓数据,第三个是表现数据\\nconst marketData = inputData[0].json.data;\\nconst positions = inputData[1].json;\\nconst performances = inputData[2].json;\\n\\n// 返回整理后的数据\\nreturn [{\\n json: {\\n marketData: marketData,\\n positions: positions,\\n performances: performances\\n }\\n}];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[736,240],\"id\":\"db872612-e46c-4ef4-8098-2ac5404e70aa\",\"name\":\"数据合并\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 商品期货交易执行系统 - 专注核心交易功能 ==========\\n// 功能:解析AI信号、执行交易、历史记录管理(无可视化)\\n\\n// ========== 配置参数 ==========\\nconst CONFIG = {\\n // 交易参数\\n MAX_POSITIONS: 6, // 最大持仓数量\\n MIN_CONFIDENCE: $vars.signalConfidence, // 最小置信度要求\\n DEFAULT_LEVERAGE: 10, // 默认杠杆\\n MAX_LEVERAGE: 20, // 最大杠杆\\n \\n // 风险管理\\n MAX_RISK_PER_TRADE: 1000, // 单笔最大风险\\n MAX_TOTAL_RISK: 5000, // 总最大风险\\n};\\n\\n// ========== 工具函数 ==========\\n\\n\\nfunction parseAIOutput(output) {\\n try {\\n Log('📥 开始解析AI输出...');\\n \\n // 清理输出格式\\n let cleaned = output.replace(/```[a-z]*\\\\n?/gi, '').trim();\\n \\n // 方法1:尝试标准JSON解析\\n try {\\n const standardResult = parseStandardJson(cleaned);\\n if (standardResult.length > 0) {\\n Log(`✅ 标准JSON解析成功,获得 ${standardResult.length} 个信号`);\\n return standardResult;\\n }\\n } catch (e) {\\n Log(`⚠️ 标准JSON解析失败: ${e.message}`);\\n }\\n \\n // 方法2:逐个提取决策对象\\n try {\\n const extractResult = extractDecisionObjects(cleaned);\\n if (extractResult.length > 0) {\\n Log(`✅ 对象提取解析成功,获得 ${extractResult.length} 个信号`);\\n return extractResult;\\n }\\n } catch (e) {\\n Log(`⚠️ 对象提取解析失败: ${e.message}`);\\n }\\n \\n // 方法3:正则表达式提取\\n try {\\n const regexResult = extractWithRegex(cleaned);\\n if (regexResult.length > 0) {\\n Log(`✅ 正则表达式解析成功,获得 ${regexResult.length} 个信号`);\\n return regexResult;\\n }\\n } catch (e) {\\n Log(`⚠️ 正则表达式解析失败: ${e.message}`);\\n }\\n \\n // 方法4:关键词提取(最后手段)\\n try {\\n const keywordResult = extractWithKeywords(cleaned);\\n if (keywordResult.length > 0) {\\n Log(`✅ 关键词解析成功,获得 ${keywordResult.length} 个信号`);\\n return keywordResult;\\n }\\n } catch (e) {\\n Log(`⚠️ 关键词解析失败: ${e.message}`);\\n }\\n \\n Log('❌ 所有解析方法都失败了');\\n return [];\\n \\n } catch (e) {\\n Log(`❌ parseAIOutput总体失败: ${e.message}`);\\n return [];\\n }\\n}\\n\\n/**\\n * 方法1:标准JSON解析(修复版)\\n */\\nfunction parseStandardJson(text) {\\n const jsonStart = text.indexOf('{');\\n const jsonEnd = text.lastIndexOf('}');\\n \\n if (jsonStart === -1 || jsonEnd === -1 || jsonEnd <= jsonStart) {\\n throw new Error('找不到JSON结构');\\n }\\n \\n let jsonStr = text.substring(jsonStart, jsonEnd + 1);\\n \\n // 修复常见JSON问题\\n jsonStr = fixCommonJsonIssues(jsonStr);\\n \\n const parsed = JSON.parse(jsonStr);\\n \\n if (parsed.decisions && Array.isArray(parsed.decisions)) {\\n return parsed.decisions.filter(d => d.signal && d.contract);\\n } else if (parsed.signal && parsed.contract) {\\n return [parsed];\\n }\\n \\n return [];\\n}\\n\\n/**\\n * 方法2:逐个提取决策对象\\n */\\nfunction extractDecisionObjects(text) {\\n const decisions = [];\\n \\n // 查找所有独立的决策对象\\n const decisionPattern = /\\\\{\\\\s*\\\"signal\\\"[^}]*\\\"contract\\\"[^}]*\\\\}/g;\\n let match;\\n \\n while ((match = decisionPattern.exec(text)) !== null) {\\n try {\\n let decisionStr = match[0];\\n \\n // 修复这个单独的对象\\n decisionStr = fixCommonJsonIssues(decisionStr);\\n \\n const decision = JSON.parse(decisionStr);\\n \\n // 验证必要字段\\n if (decision.signal && decision.contract) {\\n // 补充缺失字段\\n if (!decision.confidence) decision.confidence = 0.5;\\n if (!decision.leverage) decision.leverage = 1;\\n if (!decision.risk_usd) decision.risk_usd = 100;\\n if (!decision.profit_target) decision.profit_target = 0;\\n if (!decision.stop_loss) decision.stop_loss = 0;\\n if (!decision.justification) decision.justification = '从部分数据恢复';\\n \\n decisions.push(decision);\\n Log(`✅ 提取到决策: ${decision.contract} - ${decision.signal}`);\\n }\\n } catch (e) {\\n Log(`⚠️ 解析单个决策对象失败: ${e.message}`);\\n continue;\\n }\\n }\\n \\n return decisions;\\n}\\n\\n/**\\n * 方法3:正则表达式提取关键信息\\n */\\nfunction extractWithRegex(text) {\\n const decisions = [];\\n \\n // 更灵活的正则模式\\n const patterns = [\\n // 模式1:完整信息\\n /\\\"signal\\\":\\\\s*\\\"([^\\\"]+)\\\"[^}]*\\\"contract\\\":\\\\s*\\\"([^\\\"]+)\\\"[^}]*\\\"confidence\\\":\\\\s*([0-9.]+)[^}]*\\\"leverage\\\":\\\\s*([0-9]+)[^}]*\\\"profit_target\\\":\\\\s*([0-9.]+)[^}]*\\\"stop_loss\\\":\\\\s*([0-9.]+)[^}]*\\\"risk_usd\\\":\\\\s*([0-9.]+)/g,\\n \\n // 模式2:基本信息\\n /\\\"contract\\\":\\\\s*\\\"([^\\\"]+)\\\"[^}]*\\\"signal\\\":\\\\s*\\\"([^\\\"]+)\\\"[^}]*\\\"confidence\\\":\\\\s*([0-9.]+)/g,\\n \\n // 模式3:最简信息\\n /\\\"contract\\\":\\\\s*\\\"([^\\\"]+)\\\"[^}]*\\\"signal\\\":\\\\s*\\\"([^\\\"]+)\\\"/g\\n ];\\n \\n for (let i = 0; i < patterns.length; i++) {\\n const pattern = patterns[i];\\n let match;\\n \\n while ((match = pattern.exec(text)) !== null) {\\n let decision;\\n \\n if (i === 0) {\\n // 完整信息\\n decision = {\\n signal: match[1],\\n contract: match[2],\\n confidence: parseFloat(match[3]) || 0.5,\\n leverage: parseInt(match[4]) || 1,\\n profit_target: parseFloat(match[5]) || 0,\\n stop_loss: parseFloat(match[6]) || 0,\\n risk_usd: parseFloat(match[7]) || 100,\\n justification: \\\"正则提取完整信息\\\"\\n };\\n } else if (i === 1) {\\n // 基本信息\\n decision = {\\n signal: match[2],\\n contract: match[1],\\n confidence: parseFloat(match[3]) || 0.5,\\n leverage: 1,\\n profit_target: 0,\\n stop_loss: 0,\\n risk_usd: 100,\\n justification: \\\"正则提取基本信息\\\"\\n };\\n } else {\\n // 最简信息\\n decision = {\\n signal: match[2],\\n contract: match[1],\\n confidence: 0.5,\\n leverage: 1,\\n profit_target: 0,\\n stop_loss: 0,\\n risk_usd: 100,\\n justification: \\\"正则提取最简信息\\\"\\n };\\n }\\n \\n // 避免重复\\n if (!decisions.find(d => d.contract === decision.contract)) {\\n decisions.push(decision);\\n Log(`✅ 正则提取: ${decision.contract} - ${decision.signal}`);\\n }\\n }\\n \\n // 如果找到了信号,就不尝试下一个模式\\n if (decisions.length > 0) break;\\n }\\n \\n return decisions;\\n}\\n\\n/**\\n * 方法4:关键词提取(最后手段)\\n */\\nfunction extractWithKeywords(text) {\\n const decisions = [];\\n const lines = text.split('\\\\n');\\n \\n let currentDecision = null;\\n \\n for (let line of lines) {\\n line = line.trim();\\n \\n // 查找contract信息\\n const contractMatch = line.match(/[\\\"']contract[\\\"']:\\\\s*[\\\"']([A-Z0-9]+)[\\\"']/i);\\n if (contractMatch) {\\n if (currentDecision) {\\n decisions.push(currentDecision);\\n }\\n currentDecision = {\\n contract: contractMatch[1],\\n signal: 'hold',\\n confidence: 0.5,\\n leverage: 1,\\n profit_target: 0,\\n stop_loss: 0,\\n risk_usd: 100,\\n justification: \\\"关键词提取\\\"\\n };\\n }\\n \\n // 查找signal信息\\n const signalMatch = line.match(/[\\\"']signal[\\\"']:\\\\s*[\\\"'](buy_to_enter|sell_to_enter|close|hold)[\\\"']/i);\\n if (signalMatch && currentDecision) {\\n currentDecision.signal = signalMatch[1];\\n }\\n \\n // 查找confidence信息\\n const confidenceMatch = line.match(/[\\\"']confidence[\\\"']:\\\\s*([0-9.]+)/i);\\n if (confidenceMatch && currentDecision) {\\n currentDecision.confidence = parseFloat(confidenceMatch[1]);\\n }\\n \\n // 查找其他数值信息\\n const leverageMatch = line.match(/[\\\"']leverage[\\\"']:\\\\s*([0-9]+)/i);\\n if (leverageMatch && currentDecision) {\\n currentDecision.leverage = parseInt(leverageMatch[1]);\\n }\\n \\n const profitMatch = line.match(/[\\\"']profit_target[\\\"']:\\\\s*([0-9.]+)/i);\\n if (profitMatch && currentDecision) {\\n currentDecision.profit_target = parseFloat(profitMatch[1]);\\n }\\n \\n const stopMatch = line.match(/[\\\"']stop_loss[\\\"']:\\\\s*([0-9.]+)/i);\\n if (stopMatch && currentDecision) {\\n currentDecision.stop_loss = parseFloat(stopMatch[1]);\\n }\\n \\n const riskMatch = line.match(/[\\\"']risk_usd[\\\"']:\\\\s*([0-9.]+)/i);\\n if (riskMatch && currentDecision) {\\n currentDecision.risk_usd = parseFloat(riskMatch[1]);\\n }\\n }\\n \\n // 添加最后一个决策\\n if (currentDecision) {\\n decisions.push(currentDecision);\\n }\\n \\n return decisions.filter(d => d.contract && d.signal);\\n}\\n\\n/**\\n * 修复常见JSON问题\\n */\\nfunction fixCommonJsonIssues(jsonStr) {\\n // 1. 修复属性名缺少引号\\n jsonStr = jsonStr.replace(/([{,]\\\\s*)([a-zA-Z_][a-zA-Z0-9_]*)\\\\s*:/g, '$1\\\"$2\\\":');\\n \\n // 2. 修复单引号\\n jsonStr = jsonStr.replace(/'/g, '\\\"');\\n \\n // 3. 修复尾随逗号\\n jsonStr = jsonStr.replace(/,(\\\\s*[}\\\\]])/g, '$1');\\n \\n // 4. 修复多余的引号\\n jsonStr = jsonStr.replace(/\\\"{2,}/g, '\\\"');\\n \\n // 5. 修复数字前的多余引号\\n jsonStr = jsonStr.replace(/\\\"([0-9.]+)\\\"/g, '$1');\\n \\n // 6. 确保字符串值有引号\\n jsonStr = jsonStr.replace(/:\\\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\\\s*([,}])/g, ': \\\"$1\\\"$2');\\n \\n // 7. 修复布尔值\\n jsonStr = jsonStr.replace(/:\\\\s*\\\"(true|false|null)\\\"\\\\s*([,}])/g, ': $1$2');\\n \\n return jsonStr;\\n}\\n\\n/**\\n * 获取合约精度信息 - 商品期货版本\\n */\\nfunction getPrecision(contract) {\\n try {\\n const markets = _G('markets');\\n \\n if (markets && markets[contract]) {\\n const market = markets[contract];\\n \\n return {\\n exchange: market.ExchangeID, // 交易所ID\\n priceTick: market.PriceTick, // 价格最小变动单位\\n volumeMultiple: market.VolumeMultiple, // 合约乘数\\n // 计算价格精度(小数位数)\\n pricePrecision: getPricePrecision(market.PriceTick),\\n contract: contract\\n };\\n }\\n \\n // 如果未找到市场信息,返回 null\\n Log(`⚠️ 未找到${contract}的市场信息`);\\n return null;\\n \\n } catch (e) {\\n Log(`⚠️ 获取${contract}精度失败: ${e.message}`);\\n return null;\\n }\\n}\\n\\n/**\\n * 根据PriceTick计算价格精度\\n */\\nfunction getPricePrecision(priceTick) {\\n if (priceTick >= 1) return 0;\\n \\n const tickStr = priceTick.toString();\\n const decimalIndex = tickStr.indexOf('.');\\n \\n if (decimalIndex === -1) return 0;\\n \\n return tickStr.length - decimalIndex - 1;\\n}\\n\\n/**\\n * 计算交易数量 - 商品期货版本\\n */\\nfunction calculateQuantity(entryPrice, stopLoss, riskUsd, volumeMultiple) {\\n try {\\n // 1. 计算每手止损金额\\n // 公式: 止损金额 = |入场价 - 止损价| × 合约乘数\\n const stopLossMoney = Math.abs(entryPrice - stopLoss) * volumeMultiple;\\n \\n if (stopLossMoney <= 0) {\\n Log('❌ 止损金额计算错误');\\n return 0;\\n }\\n \\n // 2. 计算合约数量(基于风险金额)\\n // 公式: 合约数量 = 风险金额 / 每手止损金额\\n const contractQuantity = Math.floor(riskUsd / stopLossMoney);\\n \\n // 3. 至少1手\\n const finalQuantity = Math.max(1, contractQuantity);\\n \\n // 4. 记录计算详情\\n Log(`💡 计算详情: 入场价=${entryPrice} 止损价=${stopLoss} 合约乘数=${volumeMultiple}`);\\n Log(`💡 每手止损=${_N(stopLossMoney, 2)} 风险金额=${riskUsd} 最终数量=${finalQuantity}手`);\\n \\n return finalQuantity;\\n \\n } catch (e) {\\n Log(`❌ 计算数量失败: ${e.message}`);\\n return 0;\\n }\\n}\\n\\n/**\\n * 验证入场条件\\n */\\nfunction validateEntry(contract, signal, currentPrice, profitTarget, stopLoss, confidence) {\\n // 验证置信度\\n if (confidence < CONFIG.MIN_CONFIDENCE) {\\n Log(`⚠️ ${contract}: 置信度 ${confidence} 低于最小要求 ${CONFIG.MIN_CONFIDENCE}`);\\n return false;\\n }\\n \\n // 验证价格逻辑\\n const isLong = signal === 'buy_to_enter';\\n \\n if (isLong) {\\n if (profitTarget <= currentPrice) {\\n Log(`⚠️ ${contract}: 多头止盈价 ${profitTarget} 应高于当前价 ${currentPrice}`);\\n return false;\\n }\\n if (stopLoss >= currentPrice) {\\n Log(`⚠️ ${contract}: 多头止损价 ${stopLoss} 应低于当前价 ${currentPrice}`);\\n return false;\\n }\\n } else if (signal === 'sell_to_enter') {\\n if (profitTarget >= currentPrice) {\\n Log(`⚠️ ${contract}: 空头止盈价 ${profitTarget} 应低于当前价 ${currentPrice}`);\\n return false;\\n }\\n if (stopLoss <= currentPrice) {\\n Log(`⚠️ ${contract}: 空头止损价 ${stopLoss} 应高于当前价 ${currentPrice}`);\\n return false;\\n }\\n }\\n \\n return true;\\n}\\n\\n/**\\n * 检查是否有持仓 - 商品期货版本\\n */\\nfunction hasPosition(contract) {\\n try {\\n exchange.SetContractType(contract);\\n const positions = exchange.GetPosition(contract);\\n \\n if (!positions || positions.length === 0) {\\n return false;\\n }\\n \\n // 检查是否有该合约的持仓(多头或空头)\\n return positions.some(p => Math.abs(p.Amount) > 0);\\n \\n } catch (e) {\\n Log(`⚠️ 检查${contract}持仓失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 保存交易记录 - 商品期货版本\\n */\\nfunction saveTradeToHistory(orderDetail, signalInfo, contract) {\\n try {\\n const currentTime = Date.now();\\n \\n // 构建交易记录\\n const tradeRecord = {\\n // 订单信息\\n Id: orderDetail.Id,\\n Price: orderDetail.Price,\\n Amount: orderDetail.Amount,\\n DealAmount: orderDetail.DealAmount,\\n AvgPrice: orderDetail.AvgPrice,\\n Status: orderDetail.Status,\\n Type: orderDetail.Type,\\n Offset: orderDetail.Offset,\\n ContractType: orderDetail.ContractType,\\n \\n // AI信号信息\\n aiSignal: signalInfo.signal,\\n confidence: signalInfo.confidence,\\n profitTarget: signalInfo.profit_target,\\n stopLoss: signalInfo.stop_loss,\\n leverage: signalInfo.leverage,\\n riskUsd: signalInfo.risk_usd,\\n justification: signalInfo.justification,\\n \\n // 时间戳\\n time: currentTime,\\n timeStr: new Date(currentTime).toISOString()\\n };\\n \\n // 获取现有历史记录\\n let tradesHistory = _G('tradesHistory') || {};\\n \\n // 初始化合约记录\\n if (!tradesHistory[contract]) {\\n tradesHistory[contract] = [];\\n }\\n \\n // 添加记录\\n tradesHistory[contract].push(tradeRecord);\\n \\n // 限制历史记录数量(每个合约最多保存1000条)\\n if (tradesHistory[contract].length > 1000) {\\n tradesHistory[contract] = tradesHistory[contract].slice(-1000);\\n }\\n \\n // 保存\\n _G('tradesHistory', tradesHistory);\\n \\n const typeStr = orderDetail.Type === 0 ? '买入' : '卖出';\\n const offsetStr = orderDetail.Offset === 0 ? '开仓' : '平仓';\\n Log(`📝 ${contract} 交易记录已保存: ${typeStr}${offsetStr} ${orderDetail.DealAmount}手 @ ${orderDetail.AvgPrice}`);\\n \\n } catch (e) {\\n Log(`❌ 保存交易记录失败: ${e.message}`);\\n }\\n}\\n\\n/**\\n * 保存退出计划\\n */\\nfunction saveExitPlan(contract, signalInfo) {\\n try {\\n const exitPlan = {\\n profit_target: signalInfo.profit_target,\\n stop_loss: signalInfo.stop_loss,\\n invalidation_condition: signalInfo.invalidation_condition || \\\"\\\",\\n confidence: signalInfo.confidence,\\n risk_usd: signalInfo.risk_usd,\\n leverage: signalInfo.leverage,\\n timestamp: Date.now(),\\n justification: signalInfo.justification\\n };\\n \\n _G(`exit_plan_${contract}`, exitPlan);\\n Log(`💾 ${contract} 退出计划已保存`);\\n } catch (e) {\\n Log(`❌ 保存${contract}退出计划失败: ${e.message}`);\\n }\\n}\\n\\n// ========== 交易执行函数 ==========\\n\\n/**\\n * 执行平仓 - 商品期货版本(支持上期所今仓/昨仓)\\n */\\nfunction executeClose(contract, reason = \\\"AI信号\\\") {\\n try {\\n exchange.SetContractType(contract);\\n \\n // 获取合约信息\\n const precision = getPrecision(contract);\\n if (!precision) {\\n Log(`❌ ${contract}: 无法获取合约信息`);\\n return false;\\n }\\n \\n const exchangeID = precision.exchange;\\n const isShfe = exchangeID === 'SHFE'; // 上期所\\n \\n // 取消所有未完成订单\\n const orders = exchange.GetOrders();\\n if (orders && orders.length > 0) {\\n orders.forEach(o => {\\n try {\\n exchange.CancelOrder(o.Id);\\n } catch (e) {\\n Log(`⚠️ 取消订单失败: ${e.message}`);\\n }\\n });\\n }\\n \\n // 查找持仓\\n const positions = exchange.GetPosition(contract);\\n \\n if (!positions || positions.length === 0) {\\n Log(`⚠️ ${contract}: 未找到持仓`);\\n return false;\\n }\\n \\n let successCount = 0;\\n \\n // 平掉所有持仓(多头和空头)\\n for (let pos of positions) {\\n if (Math.abs(pos.Amount) === 0) continue;\\n \\n const isLong = pos.Type === PD_LONG || pos.Type === 0;\\n const closeAmount = Math.abs(pos.Amount);\\n \\n // 根据交易所设置平仓方向\\n let closeDirection = '';\\n \\n if (isShfe) {\\n // 上期所: 优先平今仓\\n if (isLong) {\\n closeDirection = \\\"closebuy_today\\\"; // 平多今\\n } else {\\n closeDirection = \\\"closesell_today\\\"; // 平空今\\n }\\n \\n Log(`📍 ${contract}: 上期所合约,使用平今指令 ${closeDirection}`);\\n } else {\\n // 其他交易所: 普通平仓\\n closeDirection = isLong ? \\\"closebuy\\\" : \\\"closesell\\\";\\n Log(`📍 ${contract}: 非上期所合约,使用普通平仓指令 ${closeDirection}`);\\n }\\n \\n // 设置平仓方向\\n exchange.SetDirection(closeDirection);\\n \\n // 执行平仓(市价单)\\n const orderId = isLong ? \\n exchange.Sell(-1, closeAmount) : \\n exchange.Buy(-1, closeAmount);\\n \\n if (orderId) {\\n const directionStr = isLong ? '多' : '空';\\n Log(`✅ ${contract}: 平${directionStr}成功 (${reason}) 方向=${closeDirection} 数量=${closeAmount}手`);\\n successCount++;\\n \\n // 获取订单详情并保存\\n Sleep(500);\\n try {\\n const orderDetail = exchange.GetOrder(orderId);\\n if (orderDetail) {\\n saveTradeToHistory(orderDetail, { \\n signal: 'close', \\n confidence: 1.0, \\n justification: reason,\\n closeDirection: closeDirection // 记录平仓方向\\n }, contract);\\n }\\n } catch (e) {\\n Log(`⚠️ 获取平仓订单详情失败: ${e.message}`);\\n }\\n \\n // 如果是上期所且平今失败,尝试平昨\\n if (isShfe && !orderId) {\\n Log(`⚠️ ${contract}: 平今仓失败,尝试平昨仓`);\\n \\n const closeYesterdayDirection = isLong ? \\\"closebuy\\\" : \\\"closesell\\\";\\n exchange.SetDirection(closeYesterdayDirection);\\n \\n const yesterdayOrderId = isLong ? \\n exchange.Sell(-1, closeAmount) : \\n exchange.Buy(-1, closeAmount);\\n \\n if (yesterdayOrderId) {\\n Log(`✅ ${contract}: 平昨仓成功`);\\n successCount++;\\n \\n Sleep(500);\\n try {\\n const orderDetail = exchange.GetOrder(yesterdayOrderId);\\n if (orderDetail) {\\n saveTradeToHistory(orderDetail, { \\n signal: 'close', \\n confidence: 1.0, \\n justification: reason + '(平昨)',\\n closeDirection: closeYesterdayDirection\\n }, contract);\\n }\\n } catch (e) {\\n Log(`⚠️ 获取平昨订单详情失败: ${e.message}`);\\n }\\n }\\n }\\n } else {\\n Log(`❌ ${contract}: 平仓失败`);\\n \\n // 如果是上期所,尝试平昨仓\\n if (isShfe) {\\n Log(`⚠️ ${contract}: 上期所平今失败,尝试平昨仓`);\\n \\n const closeYesterdayDirection = isLong ? \\\"closebuy\\\" : \\\"closesell\\\";\\n exchange.SetDirection(closeYesterdayDirection);\\n \\n const yesterdayOrderId = isLong ? \\n exchange.Sell(-1, closeAmount) : \\n exchange.Buy(-1, closeAmount);\\n \\n if (yesterdayOrderId) {\\n Log(`✅ ${contract}: 平昨仓成功 数量=${closeAmount}手`);\\n successCount++;\\n \\n Sleep(500);\\n try {\\n const orderDetail = exchange.GetOrder(yesterdayOrderId);\\n if (orderDetail) {\\n saveTradeToHistory(orderDetail, { \\n signal: 'close', \\n confidence: 1.0, \\n justification: reason + '(平昨)',\\n closeDirection: closeYesterdayDirection\\n }, contract);\\n }\\n } catch (e) {\\n Log(`⚠️ 获取平昨订单详情失败: ${e.message}`);\\n }\\n } else {\\n Log(`❌ ${contract}: 平昨仓也失败了`);\\n }\\n }\\n }\\n }\\n \\n if (successCount > 0) {\\n // 清除退出计划\\n _G(`exit_plan_${contract}`, null);\\n return true;\\n }\\n \\n return false;\\n \\n } catch (e) {\\n Log(`❌ ${contract} 平仓执行失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 执行开仓 - 商品期货版本\\n */\\nfunction executeEntry(contract, signalInfo) {\\n try {\\n exchange.SetContractType(contract);\\n \\n // 获取当前价格\\n const ticker = exchange.GetTicker();\\n if (!ticker) {\\n Log(`❌ ${contract}: 无法获取价格信息`);\\n return false;\\n }\\n \\n const currentPrice = ticker.Last;\\n const isLong = signalInfo.signal === 'buy_to_enter';\\n \\n // 验证入场条件\\n if (!validateEntry(contract, signalInfo.signal, currentPrice, signalInfo.profit_target, signalInfo.stop_loss, signalInfo.confidence)) {\\n return false;\\n }\\n \\n // 获取精度信息\\n const precision = getPrecision(contract);\\n if (!precision) {\\n Log(`❌ ${contract}: 无法获取合约精度信息`);\\n return false;\\n }\\n \\n // 计算数量\\n const quantity = calculateQuantity(\\n currentPrice, \\n signalInfo.stop_loss, \\n signalInfo.risk_usd, \\n precision.volumeMultiple\\n );\\n \\n if (quantity <= 0) {\\n Log(`⚠️ ${contract}: 计算数量无效,跳过开仓`);\\n return false;\\n }\\n \\n // 设置交易方向\\n exchange.SetDirection(isLong ? \\\"buy\\\" : \\\"sell\\\");\\n \\n // 执行开仓(市价单)\\n const orderId = isLong ? \\n exchange.Buy(-1, quantity) : \\n exchange.Sell(-1, quantity);\\n \\n if (orderId) {\\n const directionStr = isLong ? '多' : '空';\\n Log(`✅ ${contract}: 开${directionStr}成功 数量=${quantity}手 风险=${signalInfo.risk_usd} 置信度=${signalInfo.confidence}`);\\n \\n // 保存退出计划\\n saveExitPlan(contract, signalInfo);\\n \\n // 获取订单详情并保存\\n Sleep(1000);\\n try {\\n const orderDetail = exchange.GetOrder(orderId);\\n if (orderDetail) {\\n saveTradeToHistory(orderDetail, signalInfo, contract);\\n }\\n } catch (e) {\\n Log(`⚠️ 获取开仓订单详情失败: ${e.message}`);\\n }\\n \\n return true;\\n } else {\\n Log(`❌ ${contract}: 开仓失败`);\\n return false;\\n }\\n } catch (e) {\\n Log(`❌ ${contract} 开仓执行失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n// ========== 主执行逻辑 ==========\\n\\nfunction main() {\\n try {\\n // 获取AI输出\\n const aiOutput = $input.first().json.output;\\n Log('调试aiOutput:', aiOutput);\\n \\n const signals = parseAIOutput(aiOutput);\\n \\n _G('AISignal', signals);\\n \\n Log('🤖 AI信号解析结果:', signals);\\n \\n // 如果没有有效信号,退出\\n if (!signals || signals.length === 0) {\\n Log('⚠️ 未获取到有效的AI交易信号');\\n \\n // 保存空的执行结果\\n _G('latestExecutionResults', {\\n results: {},\\n timestamp: Date.now(),\\n totalSignals: 0,\\n totalExecutions: 0,\\n note: 'No valid signals'\\n });\\n \\n return { json: { processed: false, reason: 'No valid signals' } };\\n }\\n \\n // 执行交易信号\\n const executionResults = {};\\n let totalExecutions = 0;\\n \\n // 处理每个合约的信号\\n for (let i = 0; i < signals.length; i++) {\\n const signalInfo = signals[i];\\n const contract = signalInfo.contract;\\n const signal = signalInfo.signal;\\n \\n if (!contract || !signal) {\\n Log(`⚠️ 跳过无效信号: ${JSON.stringify(signalInfo)}`);\\n continue;\\n }\\n \\n Log(`🎯 处理 ${contract} 的 ${signal} 信号`);\\n \\n const hasPos = hasPosition(contract);\\n let executed = false;\\n let skipReason = '';\\n \\n // 根据信号类型执行操作\\n switch (signal) {\\n case 'buy_to_enter':\\n case 'sell_to_enter':\\n if (hasPos) {\\n skipReason = '已有持仓';\\n } else {\\n executed = executeEntry(contract, signalInfo);\\n if (executed) totalExecutions++;\\n }\\n break;\\n \\n case 'close':\\n if (!hasPos) {\\n skipReason = '无持仓';\\n } else {\\n executed = executeClose(contract, 'AI信号');\\n if (executed) totalExecutions++;\\n }\\n break;\\n \\n case 'hold':\\n skipReason = hasPos ? '持仓观望' : '空仓等待';\\n break;\\n \\n default:\\n skipReason = '未知信号';\\n }\\n \\n // 记录执行结果\\n executionResults[contract] = {\\n signal: signal,\\n executed: executed,\\n skipReason: skipReason,\\n confidence: signalInfo.confidence || 0,\\n timestamp: Date.now(),\\n hasPosition: hasPos,\\n // 添加更多执行细节\\n leverage: signalInfo.leverage || 0,\\n risk_usd: signalInfo.risk_usd || 0,\\n profit_target: signalInfo.profit_target || 0,\\n stop_loss: signalInfo.stop_loss || 0,\\n justification: signalInfo.justification || ''\\n };\\n \\n // 避免请求过于频繁\\n Sleep(500);\\n }\\n \\n // ===== 保存执行结果到_G =====\\n const executionSummary = {\\n results: executionResults,\\n timestamp: Date.now(),\\n totalSignals: signals.length,\\n totalExecutions: totalExecutions,\\n // 统计信息\\n stats: {\\n executed: Object.values(executionResults).filter(r => r.executed).length,\\n skipped: Object.values(executionResults).filter(r => !r.executed && r.skipReason).length,\\n failed: Object.values(executionResults).filter(r => !r.executed && !r.skipReason).length,\\n holds: Object.values(executionResults).filter(r => r.signal === 'hold').length,\\n entries: Object.values(executionResults).filter(r => ['buy_to_enter', 'sell_to_enter'].includes(r.signal)).length,\\n closes: Object.values(executionResults).filter(r => r.signal === 'close').length\\n }\\n };\\n \\n _G('latestExecutionResults', executionSummary);\\n \\n Log(`💾 执行结果已保存到全局存储`);\\n Log(`📊 执行总结: 处理了 ${signals.length} 个信号,执行了 ${totalExecutions} 个操作`);\\n \\n // 详细输出执行结果\\n Object.entries(executionResults).forEach(([contract, result]) => {\\n if (result.executed) {\\n Log(`✅ ${contract}: ${result.signal} 执行成功 (置信度: ${(result.confidence * 100).toFixed(0)}%)`);\\n } else if (result.skipReason) {\\n Log(`⏭️ ${contract}: ${result.signal} 跳过执行 - ${result.skipReason}`);\\n } else {\\n Log(`❌ ${contract}: ${result.signal} 执行失败`);\\n }\\n });\\n \\n return { \\n json: { \\n processed: true, \\n totalSignals: signals.length,\\n totalExecutions: totalExecutions,\\n results: executionResults,\\n summary: executionSummary.stats\\n } \\n };\\n \\n } catch (e) {\\n Log(`❌ 主执行流程失败: ${e.message}`);\\n Log(`❌ 错误堆栈: ${e.stack}`);\\n \\n // 即使出错也保存错误信息\\n _G('latestExecutionResults', {\\n results: {},\\n timestamp: Date.now(),\\n totalSignals: 0,\\n totalExecutions: 0,\\n error: e.message,\\n note: 'Execution failed'\\n });\\n \\n return { json: { processed: false, error: e.message } };\\n }\\n}\\n\\n// 执行主函数\\nreturn main();\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[1296,240],\"id\":\"18d0fb97-7005-4b9e-81cb-7b997228b1d2\",\"name\":\"交易执行\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"function analyzePerformance(orders, contract) {\\n if (!orders || orders.length === 0) return { contract, totalTrades: 0, message: 'No stored trade records' }\\n \\n const filteredOrders = orders.filter(o => o.Status === 1)\\n const validOrders = filteredOrders.sort((a, b) => a.time - b.time)\\n \\n if (validOrders.length === 0) return { contract, totalTrades: 0}\\n \\n let trades = []\\n let positions = []\\n \\n for (let order of validOrders) {\\n const { Type: type, DealAmount: amount, AvgPrice: price, time, Id: id } = order\\n \\n let remaining = amount\\n \\n for (let i = 0; i < positions.length && remaining > 0.0001; i++) {\\n const pos = positions[i]\\n \\n if ((type === 0 && pos.type === 1) || (type === 1 && pos.type === 0)) {\\n const tradeAmount = Math.min(remaining, pos.amount)\\n const holdTime = time - pos.time\\n \\n let profit, direction\\n if (pos.type === 0) {\\n profit = (price - pos.price) * tradeAmount * 10\\n direction = 'LONG'\\n } else {\\n profit = (pos.price - price) * tradeAmount * 10\\n direction = 'SHORT'\\n }\\n \\n trades.push({\\n direction,\\n openPrice: pos.price,\\n closePrice: price,\\n amount: tradeAmount,\\n profit,\\n holdTime: holdTime,\\n openOrderId: pos.id,\\n closeOrderId: id,\\n closeTime: time\\n })\\n \\n pos.amount -= tradeAmount\\n remaining -= tradeAmount\\n \\n if (pos.amount <= 0.0001) {\\n positions.splice(i, 1)\\n i--\\n }\\n }\\n }\\n \\n if (remaining > 0.0001) {\\n positions.push({\\n type: type,\\n price: price,\\n amount: remaining,\\n time: time,\\n id: id\\n })\\n }\\n }\\n \\n if (trades.length === 0) return { \\n contract, \\n totalTrades: 0, \\n openPositions: positions.length,\\n totalOrders: validOrders.length,\\n consecutiveLosses: 0,\\n freezeStatus: 'free'\\n }\\n \\n trades.sort((a, b) => a.closeTime - b.closeTime)\\n \\n function calculateRecentConsecutiveLosses(tradesArray) {\\n const currentTime = Date.now()\\n const fourHoursAgo = currentTime - (4 * 60 * 60 * 1000)\\n \\n const recentTrades = tradesArray.filter(trade => trade.closeTime >= fourHoursAgo)\\n \\n if (recentTrades.length === 0) return 0\\n \\n recentTrades.sort((a, b) => a.closeTime - b.closeTime)\\n \\n let consecutiveLosses = 0\\n for (let i = recentTrades.length - 1; i >= 0; i--) {\\n if (recentTrades[i].profit <= 0) {\\n consecutiveLosses++\\n } else {\\n break\\n }\\n }\\n \\n return consecutiveLosses\\n }\\n \\n const consecutiveLosses = calculateRecentConsecutiveLosses(trades)\\n \\n const currentTime = Date.now()\\n let freezeStatus = 'free'\\n let freezeStartTime = null\\n let freezeEndTime = null\\n \\n if (consecutiveLosses > 2) {\\n const frozenData = _G(`frozen_${contract}`)\\n \\n if (frozenData && frozenData.freezeEndTime > currentTime) {\\n freezeStatus = 'frozen'\\n freezeStartTime = frozenData.freezeStartTime\\n freezeEndTime = frozenData.freezeEndTime\\n } else {\\n const lastTriggerLosses = frozenData ? frozenData.triggerLosses : 0\\n \\n if (consecutiveLosses > lastTriggerLosses || !frozenData) {\\n freezeStatus = 'frozen'\\n freezeStartTime = currentTime\\n freezeEndTime = currentTime + (4 * 60 * 60 * 1000)\\n \\n _G(`frozen_${contract}`, {\\n freezeStartTime: freezeStartTime,\\n freezeEndTime: freezeEndTime,\\n triggerLosses: consecutiveLosses,\\n triggerTime: currentTime\\n })\\n \\n Log(`🧊 ${contract} 进入冷却期: 最近4小时连续亏损 ${consecutiveLosses} 次,4小时后解冻`)\\n } else {\\n freezeStatus = 'free'\\n _G(`frozen_${contract}`, null)\\n }\\n }\\n } else {\\n const frozenData = _G(`frozen_${contract}`)\\n if (frozenData && frozenData.freezeEndTime <= currentTime) {\\n _G(`frozen_${contract}`, null)\\n } else if (!frozenData) {\\n freezeStatus = 'free'\\n } else {\\n freezeStatus = 'frozen'\\n freezeStartTime = frozenData.freezeStartTime\\n freezeEndTime = frozenData.freezeEndTime\\n }\\n }\\n \\n const wins = trades.filter(t => t.profit > 0)\\n const losses = trades.filter(t => t.profit <= 0)\\n const longTrades = trades.filter(t => t.direction === 'LONG')\\n const shortTrades = trades.filter(t => t.direction === 'SHORT')\\n \\n const longWins = longTrades.filter(t => t.profit > 0)\\n const longLosses = longTrades.filter(t => t.profit <= 0)\\n const longWinProfit = longWins.reduce((sum, t) => sum + t.profit, 0)\\n const longLossProfit = Math.abs(longLosses.reduce((sum, t) => sum + t.profit, 0))\\n \\n const shortWins = shortTrades.filter(t => t.profit > 0)\\n const shortLosses = shortTrades.filter(t => t.profit <= 0)\\n const shortWinProfit = shortWins.reduce((sum, t) => sum + t.profit, 0)\\n const shortLossProfit = Math.abs(shortLosses.reduce((sum, t) => sum + t.profit, 0))\\n \\n const totalProfit = trades.reduce((sum, t) => sum + t.profit, 0)\\n const totalWinProfit = wins.reduce((sum, t) => sum + t.profit, 0)\\n const totalLossProfit = Math.abs(losses.reduce((sum, t) => sum + t.profit, 0))\\n const winRate = (wins.length / trades.length * 100)\\n \\n const result = {\\n contract,\\n totalTrades: trades.length,\\n totalOrders: validOrders.length,\\n openPositions: positions.length,\\n totalProfit: parseFloat(totalProfit.toFixed(2)),\\n winRate: parseFloat(winRate.toFixed(2)),\\n winCount: wins.length,\\n lossCount: losses.length,\\n avgProfit: parseFloat((totalProfit / trades.length).toFixed(2)),\\n maxProfit: wins.length > 0 ? parseFloat(Math.max(...wins.map(t => t.profit)).toFixed(2)) : 0,\\n maxLoss: parseFloat(Math.min(...trades.map(t => t.profit)).toFixed(2)),\\n profitLossRatio: totalLossProfit > 0 ? parseFloat((totalWinProfit / totalLossProfit).toFixed(2)) : (totalWinProfit > 0 ? 999 : 0),\\n longWinCount: longWins.length,\\n longLossCount: longLosses.length,\\n longWinProfit: parseFloat(longWinProfit.toFixed(2)),\\n longLossProfit: parseFloat(longLossProfit.toFixed(2)),\\n shortWinCount: shortWins.length,\\n shortLossCount: shortLosses.length,\\n shortWinProfit: parseFloat(shortWinProfit.toFixed(2)),\\n shortLossProfit: parseFloat(shortLossProfit.toFixed(2)),\\n avgHoldTimeHours: trades.length > 0 ? parseFloat((trades.reduce((sum, t) => sum + t.holdTime, 0) / trades.length / (1000 * 60 * 60)).toFixed(2)) : 0,\\n maxHoldTimeHours: trades.length > 0 ? parseFloat((Math.max(...trades.map(t => t.holdTime)) / (1000 * 60 * 60)).toFixed(2)) : 0,\\n minHoldTimeHours: trades.length > 0 ? parseFloat((Math.min(...trades.map(t => t.holdTime)) / (1000 * 60 * 60)).toFixed(2)) : 0,\\n consecutiveLosses: consecutiveLosses,\\n recentLossesDescription: `最近4小时连续亏损${consecutiveLosses}次`,\\n freezeStatus: freezeStatus,\\n freezeStartTime: freezeStartTime,\\n freezeEndTime: freezeEndTime\\n }\\n \\n if (freezeStatus === 'frozen') {\\n const remainingFreezeHours = parseFloat(((freezeEndTime - currentTime) / (1000 * 60 * 60)).toFixed(2))\\n result.remainingFreezeHours = Math.max(0, remainingFreezeHours)\\n }\\n \\n return result\\n}\\n\\nconst contracts = $input.first().json.contracts\\nconst results = {}\\nconst tradesHistory = _G('tradesHistory') || {}\\n\\nLog('📊 开始分析最近4小时的交易表现...')\\n\\nfor (let contract of contracts) {\\n try {\\n const orders = tradesHistory[contract] || []\\n results[contract] = analyzePerformance(orders, contract)\\n } catch (e) {\\n results[contract] = { error: e.toString() }\\n }\\n}\\n\\nconst sorted = Object.values(results)\\n .filter(r => !r.error && r.totalTrades > 0)\\n .sort((a, b) => b.totalProfit - a.totalProfit)\\n\\nconst frozenContracts = sorted.filter(r => r.freezeStatus === 'frozen')\\nconst freeContracts = sorted.filter(r => r.freezeStatus === 'free')\\n\\nLog(`🧊 冷却状态统计:`)\\nLog(` 冷却中合约: ${frozenContracts.length}`)\\nLog(` 可交易合约: ${freeContracts.length}`)\\n\\nif (frozenContracts.length > 0) {\\n Log(`❄️ 冷却中的合约:`)\\n frozenContracts.forEach(contract => {\\n Log(` ${contract.contract}: ${contract.recentLossesDescription}, 剩余${contract.remainingFreezeHours || 0}小时解冻`)\\n })\\n}\\n\\nreturn {\\n sorted: sorted,\\n summary: {\\n totalProfit: sorted.reduce((sum, r) => sum + r.totalProfit, 0).toFixed(2),\\n avgWinRate: sorted.length > 0 ? (sorted.reduce((sum, r) => sum + r.winRate, 0) / sorted.length).toFixed(2) : 0,\\n totalRecords: Object.values(tradesHistory).reduce((sum, contractTrades) => sum + contractTrades.length, 0),\\n contractsWithData: Object.keys(tradesHistory).length,\\n frozenContractsCount: frozenContracts.length,\\n freeContractsCount: freeContracts.length,\\n analysisMethod: '基于最近4小时连续亏损'\\n },\\n frozenContracts: frozenContracts.map(contract => ({\\n contract: contract.contract,\\n consecutiveLosses: contract.consecutiveLosses,\\n recentLossesDescription: contract.recentLossesDescription,\\n remainingFreezeHours: contract.remainingFreezeHours || 0\\n })),\\n freeContracts: freeContracts.map(contract => contract.contract)\\n}\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[256,448],\"id\":\"ada62604-53ef-413f-b11d-88e8165fb4e9\",\"name\":\"合约表现统计\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 止盈止损监控系统 - 商品期货版 ==========\\n\\nif (_G('invoketime') === null) {\\n return {}\\n}\\n\\nconst sltpChoice = $vars.sltpChoice;\\nconst FIXED_PROFIT_RATIO = $vars.tpPercent;\\nconst FIXED_LOSS_RATIO = $vars.slPercent;\\n\\n/**\\n * 判断是否为上期所合约\\n */\\nfunction isSHFE(contract) {\\n const markets = _G('markets');\\n if (markets && markets[contract]) {\\n return markets[contract].ExchangeID === 'SHFE';\\n }\\n}\\n\\n/**\\n * 保存交易记录到历史数据库 - 商品期货版\\n */\\nfunction saveTradeToHistory(orderDetail, contract, signalInfo) {\\n try {\\n const currentTime = Date.now();\\n \\n const tradeRecord = {\\n // 订单信息\\n Id: orderDetail.Id,\\n Price: orderDetail.Price,\\n Amount: orderDetail.Amount,\\n DealAmount: orderDetail.DealAmount,\\n AvgPrice: orderDetail.AvgPrice,\\n Status: orderDetail.Status,\\n Type: orderDetail.Type,\\n Offset: orderDetail.Offset,\\n ContractType: orderDetail.ContractType,\\n \\n // AI信号信息\\n aiSignal: signalInfo.signal || 'auto_close',\\n confidence: signalInfo.confidence || 1.0,\\n profitTarget: signalInfo.profit_target,\\n stopLoss: signalInfo.stop_loss,\\n leverage: signalInfo.leverage,\\n riskUsd: signalInfo.risk_usd,\\n justification: signalInfo.justification,\\n \\n // 时间戳\\n time: currentTime,\\n timeStr: new Date(currentTime).toISOString()\\n };\\n \\n let tradesHistory = _G('tradesHistory') || {};\\n \\n if (!tradesHistory[contract]) {\\n tradesHistory[contract] = [];\\n }\\n \\n tradesHistory[contract].push(tradeRecord);\\n \\n if (tradesHistory[contract].length > 1000) {\\n tradesHistory[contract] = tradesHistory[contract].slice(-1000);\\n }\\n \\n _G('tradesHistory', tradesHistory);\\n \\n const typeStr = (orderDetail.Type % 2) === 0 ? '买入' : '卖出';\\n Log(`📝 ${contract} 交易记录已保存: ${typeStr} ${orderDetail.DealAmount}手 @ ${orderDetail.AvgPrice}`);\\n \\n } catch (e) {\\n Log(`❌ 保存${contract}交易记录失败: ${e.message}`);\\n }\\n}\\n\\n/**\\n * 平仓函数 - 商品期货版\\n */\\nfunction closePosition(contract, pos, isLong, reason, pnl) {\\n try {\\n const closeAmount = _N(Math.abs(pos.Amount), 0);\\n const directionStr = isLong ? '多' : '空';\\n \\n Log(`🔄 开始平仓: ${contract} ${directionStr} ${closeAmount}手 原因=${reason}`);\\n \\n let orderId = null;\\n \\n // 判断是否为上期所\\n if (isSHFE(contract)) {\\n Log(`📍 ${contract} 是上期所合约,使用今仓平仓指令`);\\n \\n // 上期所: 先尝试平今仓\\n const closeTodayDirection = isLong ? \\\"closebuy_today\\\" : \\\"closesell_today\\\";\\n exchange.SetDirection(closeTodayDirection);\\n \\n orderId = isLong ? \\n exchange.Sell(-1, closeAmount) : \\n exchange.Buy(-1, closeAmount);\\n \\n if (orderId) {\\n Log(`✅ ${contract} 平今仓成功 ${closeAmount}手`);\\n } else {\\n // 如果平今失败,尝试平昨\\n Log(`⚠️ ${contract} 平今仓失败,尝试平昨仓`);\\n Sleep(300);\\n \\n const closeYesterdayDirection = isLong ? \\\"closebuy\\\" : \\\"closesell\\\";\\n exchange.SetDirection(closeYesterdayDirection);\\n \\n orderId = isLong ? \\n exchange.Sell(-1, closeAmount) : \\n exchange.Buy(-1, closeAmount);\\n \\n if (!orderId) {\\n Log(`❌ ${contract} 平昨仓也失败`);\\n return false;\\n }\\n \\n Log(`✅ ${contract} 平昨仓成功 ${closeAmount}手`);\\n }\\n } else {\\n // 非上期所: 普通平仓\\n Log(`📍 ${contract} 非上期所合约,使用普通平仓指令`);\\n \\n const closeDirection = isLong ? \\\"closebuy\\\" : \\\"closesell\\\";\\n exchange.SetDirection(closeDirection);\\n \\n orderId = isLong ? \\n exchange.Sell(-1, closeAmount) : \\n exchange.Buy(-1, closeAmount);\\n \\n if (!orderId) {\\n Log(`❌ ${contract} 平仓失败`);\\n return false;\\n }\\n \\n Log(`✅ ${contract} 平仓成功 ${closeAmount}手`);\\n }\\n \\n // 记录日志\\n const reasonEmoji = reason === '止盈' ? '✅' : '❌';\\n Log(`${reasonEmoji} ${contract} ${reason} ${(pnl*100).toFixed(2)}%`);\\n \\n // 清除退出计划\\n _G(`exit_plan_${contract}`, null);\\n \\n // 保存交易记录到历史\\n Sleep(1000);\\n try {\\n const orderDetail = exchange.GetOrder(orderId);\\n if (orderDetail && orderDetail.Status === 1) {\\n saveTradeToHistory(orderDetail, contract, { \\n signal: 'close', \\n confidence: 1.0, \\n justification: `自动${reason} PnL: ${(pnl*100).toFixed(2)}%` \\n });\\n }\\n } catch (e) {\\n Log(`⚠️ 获取${contract}${reason}订单详情失败: ${e.message}`);\\n }\\n \\n return true;\\n \\n } catch (e) {\\n Log(`❌ ${contract} 平仓失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 监控单个合约持仓\\n */\\nfunction monitorPosition(contract) {\\n try {\\n // 设置合约\\n exchange.SetContractType(contract);\\n \\n // 获取该合约的持仓\\n const positions = exchange.GetPosition(contract);\\n \\n if (!positions || positions.length === 0) {\\n return { status: \\\"no_position\\\" };\\n }\\n \\n // 查找有效持仓(数量不为0)\\n const pos = positions.find(p => Math.abs(p.Amount) > 0);\\n \\n if (!pos) {\\n return { status: \\\"no_position\\\" };\\n }\\n \\n // 获取当前价格\\n const ticker = exchange.GetTicker();\\n if (!ticker) {\\n return { status: \\\"no_ticker\\\" };\\n }\\n \\n const isLong = (pos.Type % 2) === 0;\\n const currentPrice = ticker.Last;\\n \\n // 计算盈亏百分比\\n const pnl = (currentPrice - pos.Price) * (isLong ? 1 : -1) / pos.Price;\\n \\n let shouldTP = false;\\n let shouldSL = false;\\n let profitTarget = null;\\n let stopLoss = null;\\n \\n // 根据配置选择止盈止损模式\\n if (sltpChoice) {\\n // 固定模式\\n shouldTP = pnl >= FIXED_PROFIT_RATIO;\\n shouldSL = pnl <= -FIXED_LOSS_RATIO;\\n profitTarget = pos.Price * (isLong ? (1 + FIXED_PROFIT_RATIO) : (1 - FIXED_PROFIT_RATIO));\\n stopLoss = pos.Price * (isLong ? (1 - FIXED_LOSS_RATIO) : (1 + FIXED_LOSS_RATIO));\\n } else {\\n // 智能模式(使用AI设置的目标)\\n const exitPlan = _G(`exit_plan_${contract}`);\\n \\n if (exitPlan?.profit_target && exitPlan?.stop_loss) {\\n profitTarget = exitPlan.profit_target;\\n stopLoss = exitPlan.stop_loss;\\n\\n shouldTP = isLong ? currentPrice >= profitTarget : currentPrice <= profitTarget;\\n shouldSL = isLong ? currentPrice <= stopLoss : currentPrice >= stopLoss;\\n } else {\\n // 如果没有AI计划,回退到固定模式\\n shouldTP = pnl >= FIXED_PROFIT_RATIO;\\n shouldSL = pnl <= -FIXED_LOSS_RATIO;\\n profitTarget = pos.Price * (isLong ? (1 + FIXED_PROFIT_RATIO) : (1 - FIXED_PROFIT_RATIO));\\n stopLoss = pos.Price * (isLong ? (1 - FIXED_LOSS_RATIO) : (1 + FIXED_LOSS_RATIO));\\n }\\n }\\n \\n // 执行止盈\\n if (shouldTP) {\\n const success = closePosition(contract, pos, isLong, \\\"止盈\\\", pnl);\\n return success ? \\n { status: \\\"closed\\\", reason: \\\"profit\\\", pnl: pnl } : \\n { status: \\\"close_failed\\\" };\\n }\\n \\n // 执行止损\\n if (shouldSL) {\\n const success = closePosition(contract, pos, isLong, \\\"止损\\\", pnl);\\n return success ? \\n { status: \\\"closed\\\", reason: \\\"loss\\\", pnl: pnl } : \\n { status: \\\"close_failed\\\" };\\n }\\n \\n // 继续监控\\n return { \\n status: \\\"monitoring\\\", \\n pnl: pnl, \\n position: pos, \\n currentPrice: currentPrice,\\n isLong: isLong,\\n profitTarget: profitTarget,\\n stopLoss: stopLoss\\n };\\n \\n } catch (e) {\\n Log(`❌ 监控${contract}失败: ${e.message}`);\\n return { status: \\\"error\\\", error: e.message };\\n }\\n}\\n\\n/**\\n * 获取合约表现数据\\n */\\nfunction getContractPerformanceData() {\\n try {\\n const tradesHistory = _G('tradesHistory') || {};\\n const performanceData = {};\\n \\n Object.keys(tradesHistory).forEach(contract => {\\n const orders = tradesHistory[contract] || [];\\n performanceData[contract] = analyzePerformance(orders, contract);\\n });\\n \\n return performanceData;\\n } catch (e) {\\n Log(`⚠️ 获取表现数据失败: ${e.message}`);\\n return {};\\n }\\n}\\n\\n/**\\n * 分析交易表现 - 商品期货版\\n */\\nfunction analyzePerformance(orders, contract) {\\n if (!orders || orders.length === 0) {\\n return { contract, totalTrades: 0, totalProfit: 0, freezeStatus: 'free' };\\n }\\n \\n const filteredOrders = orders.filter(o => o.Status === 1);\\n const validOrders = filteredOrders.sort((a, b) => a.time - b.time);\\n \\n if (validOrders.length === 0) {\\n return { contract, totalTrades: 0, totalProfit: 0, freezeStatus: 'free' };\\n }\\n \\n // 获取合约乘数\\n const markets = _G('markets');\\n const volumeMultiple = markets && markets[contract] ? \\n (markets[contract].VolumeMultiple || 10) : 10;\\n \\n let trades = [];\\n let positions = [];\\n \\n // 交易配对逻辑\\n for (let order of validOrders) {\\n const { Type: type, DealAmount: amount, AvgPrice: price, time, Id: id } = order;\\n \\n let remaining = amount;\\n \\n for (let i = 0; i < positions.length && remaining > 0.0001; i++) {\\n const pos = positions[i];\\n \\n // 期货:Type为偶数是多单,奇数是空单\\n const isOrderBuy = (type % 2) === 0;\\n const isPosBuy = (pos.type % 2) === 0;\\n \\n // 平仓条件:多单遇到空单,或空单遇到多单\\n if (isOrderBuy !== isPosBuy) {\\n const tradeAmount = Math.min(remaining, pos.amount);\\n const holdTime = time - pos.time;\\n \\n let profit, direction;\\n if (isPosBuy) {\\n // 平多仓\\n profit = (price - pos.price) * tradeAmount * volumeMultiple;\\n direction = 'LONG';\\n } else {\\n // 平空仓\\n profit = (pos.price - price) * tradeAmount * volumeMultiple;\\n direction = 'SHORT';\\n }\\n \\n trades.push({\\n direction,\\n openPrice: pos.price,\\n closePrice: price,\\n amount: tradeAmount,\\n profit,\\n holdTime: holdTime,\\n closeTime: time\\n });\\n \\n pos.amount -= tradeAmount;\\n remaining -= tradeAmount;\\n \\n if (pos.amount <= 0.0001) {\\n positions.splice(i, 1);\\n i--;\\n }\\n }\\n }\\n \\n if (remaining > 0.0001) {\\n positions.push({\\n type: type,\\n price: price,\\n amount: remaining,\\n time: time,\\n id: id\\n });\\n }\\n }\\n \\n if (trades.length === 0) {\\n return { \\n contract, \\n totalTrades: 0, \\n totalProfit: 0,\\n winRate: 0,\\n avgProfit: 0,\\n maxProfit: 0,\\n maxLoss: 0,\\n profitLossRatio: 0,\\n avgHoldTimeHours: 0,\\n longWinProfit: 0,\\n shortWinProfit: 0,\\n freezeStatus: 'free'\\n };\\n }\\n \\n // 计算统计数据\\n trades.sort((a, b) => a.closeTime - b.closeTime);\\n \\n const wins = trades.filter(t => t.profit > 0);\\n const losses = trades.filter(t => t.profit <= 0);\\n const longTrades = trades.filter(t => t.direction === 'LONG');\\n const shortTrades = trades.filter(t => t.direction === 'SHORT');\\n \\n const longWins = longTrades.filter(t => t.profit > 0);\\n const shortWins = shortTrades.filter(t => t.profit > 0);\\n \\n const totalProfit = trades.reduce((sum, t) => sum + t.profit, 0);\\n const totalWinProfit = wins.reduce((sum, t) => sum + t.profit, 0);\\n const totalLossProfit = Math.abs(losses.reduce((sum, t) => sum + t.profit, 0));\\n const winRate = (wins.length / trades.length * 100);\\n \\n // 检查冷却状态\\n const frozenData = _G(`frozen_${contract}`);\\n const currentTime = Date.now();\\n const freezeStatus = (frozenData && frozenData.freezeEndTime > currentTime) ? 'frozen' : 'free';\\n \\n // 计算最近4小时的连续亏损\\n function calculateRecentConsecutiveLosses(tradesArray) {\\n const currentTime = Date.now();\\n const fourHoursAgo = currentTime - (4 * 60 * 60 * 1000);\\n \\n const recentTrades = tradesArray.filter(trade => trade.closeTime >= fourHoursAgo);\\n \\n if (recentTrades.length === 0) {\\n return 0;\\n }\\n \\n recentTrades.sort((a, b) => a.closeTime - b.closeTime);\\n \\n let consecutiveLosses = 0;\\n for (let i = recentTrades.length - 1; i >= 0; i--) {\\n if (recentTrades[i].profit <= 0) {\\n consecutiveLosses++;\\n } else {\\n break;\\n }\\n }\\n \\n return consecutiveLosses;\\n }\\n \\n const consecutiveLosses = calculateRecentConsecutiveLosses(trades);\\n \\n return {\\n contract,\\n totalTrades: trades.length,\\n totalProfit: parseFloat(totalProfit.toFixed(2)),\\n winRate: parseFloat(winRate.toFixed(2)),\\n avgProfit: parseFloat((totalProfit / trades.length).toFixed(2)),\\n maxProfit: wins.length > 0 ? parseFloat(Math.max(...wins.map(t => t.profit)).toFixed(2)) : 0,\\n maxLoss: parseFloat(Math.min(...trades.map(t => t.profit)).toFixed(2)),\\n profitLossRatio: totalLossProfit > 0 ? parseFloat((totalWinProfit / totalLossProfit).toFixed(2)) : (totalWinProfit > 0 ? 999 : 0),\\n avgHoldTimeHours: parseFloat((trades.reduce((sum, t) => sum + t.holdTime, 0) / trades.length / (1000 * 60 * 60)).toFixed(2)),\\n longWinProfit: parseFloat(longWins.reduce((sum, t) => sum + t.profit, 0).toFixed(2)),\\n shortWinProfit: parseFloat(shortWins.reduce((sum, t) => sum + t.profit, 0).toFixed(2)),\\n consecutiveLosses: consecutiveLosses,\\n freezeStatus: freezeStatus\\n };\\n}\\n\\n/**\\n * 创建AI信号表\\n */\\nfunction createMonitoringTable() {\\n const signalTable = {\\n type: \\\"table\\\",\\n title: \\\"🤖 AI智能体\\\",\\n cols: [\\\"合约\\\", \\\"信号类型\\\", \\\"执行状态\\\", \\\"置信度\\\", \\\"止盈目标\\\", \\\"止损价位\\\", \\\"风险金额\\\", \\\"理由\\\"],\\n rows: []\\n };\\n \\n try {\\n const aiSignals = _G('AISignal') || [];\\n const executionData = _G('latestExecutionResults') || { results: {} };\\n const executionResults = executionData.results || {};\\n \\n if (!aiSignals || aiSignals.length === 0) {\\n signalTable.rows.push([\\n \\\"📭 暂无信号\\\",\\n \\\"⏳ 等待AI分析\\\",\\n \\\"⏭️ 待分析\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"等待AI智能体生成交易信号\\\"\\n ]);\\n return signalTable;\\n }\\n \\n aiSignals.forEach(aiSignal => {\\n if (!aiSignal || !aiSignal.contract) return;\\n \\n const contract = aiSignal.contract;\\n const executionResult = executionResults[contract];\\n \\n let signalDisplay = aiSignal.signal;\\n let signalEmoji = \\\"⏸️\\\";\\n \\n switch (aiSignal.signal) {\\n case 'buy_to_enter':\\n signalDisplay = \\\"🟢 做多入场\\\";\\n signalEmoji = \\\"📈\\\";\\n break;\\n case 'sell_to_enter':\\n signalDisplay = \\\"🔴 做空入场\\\";\\n signalEmoji = \\\"📉\\\";\\n break;\\n case 'close':\\n signalDisplay = \\\"⏹️ 平仓\\\";\\n signalEmoji = \\\"🔚\\\";\\n break;\\n case 'hold':\\n signalDisplay = \\\"⏸️ 持仓观望\\\";\\n signalEmoji = \\\"⏳\\\";\\n break;\\n }\\n \\n let statusDisplay = \\\"\\\";\\n if (executionResult) {\\n if (executionResult.executed) {\\n statusDisplay = \\\"✅ 已执行\\\";\\n } else if (executionResult.skipReason) {\\n statusDisplay = `⏭️ ${executionResult.skipReason}`;\\n } else {\\n statusDisplay = \\\"❌ 执行失败\\\";\\n }\\n } else {\\n statusDisplay = aiSignal.signal === 'hold' ? \\\"⏭️ 持仓观望\\\" : \\\"⏭️ 等待执行\\\";\\n }\\n \\n const confidence = aiSignal.confidence ? (aiSignal.confidence * 100).toFixed(0) : 0;\\n let confidenceDisplay = \\\"-\\\";\\n if (confidence >= 80) {\\n confidenceDisplay = `🔥 ${confidence}%`;\\n } else if (confidence >= 60) {\\n confidenceDisplay = `⚡ ${confidence}%`;\\n } else if (confidence > 0) {\\n confidenceDisplay = `⚠️ ${confidence}%`;\\n }\\n \\n let biasEmoji = \\\"\\\";\\n switch (aiSignal.historical_bias) {\\n case 'LONG':\\n biasEmoji = \\\"📈\\\";\\n break;\\n case 'SHORT':\\n biasEmoji = \\\"📉\\\";\\n break;\\n case 'BALANCED':\\n biasEmoji = \\\"⚖️\\\";\\n break;\\n }\\n \\n signalTable.rows.push([\\n `💎 ${contract}`,\\n `${signalEmoji} ${signalDisplay}`,\\n statusDisplay,\\n confidenceDisplay,\\n aiSignal.profit_target && aiSignal.profit_target > 0 ? `🎯 ${_N(aiSignal.profit_target, 2)}` : \\\"-\\\",\\n aiSignal.stop_loss && aiSignal.stop_loss > 0 ? `🛡️ ${_N(aiSignal.stop_loss, 2)}` : \\\"-\\\",\\n aiSignal.risk_usd && aiSignal.risk_usd > 0 ? `💰 ¥${_N(parseFloat(aiSignal.risk_usd), 2)}` : \\\"-\\\",\\n `${biasEmoji} ${aiSignal.justification || \\\"无详细说明\\\"}`\\n ]);\\n });\\n \\n if (executionData.timestamp) {\\n const executionTime = new Date(executionData.timestamp).toLocaleTimeString();\\n const executedCount = Object.values(executionResults).filter(r => r.executed).length;\\n const totalSignals = executionData.totalSignals || 0;\\n \\n signalTable.rows.push([\\n \\\"📊 执行汇总\\\",\\n `${totalSignals} 个信号`,\\n `✅ ${executedCount} 个已执行`,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n `最后更新: ${executionTime}`\\n ]);\\n }\\n \\n } catch (e) {\\n Log(`❌ 创建AI信号表失败: ${e.message}`);\\n signalTable.rows.push([\\n \\\"❌ 错误\\\",\\n \\\"获取信号失败\\\",\\n \\\"🚨\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n e.message.substring(0, 50)\\n ]);\\n }\\n \\n return signalTable;\\n}\\n\\n/**\\n * 创建持仓表\\n */\\nfunction createPositionTable() {\\n const positionTable = {\\n type: \\\"table\\\",\\n title: \\\"💼 当前持仓状态\\\",\\n cols: [\\\"合约\\\", \\\"方向\\\", \\\"数量\\\", \\\"入场价\\\", \\\"当前价\\\", \\\"盈亏%\\\", \\\"盈亏¥\\\", \\\"止盈\\\", \\\"止损\\\"],\\n rows: []\\n };\\n \\n try {\\n // 获取所有合约列表\\n const markets = _G('markets');\\n if (!markets) {\\n positionTable.rows.push([\\n \\\"❌ 错误\\\",\\n \\\"未找到合约列表\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\"\\n ]);\\n return positionTable;\\n }\\n \\n const contracts = Object.keys(markets);\\n let totalPnl = 0;\\n let positionCount = 0;\\n \\n // 遍历每个合约检查持仓\\n contracts.forEach(contract => {\\n try {\\n exchange.SetContractType(contract);\\n \\n // 获取该合约的持仓\\n const positions = exchange.GetPosition(contract);\\n \\n if (positions && positions.length > 0) {\\n positions.forEach(pos => {\\n if (Math.abs(pos.Amount) > 0) {\\n const ticker = exchange.GetTicker();\\n \\n if (ticker) {\\n const isLong = (pos.Type % 2) === 0;\\n const currentPrice = ticker.Last;\\n const pnlPercent = ((currentPrice - pos.Price) * (isLong ? 1 : -1) / pos.Price * 100);\\n \\n // 获取合约乘数计算实际盈亏\\n const volumeMultiple = markets[contract].VolumeMultiple || 10;\\n const pnlYuan = (currentPrice - pos.Price) * (isLong ? 1 : -1) * Math.abs(pos.Amount) * volumeMultiple;\\n \\n let pnlPercentDisplay = \\\"\\\";\\n let pnlYuanDisplay = \\\"\\\";\\n if (pnlPercent > 0) {\\n pnlPercentDisplay = `🟢 +${pnlPercent.toFixed(2)}%`;\\n pnlYuanDisplay = `🟢 +¥${pnlYuan.toFixed(2)}`;\\n } else if (pnlPercent < 0) {\\n pnlPercentDisplay = `🔴 ${pnlPercent.toFixed(2)}%`;\\n pnlYuanDisplay = `🔴 ¥${pnlYuan.toFixed(2)}`;\\n } else {\\n pnlPercentDisplay = `⚪ ${pnlPercent.toFixed(2)}%`;\\n pnlYuanDisplay = `⚪ ¥${pnlYuan.toFixed(2)}`;\\n }\\n \\n const exitPlan = _G(`exit_plan_${contract}`);\\n \\n positionTable.rows.push([\\n `💎 ${contract}`,\\n isLong ? \\\"📈 多\\\" : \\\"📉 空\\\",\\n _N(Math.abs(pos.Amount), 0),\\n `${_N(pos.Price, 2)}`,\\n `${_N(currentPrice, 2)}`,\\n pnlPercentDisplay,\\n pnlYuanDisplay,\\n exitPlan?.profit_target ? `${_N(exitPlan.profit_target, 2)}` : \\\"-\\\",\\n exitPlan?.stop_loss ? `${_N(exitPlan.stop_loss, 2)}` : \\\"-\\\"\\n ]);\\n \\n totalPnl += pnlPercent;\\n positionCount++;\\n }\\n }\\n });\\n }\\n \\n Sleep(200);\\n \\n } catch (e) {\\n Log(`⚠️ 检查${contract}持仓失败: ${e.message}`);\\n }\\n });\\n \\n if (positionCount > 0) {\\n const avgPnl = totalPnl / positionCount;\\n let summaryEmoji = avgPnl > 0 ? \\\"📊 ✅\\\" : \\\"📊 ⚠️\\\";\\n positionTable.rows.push([\\n `${summaryEmoji} 汇总`,\\n `${positionCount} 个持仓`,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n `平均: ${avgPnl > 0 ? '🟢' : '🔴'} ${_N(avgPnl, 2)}%`,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\"\\n ]);\\n } else {\\n positionTable.rows.push([\\n \\\"📭 空仓\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\"\\n ]);\\n }\\n \\n } catch (e) {\\n Log(`❌ 创建持仓表失败: ${e.message}`);\\n }\\n \\n return positionTable;\\n}\\n\\n/**\\n * 创建表现统计表 - 商品期货版\\n */\\nfunction createPerformanceTable() {\\n const performanceTable = {\\n type: \\\"table\\\",\\n title: \\\"📈 各合约交易表现 (按总盈亏排序)\\\",\\n cols: [\\\"合约\\\", \\\"总交易\\\", \\\"总盈亏\\\", \\\"胜率%\\\", \\\"平均盈亏\\\", \\\"最大盈利\\\", \\\"最大亏损\\\", \\\"盈亏比\\\", \\\"冷却状态\\\"],\\n rows: []\\n };\\n \\n try {\\n const performanceData = getContractPerformanceData();\\n const contractPerformances = Object.values(performanceData)\\n .filter(r => !r.error)\\n .sort((a, b) => b.totalProfit - a.totalProfit);\\n \\n if (contractPerformances.length === 0) {\\n performanceTable.rows.push([\\n \\\"📭 暂无数据\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"等待交易数据\\\"\\n ]);\\n } else {\\n contractPerformances.forEach(performance => {\\n // 冷却状态显示\\n let freezeDisplay = \\\"🟢 正常\\\";\\n if (performance.freezeStatus === 'frozen') {\\n freezeDisplay = `🧊 冷却中 (${performance.consecutiveLosses}连亏)`;\\n }\\n \\n performanceTable.rows.push([\\n `💎 ${performance.contract}`,\\n performance.totalTrades,\\n performance.totalProfit > 0 ? `🟢 ¥${performance.totalProfit}` : \\n performance.totalProfit < 0 ? `🔴 ¥${performance.totalProfit}` : `⚪ ¥0`,\\n performance.winRate > 0 ? `${performance.winRate}%` : \\\"0%\\\",\\n performance.avgProfit > 0 ? `🟢 ¥${performance.avgProfit}` : \\n performance.avgProfit < 0 ? `🔴 ¥${performance.avgProfit}` : `⚪ ¥0`,\\n performance.maxProfit > 0 ? `¥${performance.maxProfit}` : \\\"¥0\\\",\\n performance.maxLoss < 0 ? `¥${performance.maxLoss}` : \\\"¥0\\\",\\n performance.profitLossRatio > 0 ? `${performance.profitLossRatio}` : \\\"0\\\",\\n freezeDisplay\\n ]);\\n });\\n \\n // 添加汇总行\\n const totalTrades = contractPerformances.reduce((sum, p) => sum + p.totalTrades, 0);\\n const totalProfit = contractPerformances.reduce((sum, p) => sum + p.totalProfit, 0);\\n const frozenCount = contractPerformances.filter(p => p.freezeStatus === 'frozen').length;\\n \\n performanceTable.rows.push([\\n \\\"📊 汇总\\\",\\n totalTrades,\\n totalProfit > 0 ? `🟢 ¥${_N(totalProfit, 2)}` : \\n totalProfit < 0 ? `🔴 ¥${_N(totalProfit, 2)}` : `⚪ ¥0`,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n `${frozenCount} 个冷却中`\\n ]);\\n }\\n \\n } catch (e) {\\n Log(`❌ 创建表现表失败: ${e.message}`);\\n performanceTable.rows.push([\\n \\\"❌ 错误\\\",\\n \\\"获取统计失败\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n e.message.substring(0, 20)\\n ]);\\n }\\n \\n return performanceTable;\\n}\\n\\n/**\\n * 创建系统状态表\\n */\\nfunction createSystemStatusTable() {\\n const statusTable = {\\n type: \\\"table\\\",\\n title: \\\"📊 整体分析表\\\",\\n cols: [\\\"分析指标\\\", \\\"数值\\\", \\\"状态\\\", \\\"说明\\\"],\\n rows: []\\n };\\n \\n try {\\n const currentAccount = exchange.GetAccount();\\n const currentAccountValue = currentAccount.Equity;\\n const initMoney = _G('initmoney') || currentAccountValue;\\n const totalProfitYuan = currentAccountValue - initMoney;\\n const totalReturnPercent = ((currentAccountValue - initMoney) / initMoney * 100);\\n\\n LogProfit(totalProfitYuan, \\\"&\\\");\\n \\n // 获取交易统计\\n const performanceData = getContractPerformanceData();\\n const contractPerformances = Object.values(performanceData).filter(r => !r.error);\\n const totalTrades = contractPerformances.reduce((sum, p) => sum + p.totalTrades, 0);\\n const avgWinRate = contractPerformances.length > 0 ? \\n contractPerformances.reduce((sum, p) => sum + p.winRate, 0) / contractPerformances.length : 0;\\n const frozenCount = contractPerformances.filter(p => p.freezeStatus === 'frozen').length;\\n const activeContracts = contractPerformances.filter(p => p.totalTrades > 0).length;\\n \\n // 持仓统计\\n const markets = _G('markets');\\n let activePositions = 0;\\n if (markets) {\\n const contracts = Object.keys(markets);\\n contracts.forEach(contract => {\\n try {\\n exchange.SetContractType(contract);\\n const positions = exchange.GetPosition(contract);\\n if (positions) {\\n activePositions += positions.filter(p => Math.abs(p.Amount) > 0).length;\\n }\\n Sleep(100);\\n } catch (e) {\\n // 忽略错误\\n }\\n });\\n }\\n \\n statusTable.rows.push([\\n \\\"💰 账户权益\\\",\\n `¥${_N(currentAccountValue, 2)}`,\\n \\\"📊\\\",\\n `初始资金: ¥${_N(initMoney, 2)}`\\n ]);\\n \\n statusTable.rows.push([\\n \\\"💵 总盈亏\\\",\\n totalProfitYuan > 0 ? `🟢 +¥${_N(totalProfitYuan, 2)}` : \\n totalProfitYuan < 0 ? `🔴 ¥${_N(totalProfitYuan, 2)}` : `⚪ ¥0`,\\n totalProfitYuan > 0 ? \\\"✅ 盈利\\\" : totalProfitYuan < 0 ? \\\"⚠️ 亏损\\\" : \\\"⚪ 持平\\\",\\n \\\"实际账户盈亏\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"📈 总收益率\\\",\\n totalReturnPercent > 0 ? `🟢 +${_N(totalReturnPercent, 2)}%` : \\n totalReturnPercent < 0 ? `🔴 ${_N(totalReturnPercent, 2)}%` : `⚪ 0%`,\\n totalReturnPercent > 5 ? \\\"✅ 优秀\\\" : totalReturnPercent > 0 ? \\\"✅ 良好\\\" : \\\"⚠️ 需改进\\\",\\n \\\"相对初始资金\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"🎲 总交易笔数\\\",\\n totalTrades.toString(),\\n totalTrades > 50 ? \\\"✅ 充足\\\" : totalTrades > 10 ? \\\"✅ 良好\\\" : \\\"⚠️ 偏少\\\",\\n \\\"所有合约合计\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"🎯 平均胜率\\\",\\n `${_N(avgWinRate, 1)}%`,\\n avgWinRate > 60 ? \\\"✅ 优秀\\\" : avgWinRate > 45 ? \\\"✅ 良好\\\" : \\\"⚠️ 需改进\\\",\\n \\\"各合约胜率平均值\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"💼 当前持仓\\\",\\n `${activePositions} 个`,\\n activePositions > 0 ? \\\"✅ 持仓中\\\" : \\\"📭 空仓\\\",\\n \\\"活跃持仓数量\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"🧊 冷却合约\\\",\\n `${frozenCount} 个`,\\n frozenCount === 0 ? \\\"✅ 全部正常\\\" : frozenCount > 3 ? \\\"⚠️ 较多冷却\\\" : \\\"🟡 部分冷却\\\",\\n \\\"连续亏损保护中\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"📊 活跃合约\\\",\\n `${activeContracts} 个`,\\n activeContracts > 5 ? \\\"✅ 多样化\\\" : activeContracts > 2 ? \\\"✅ 良好\\\" : \\\"⚠️ 单一\\\",\\n \\\"有交易记录的合约\\\"\\n ]);\\n \\n statusTable.rows.push([\\n \\\"🎯 止盈止损\\\",\\n sltpChoice ? \\\"固定模式\\\" : \\\"智能模式\\\",\\n \\\"✅ 运行中\\\",\\n sltpChoice ? \\n `止盈:${(FIXED_PROFIT_RATIO*100).toFixed(1)}% 止损:${(FIXED_LOSS_RATIO*100).toFixed(1)}%` : \\n \\\"使用AI设定目标\\\"\\n ]);\\n \\n } catch (e) {\\n Log(`❌ 创建整体分析表失败: ${e.message}`);\\n statusTable.rows.push([\\n \\\"❌ 错误\\\",\\n \\\"计算失败\\\",\\n \\\"🚨\\\",\\n e.message.substring(0, 30)\\n ]);\\n }\\n \\n return statusTable;\\n}\\n\\n/**\\n * 显示监控仪表板\\n */\\nfunction displayMonitoringDashboard() {\\n try {\\n const signalTable = createMonitoringTable();\\n const positionTable = createPositionTable();\\n const performanceTable = createPerformanceTable();\\n const systemStatusTable = createSystemStatusTable();\\n \\n const dashboardDisplay = \\n '`' + JSON.stringify(signalTable) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(positionTable) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(performanceTable) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(systemStatusTable) + '`';\\n \\n LogStatus(dashboardDisplay);\\n \\n } catch (e) {\\n Log(`❌ 显示监控仪表板失败: ${e.message}`);\\n }\\n}\\n\\n/**\\n * 主函数\\n */\\nfunction main() {\\n try {\\n const monitoringResults = {};\\n let totalActions = 0;\\n \\n // 获取所有合约列表\\n const markets = _G('markets');\\n if (!markets) {\\n Log('❌ 未找到合约列表');\\n return { error: '未找到合约列表' };\\n }\\n \\n const contracts = Object.keys(markets);\\n \\n // 遍历每个合约进行监控\\n contracts.forEach(contract => {\\n try {\\n const result = monitorPosition(contract);\\n \\n monitoringResults[contract] = result;\\n \\n if (result.status === \\\"closed\\\") {\\n const reasonEmoji = result.reason === \\\"profit\\\" ? \\\"🎉\\\" : \\\"💔\\\";\\n Log(`${reasonEmoji} ${contract} 已自动平仓 - ${result.reason === \\\"profit\\\" ? \\\"止盈\\\" : \\\"止损\\\"} ${(result.pnl * 100).toFixed(2)}%`);\\n totalActions++;\\n }\\n \\n Sleep(500);\\n \\n } catch (e) {\\n Log(`⚠️ 监控${contract}出错: ${e.message}`);\\n }\\n });\\n \\n // 显示仪表板\\n displayMonitoringDashboard();\\n \\n return {\\n monitoringResults: monitoringResults,\\n totalActions: totalActions,\\n timestamp: Date.now()\\n };\\n \\n } catch (e) {\\n Log(`❌ 主执行流程失败: ${e.message}`);\\n return { error: e.message };\\n }\\n}\\n\\nreturn main();\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[48,592],\"id\":\"9f24748a-c37e-4f5c-a031-33a808607c2e\",\"name\":\"止盈止损代码\"},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":15}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-112,240],\"id\":\"fe024c67-16b8-4b71-a7da-645b93a8b43d\",\"name\":\"策略执行触发器\",\"logLevel\":0},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"seconds\",\"secondsInterval\":15}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-112,592],\"id\":\"8776e4d7-d7d0-4aba-9001-c022ab2e433d\",\"name\":\"止盈止损触发器\",\"logLevel\":0}],\"pinData\":{},\"connections\":{\"合并\":{\"main\":[[{\"node\":\"数据合并\",\"type\":\"main\",\"index\":0}]]},\"AI 智能体\":{\"main\":[[{\"node\":\"交易执行\",\"type\":\"main\",\"index\":0}]]},\"参数重置\":{\"main\":[[{\"node\":\"市场数据获取\",\"type\":\"main\",\"index\":0},{\"node\":\"持仓数据获取\",\"type\":\"main\",\"index\":0},{\"node\":\"合约表现统计\",\"type\":\"main\",\"index\":0}]]},\"OpenAI 模型\":{\"ai_languageModel\":[[{\"node\":\"AI 智能体\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"市场数据获取\":{\"main\":[[{\"node\":\"合并\",\"type\":\"main\",\"index\":0}]]},\"持仓数据获取\":{\"main\":[[{\"node\":\"合并\",\"type\":\"main\",\"index\":1}]]},\"数据合并\":{\"main\":[[{\"node\":\"AI 智能体\",\"type\":\"main\",\"index\":0}]]},\"合约表现统计\":{\"main\":[[{\"node\":\"合并\",\"type\":\"main\",\"index\":2}]]},\"策略执行触发器\":{\"main\":[[{\"node\":\"参数重置\",\"type\":\"main\",\"index\":0}]]},\"止盈止损触发器\":{\"main\":[[{\"node\":\"止盈止损代码\",\"type\":\"main\",\"index\":0}]]}},\"active\":false,\"settings\":{\"timezone\":\"Asia/Shanghai\",\"executionOrder\":\"v1\"},\"tags\":[],\"credentials\":{},\"id\":\"05c0f6f3-9ba1-4d3a-abe6-9e94581080d6\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"策略执行触发器\"}}"}