策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"hours\",\"hoursInterval\":4,\"triggerAtMinute\":0}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-512,16],\"id\":\"cfd83d9d-f6a9-40ba-a5a0-2de140ee832f\",\"name\":\"定时触发器\"},{\"parameters\":{\"text\":\"=你是一位专业的交易监督顾问,负责对量化策略的交易决策进行把关。你的主要职责是:\\n- 在关键时刻提供第二层判断\\n- 识别技术信号与市场环境的矛盾\\n- 捕捉重大风险信号\\n- 避免策略在极端情况下的误判\\n\\n**你不是**: 主策略执行者,不需要过度干预每个细节决策\\n**你是**: 最后一道防线,在重要关口提供专业意见\\n\\n---\\n\\n## 重要声明:基于现有信息决策\\n\\n⚠️ **数据完整性说明**:\\n\\n由于市场环境和系统限制,输入数据可能存在以下情况:\\n- ✅ 技术指标可能计算不全 (某些币种数据不足)\\n- ✅ 新闻获取可能缺失 (recentNews为空数组或数量少于5条,或新闻和币种无关)\\n- ✅ 部分字段可能为null或0\\n\\n**你的决策原则**:\\n1. **基于现有信息判断** - 不要因为信息不全就拒绝决策\\n2. **优先参考持仓信息** - positionStatus和positionInfo最重要\\n3. **技术指标优先** - 即使无新闻,技术信号仍有效\\n4. **新闻为辅助** - 新闻缺失时,纯技术面判断\\n5. **保守但不僵化** - 信息不足时偏保守,但不要全部观望\\n\\n**特殊情况处理**:\\n\\n| 情况 | 决策方式 |\\n|------|---------|\\n| 新闻为空 | 仅依据技术指标(score)+持仓状态判断 |\\n| score接近0 | 依据新闻面+持仓状态判断 |\\n| 反向持仓+信息不全 | 偏向保守,建议平仓 |\\n| 同向持仓+信息不全 | 可持有观察 |\\n| 无持仓+信息不全 | 建议观望 |\\n\\n**必须完成的任务**:\\n- 对输入的每个币种都必须给出决策\\n- 不能因为信息不全就跳过币种\\n- 合理利用已有信息做出最佳判断\\n\\n---\\n\\n## 策略背景\\n\\n本策略采用多周期均线综合评分系统,通过三个维度评估币种强弱:\\n\\n1. **均线排列形态** (arrangementScore: -4到+4)\\n - 正值=多头排列,负值=空头排列\\n - 绝对值越大排列越完整\\n\\n2. **均线扩散间距** (gapScore)\\n - 正值=向上扩散,负值=向下扩散\\n - 绝对值越大趋势越强劲\\n\\n3. **均线时序变化** (timeSeriesScore: -4到+4)\\n - 正值=多数均线上升,负值=下降\\n - 绝对值越大方向越一致\\n\\n**综合得分计算**:\\n```\\nscore = 扩散间距 × 排列形态得分 × 时序变化得分\\n```\\n\\n**分组说明**:\\n- **正向组**: score>0,多头趋势,得分越大越强\\n- **负向组**: score<0,空头趋势,绝对值越大越强\\n\\n---\\n\\n## 输入数据结构\\n\\n### 1. 正向组(多头信号)\\n\\n**字段说明**:\\n```json\\n{\\n \\\"symbol\\\": \\\"BTCUSDT\\\", // 交易对 (必有)\\n \\\"processedSymbol\\\": \\\"BTC_USDT.swap\\\", // FMZ格式 (必有)\\n \\\"score\\\": 0.0234, // 综合得分 (必有,可能为0)\\n \\\"arrangementScore\\\": 4, // 排列形态 (必有)\\n \\\"gapScore\\\": 0.0156, // 扩散间距 (必有)\\n \\\"timeSeriesScore\\\": 3, // 时序变化 (必有)\\n \\\"bullCount\\\": 3, // 多头条件数 (必有)\\n \\\"positionStatus\\\": \\\"无持仓\\\", // 持仓状态 (必有,见下表)\\n \\\"positionInfo\\\": { // 持仓详情 (可能为null)\\n \\\"type\\\": 0, // 0=多仓, 1=空仓\\n \\\"amount\\\": 0.5,\\n \\\"price\\\": 45000.0,\\n \\\"profit\\\": 1250.5 // 浮盈(正)/浮亏(负)\\n },\\n \\\"recentNews\\\": [ // 最近新闻 (可能为空数组[])\\n {\\n \\\"title\\\": \\\"标题\\\",\\n \\\"url\\\": \\\"链接\\\",\\n \\\"description\\\": \\\"描述\\\",\\n \\\"time\\\": \\\"时间\\\"\\n }\\n ]\\n}\\n```\\n\\n---\\n\\n### 2. 负向组(空头信号)\\n\\n**字段说明**:\\n```json\\n{\\n \\\"symbol\\\": \\\"ETHUSDT\\\",\\n \\\"processedSymbol\\\": \\\"ETH_USDT.swap\\\",\\n \\\"score\\\": -0.0289, // 综合得分 (必有,可能为0)\\n \\\"arrangementScore\\\": -4, // 排列形态 (必有)\\n \\\"gapScore\\\": -0.0198, // 扩散间距 (必有)\\n \\\"timeSeriesScore\\\": -3, // 时序变化 (必有)\\n \\\"bearCount\\\": 3, // 空头条件数 (必有)\\n \\\"positionStatus\\\": \\\"无持仓\\\", // 持仓状态 (必有,见下表)\\n \\\"positionInfo\\\": { ... }, // 同上 (可能为null)\\n \\\"recentNews\\\": [ ... ], // 同上 (可能为空数组[])\\n \\\"newsCount\\\": 5 // 新闻数量 (可能为0)\\n}\\n```\\n\\n---\\n\\n## 持仓状态(positionStatus)完整说明\\n\\n### 正向组(多头信号)可能的positionStatus:\\n\\n| positionStatus | 含义 | positionInfo.type | 判断要点 |\\n|---------------|------|------------------|---------|\\n| `\\\"无持仓\\\"` | 无仓位,技术多头信号 | null | 评估是否开多 |\\n| `\\\"持有多仓\\\"` | 同向持仓,信号强(在前5) | 0 | 持有或加仓 |\\n| `\\\"持有多仓但指标强度不在前列\\\"` | 同向持仓,信号弱化(不在前5) | 0 | 观察或减仓 |\\n| `\\\"持有空仓\\\"` | ⚠️ **反向持仓**(技术转多但持有空仓) | 1 | **重点关注**:平仓或反手 |\\n\\n### 负向组(空头信号)可能的positionStatus:\\n\\n| positionStatus | 含义 | positionInfo.type | 判断要点 |\\n|---------------|------|------------------|---------|\\n| `\\\"无持仓\\\"` | 无仓位,技术空头信号 | null | 评估是否开空 |\\n| `\\\"持有空仓\\\"` | 同向持仓,信号强(在后5) | 1 | 持有或加仓 |\\n| `\\\"持有空仓但指标强度不在前列\\\"` | 同向持仓,信号弱化(不在后5) | 1 | 观察或减仓 |\\n| `\\\"持有多仓\\\"` | ⚠️ **反向持仓**(技术转空但持有多仓) | 0 | **重点关注**:平仓或反手 |\\n\\n**关键说明**:\\n- 同向持仓 = positionInfo.type与技术信号方向一致\\n- 反向持仓 = positionInfo.type与技术信号方向相反 (⚠️ 这是AI监督的核心关注点!)\\n- \\\"指标强度不在前列\\\" = 该持仓币种的技术信号已弱化,不在前5/后5强信号中\\n\\n---\\n\\n## 核心判断逻辑\\n\\n### 情景1: 无持仓 → 判断是否开仓\\n\\n**关注重点**:\\n- 技术信号强度 (score绝对值大小)\\n- 新闻面是否支持技术信号 (若有新闻)\\n- **关键风险**: 重大利空(监管/黑客/项目方问题)\\n\\n**决策表**:\\n\\n| 技术信号 | 新闻面 | 风险 | 建议决策 |\\n|---------|-------|------|---------|\\n| 强(score绝对值>0.05) | 支持 | 无 | `开多`/`开空` |\\n| 强 | 中性 | 无 | `开多`/`开空` |\\n| 强 | 无新闻 | 无 | `开多`/`开空` (纯技术) |\\n| 强 | 矛盾 | 无 | `观望` |\\n| 强 | 任何 | 重大利空 | `观望` |\\n| 中等(0.02-0.05) | 支持 | 无 | `开多`/`开空` |\\n| 中等 | 中性/无新闻 | 无 | `观望` |\\n| 中等 | 矛盾 | 无 | `观望` |\\n| 弱(<0.02) | 任何 | 无 | `观望` |\\n\\n---\\n\\n### 情景2: 同向持仓 → 判断是否继续持有\\n\\n**同向持仓定义**:\\n- 正向组中 positionStatus = `\\\"持有多仓\\\"` 或 `\\\"持有多仓但指标强度不在前列\\\"`\\n- 负向组中 positionStatus = `\\\"持有空仓\\\"` 或 `\\\"持有空仓但指标强度不在前列\\\"`\\n\\n**关注重点**:\\n- positionStatus中是否包含\\\"指标强度不在前列\\\"(信号弱化标志)\\n- 新闻面是否转向 (若有新闻)\\n- positionInfo.profit盈亏状态\\n\\n**决策表**:\\n\\n| 信号状态 | 新闻面 | profit | 建议决策 |\\n|---------|-------|--------|---------|\\n| 持有仓位(在前5/后5) | 稳定/支持/无新闻 | 任何 | `持有` |\\n| 持有仓位(在前5/后5) | 转向/利空 | 盈利 | `平仓` |\\n| 持有仓位(在前5/后5) | 转向/利空 | 亏损 | `平仓`(止损) |\\n| \\\"但指标强度不在前列\\\" | 稳定/无新闻 | 盈利 | `持有`(观察) |\\n| \\\"但指标强度不在前列\\\" | 稳定/无新闻 | 亏损 | `平仓`(止损) |\\n| \\\"但指标强度不在前列\\\" | 转向/利空 | 任何 | `平仓` |\\n| \\\"但指标强度不在前列\\\" | 新闻缺失 | 盈利 | `持有`(观察) |\\n| \\\"但指标强度不在前列\\\" | 新闻缺失 | 亏损 | `平仓`(止损) |\\n\\n**特别说明**:\\n- \\\"但指标强度不在前列\\\" 表示技术信号已弱化,需谨慎\\n- 新闻缺失时,主要依据技术信号+盈亏状态\\n- 盈利时可观察,亏损时建议止损\\n- 新闻面重大转向时,无论盈亏都建议平仓\\n\\n---\\n\\n### 情景3: 反向持仓 → 判断是否平仓/反手\\n\\n**反向持仓定义**:\\n- 正向组中 positionStatus = `\\\"持有空仓\\\"` (技术多头但持有空仓)\\n- 负向组中 positionStatus = `\\\"持有多仓\\\"` (技术空头但持有多仓)\\n\\n**这是AI监督者的核心价值场景!**\\n\\n**关注重点**:\\n- 反向信号强度 (score绝对值)\\n- 新闻面是否确认趋势反转 (若有新闻)\\n- 信号冲突的严重程度\\n\\n**决策表**:\\n\\n| score绝对值 | 新闻面 | 建议决策 |\\n|-----------|-------|---------|\\n| 强(>0.05) | 确认反转 | `平多开空`/`平空开多` (反手) |\\n| 强(>0.05) | 中性/无新闻 | `平多开空`/`平空开多` (反手) |\\n| 强(>0.05) | 矛盾 | `平仓` (存疑时保守) |\\n| 中等(0.02-0.05) | 确认反转 | `平多开空`/`平空开多` (反手) |\\n| 中等(0.02-0.05) | 中性/无新闻 | `平仓` (信号不够强) |\\n| 中等(0.02-0.05) | 矛盾 | `平仓` |\\n| 弱(<0.02) | 任何 | `持有`(观察,信号太弱) |\\n\\n**反手操作条件** (同时满足):\\n1. score绝对值 > 0.05 (强信号)\\n2. 新闻面明确支持反向,或中性/无新闻 (但如果新闻矛盾则只平仓)\\n3. 无重大风险 (如监管打击、黑客攻击)\\n\\n**注意**: \\n- 强信号+确认反转 = 果断反手\\n- 强信号+新闻矛盾 = 保守平仓 (技术和新闻打架时不冒险)\\n- 中等信号 = 只在新闻确认时才反手,否则平仓\\n- 弱信号 = 继续观察,不急于操作\\n\\n---\\n\\n## 必须警示的情况\\n\\n### ❌ 立即反对开仓/建议平仓:\\n\\n1. **监管打击**: 政府禁令、交易所下架公告\\n2. **安全事件**: 黑客攻击、合约漏洞、项目方跑路\\n3. **重大矛盾**: 技术强多头 + 新闻重大利空 (或相反)\\n4. **系统风险**: BTC暴跌>10%、市场整体恐慌\\n\\n### ✅ 可以放行:\\n\\n1. 技术信号强 + 新闻中性或支持或缺失\\n2. 同向持仓 + 信号延续 + 新闻面稳定或缺失\\n3. 同向持仓 + 信号轻微弱化 + 盈利状态 (可观察)\\n\\n---\\n\\n## 信息不全时的决策优先级\\n\\n**决策依据优先级** (从高到低):\\n\\n1. **positionStatus + profit** (最重要)\\n - 反向持仓+亏损 → 必须平仓\\n - 同向持仓+盈利 → 可持有\\n - 同向持仓+亏损+信号弱化 → 平仓\\n\\n2. **score技术信号** (次重要)\\n - 强信号(>0.05) → 可开仓/持有\\n - 弱信号(<0.02) → 观望/平仓\\n\\n3. **新闻面** (辅助参考)\\n - 有新闻 → 结合判断\\n - 无新闻 → 纯技术面决策\\n\\n**示例场景**:\\n```javascript\\n// 场景A: 新闻缺失,但技术+持仓信息完整\\n{\\n \\\"symbol\\\": \\\"BTCUSDT\\\",\\n \\\"score\\\": 0.0856, // 强多头信号\\n \\\"positionStatus\\\": \\\"无持仓\\\",\\n \\\"recentNews\\\": [] // 无新闻\\n}\\n// 决策: 开多 (技术信号强,无风险,纯技术判断)\\n\\n// 场景B: 反向持仓+新闻缺失\\n{\\n \\\"symbol\\\": \\\"ETHUSDT\\\",\\n \\\"score\\\": 0.0623, // 多头信号\\n \\\"positionStatus\\\": \\\"持有空仓\\\", // 反向持仓\\n \\\"positionInfo\\\": { \\\"profit\\\": -200 }, // 亏损\\n \\\"recentNews\\\": [] // 无新闻\\n}\\n// 决策: 平仓 (反向持仓+亏损,即使无新闻也建议止损)\\n\\n// 场景C: 同向持仓+信号弱化+新闻缺失\\n{\\n \\\"symbol\\\": \\\"SOLUSDT\\\",\\n \\\"score\\\": 0.0134, // 弱多头信号\\n \\\"positionStatus\\\": \\\"持有多仓但指标强度不在前列\\\",\\n \\\"positionInfo\\\": { \\\"profit\\\": 500 }, // 盈利\\n \\\"recentNews\\\": [] // 无新闻\\n}\\n// 决策: 持有 (同向持仓+盈利,信号虽弱但可观察)\\n```\\n\\n---\\n\\n## 输出格式\\n\\n**只输出JSON数组**, 每个币种包含:\\n```json\\n[\\n {\\n \\\"symbol\\\": \\\"BTCUSDT\\\",\\n \\\"currentPosition\\\": \\\"无持仓\\\",\\n \\\"score\\\": 0.0234,\\n \\\"newsAnalysis\\\": \\\"新闻缺失,纯技术面判断\\\",\\n \\\"overallJudgment\\\": \\\"技术多头强劲,无持仓,建议开多\\\",\\n \\\"decision\\\": \\\"开多\\\"\\n },\\n {\\n \\\"symbol\\\": \\\"ETHUSDT\\\",\\n \\\"currentPosition\\\": \\\"持有空仓\\\",\\n \\\"score\\\": 0.0623,\\n \\\"newsAnalysis\\\": \\\"机构质押增加,资金流入(或:新闻缺失)\\\",\\n \\\"overallJudgment\\\": \\\"反向持仓亏损,技术已转多,止损\\\",\\n \\\"decision\\\": \\\"平仓\\\"\\n }\\n]\\n```\\n\\n### 字段说明:\\n\\n- **symbol**: 币种代码 (如 \\\"BTCUSDT\\\")\\n- **currentPosition**: 直接使用positionStatus的值\\n- **score**: 综合得分 (保留4位小数)\\n- **newsAnalysis**: 新闻面分析 (20字内,若无新闻写\\\"新闻缺失,纯技术判断\\\")\\n- **overallJudgment**: 综合判断理由 (30字内,说明决策依据)\\n- **decision**: 交易决策 (见下表)\\n\\n### decision取值规范:\\n\\n| 当前状态 | 可用决策 | 使用场景 |\\n|---------|---------|---------|\\n| 无持仓 | `观望` | 信号弱或风险大或信息不全,暂不开仓 |\\n| 无持仓 | `开多` | 技术多头+新闻支持(或无新闻但信号强),开多仓 |\\n| 无持仓 | `开空` | 技术空头+新闻支持(或无新闻但信号强),开空仓 |\\n| 有持仓 | `持有` | 信号延续,继续持有现有仓位 |\\n| 有持仓 | `平仓` | 平掉当前仓位(同向弱化或反向强) |\\n| 持有多仓 | `平多开空` | 反手:平多仓后开空仓(需满足反手条件) |\\n| 持有空仓 | `平空开多` | 反手:平空仓后开多仓(需满足反手条件) |\\n\\n**注意**:\\n1. `观望` 只用于无持仓时\\n2. `持有` 只用于有持仓时\\n3. 反手操作需满足强信号+盈利条件\\n4. 必须包含所有输入的币种(正向组+负向组)\\n5. 新闻缺失时在newsAnalysis中明确说明\\n\\n---\\n\\n## 核心原则\\n\\n1. **基于现有信息决策** - 不因信息不全拒绝判断\\n2. **持仓信息优先** - positionStatus和profit最重要\\n3. **相信技术信号** - 即使无新闻,技术信号仍有效\\n4. **关注重大风险** - 识别技术面无法捕捉的风险\\n5. **识别反向持仓** - 这是监督者的核心价值\\n6. **保守但不僵化** - 信息不足时偏保守但不全部观望\\n7. **不过度干预** - 只在关键时刻提供判断\\n\\n---\\n\\n## 输入数据\\n\\n**正向组(多头信号)**:\\n{{JSON.stringify($node['实时新闻获取'].json.positive)}}\\n\\n**负向组(空头信号)**:\\n{{JSON.stringify($node['实时新闻获取'].json.negative)}}\\n\\n---\\n\\n**请直接输出JSON数组,不要有任何其他内容(无需```json标记)**\",\"options\":{}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[1056,160],\"id\":\"4e751178-96c9-48d3-b448-66e311d2a04c\",\"name\":\"AI 智能体\"},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"deepseek/deepseek-v3.2\",\"mode\":\"list\",\"cachedResultName\":\"deepseek/deepseek-v3.2\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[1056,336],\"id\":\"a66f062f-5a07-4214-8217-932c81e70257\",\"name\":\"OpenAI 模型\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 移动止盈止损监控系统 + 可视化仪表板 ==========\\n\\nif (_G('initmoney') === null) {\\n return {}\\n}\\n\\n// ========== 配置参数 ==========\\nconst CONFIG = {\\n TRAILING_STOP_PERCENT: $vars.lossPercent, // 移动止损回撤百分比\\n CHECK_INTERVAL: 500 // 检查间隔(毫秒)\\n};\\n\\n// ========== 工具函数 ==========\\n\\n/**\\n * 执行平仓操作\\n */\\nfunction closePosition(coin, pos, isLong, reason, currentPnl, maxPnl) {\\n try {\\n const closeAmount = pos.Amount;\\n \\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n exchange.SetDirection(isLong ? \\\"closebuy\\\" : \\\"closesell\\\");\\n \\n const orderId = isLong ? exchange.Sell(-1, closeAmount) : exchange.Buy(-1, closeAmount);\\n \\n if (orderId) {\\n Log(`✅ ${coin} ${reason} - 当前盈利: ${(currentPnl*100).toFixed(2)}% | 最高盈利: ${(maxPnl*100).toFixed(2)}% | 数量=${closeAmount}`);\\n \\n // 清除最高盈利记录\\n _G(`${coin}_USDT.swap_maxprofit`, null);\\n \\n return true;\\n } else {\\n Log(`❌ ${coin} 平仓失败`);\\n return false;\\n }\\n } catch (e) {\\n Log(`❌ ${coin} 平仓异常: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 移动止盈止损监控\\n */\\nfunction monitorPositionWithTrailingStop(coin) {\\n try {\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n \\n const positions = exchange.GetPositions();\\n const pos = positions && positions.find(p => p.Symbol.includes(coin) && Math.abs(p.Amount) > 0);\\n \\n if (!pos) {\\n return { status: \\\"no_position\\\" };\\n }\\n \\n const ticker = exchange.GetTicker();\\n if (!ticker) {\\n return { status: \\\"no_ticker\\\" };\\n }\\n \\n const isLong = pos.Type === PD_LONG || pos.Type === 0;\\n const currentPrice = ticker.Last;\\n const entryPrice = pos.Price;\\n \\n // 计算当前盈亏百分比\\n const currentPnl = (currentPrice - entryPrice) * (isLong ? 1 : -1) / entryPrice;\\n \\n // 获取历史最高盈利\\n const symbolKey = `${coin}_USDT.swap_maxprofit`;\\n let maxProfit = _G(symbolKey);\\n \\n // 如果是首次或者当前盈利更高,更新最高盈利\\n if (maxProfit === null || currentPnl - maxProfit > 0.001) {\\n maxProfit = currentPnl;\\n _G(symbolKey, maxProfit);\\n Log(`📈 ${coin} 更新最高盈利: ${(maxProfit * 100).toFixed(2)}%`);\\n }\\n \\n // 计算回撤\\n const drawdown = maxProfit - currentPnl;\\n \\n // 判断是否触发移动止损\\n let shouldClose = false;\\n \\n if (drawdown >= CONFIG.TRAILING_STOP_PERCENT) {\\n shouldClose = true;\\n Log(`🔔 ${coin} 触发移动止损! 最高盈利: ${(maxProfit*100).toFixed(2)}% | 当前盈利: ${(currentPnl*100).toFixed(2)}% | 回撤: ${(drawdown*100).toFixed(2)}%`);\\n }\\n \\n if (shouldClose) {\\n const closeSuccess = closePosition(coin, pos, isLong, \\\"移动止损\\\", currentPnl, maxProfit);\\n return closeSuccess ? \\n { status: \\\"closed\\\", reason: \\\"trailing_stop\\\", currentPnl: currentPnl, maxPnl: maxProfit } : \\n { status: \\\"close_failed\\\" };\\n }\\n \\n return {\\n status: \\\"monitoring\\\",\\n currentPnl: currentPnl,\\n maxPnl: maxProfit,\\n drawdown: drawdown,\\n position: pos,\\n currentPrice: currentPrice,\\n isLong: isLong\\n };\\n \\n } catch (e) {\\n Log(`❌ ${coin} 监控异常: ${e.message}`);\\n return { status: \\\"error\\\", error: e.message };\\n }\\n}\\n\\n// ========== 可视化图表函数 ==========\\n\\n/**\\n * 创建账户概览表\\n */\\nfunction createAccountOverviewTable() {\\n const accountTable = {\\n type: \\\"table\\\",\\n title: \\\"💰 账户概览\\\",\\n cols: [\\\"💵 初始权益\\\", \\\"💰 实时权益\\\", \\\"💸 收益(USD)\\\", \\\"📈 盈利率\\\", \\\"🛡️ 止损模式\\\"],\\n rows: []\\n };\\n \\n try {\\n const currentAccount = exchange.GetAccount();\\n const currentEquity = currentAccount.Equity;\\n const initMoney = _G('initmoney') || currentEquity;\\n const totalProfit = currentEquity - initMoney;\\n const profitPercent = ((currentEquity - initMoney) / initMoney * 100);\\n \\n // LogProfit\\n LogProfit(totalProfit, \\\"&\\\");\\n \\n // 状态行\\n const statusRow = [];\\n \\n // 初始权益 - 状态\\n statusRow.push(\\\"📊\\\");\\n \\n // 实时权益 - 状态\\n statusRow.push(currentEquity >= initMoney ? \\\"✅\\\" : \\\"⚠️\\\");\\n \\n // 收益状态\\n if (totalProfit > 0) {\\n statusRow.push(\\\"✅ 盈利\\\");\\n } else if (totalProfit < 0) {\\n statusRow.push(\\\"⚠️ 亏损\\\");\\n } else {\\n statusRow.push(\\\"⚪ 持平\\\");\\n }\\n \\n // 盈利率状态\\n if (profitPercent > 5) {\\n statusRow.push(\\\"🔥 优秀\\\");\\n } else if (profitPercent > 0) {\\n statusRow.push(\\\"✅ 良好\\\");\\n } else {\\n statusRow.push(\\\"⚠️ 需改进\\\");\\n }\\n \\n // 止损模式状态\\n statusRow.push(\\\"✅ 运行中\\\");\\n \\n // 数值行\\n const valueRow = [\\n `$${_N(initMoney, 2)}`,\\n `$${_N(currentEquity, 2)}`,\\n totalProfit > 0 ? `🟢 +$${_N(totalProfit, 2)}` : \\n totalProfit < 0 ? `🔴 $${_N(totalProfit, 2)}` : `⚪ $0`,\\n profitPercent > 0 ? `🟢 +${_N(profitPercent, 2)}%` : \\n profitPercent < 0 ? `🔴 ${_N(profitPercent, 2)}%` : `⚪ 0%`,\\n `移动止损 ${(CONFIG.TRAILING_STOP_PERCENT * 100).toFixed(1)}%`\\n ];\\n \\n accountTable.rows.push(valueRow);\\n accountTable.rows.push(statusRow);\\n \\n \\n } catch (e) {\\n Log(`❌ 创建账户概览表失败: ${e.message}`);\\n accountTable.rows.push([\\n \\\"❌ 错误\\\",\\n \\\"获取账户失败\\\",\\n \\\"🚨\\\",\\n \\\"🚨\\\",\\n \\\"🚨\\\"\\n ]);\\n }\\n \\n return accountTable;\\n}\\n\\n/**\\n * 创建AI决策表 - 按正负向分组\\n */\\nfunction createAIDecisionTable() {\\n const aiTable = {\\n type: \\\"table\\\",\\n title: \\\"🤖 AI决策信号\\\",\\n cols: [\\\"分组\\\", \\\"币种\\\", \\\"决策\\\", \\\"执行状态\\\", \\\"评分\\\", \\\"新闻分析\\\", \\\"综合判断\\\"],\\n rows: []\\n };\\n \\n try {\\n // 从_G获取最新执行结果\\n const executionData = _G('latestExecutionResults') || { results: {} };\\n const executionResults = executionData.results || {};\\n \\n if (Object.keys(executionResults).length === 0) {\\n aiTable.rows.push([\\n \\\"⏳\\\",\\n \\\"📭 暂无信号\\\",\\n \\\"⏳ 等待AI\\\",\\n \\\"⏭️ 待分析\\\",\\n \\\"-\\\",\\n \\\"等待AI生成交易决策\\\",\\n \\\"-\\\"\\n ]);\\n } else {\\n // 分组:正向组(score>0)和负向组(score<0)\\n const positiveResults = [];\\n const negativeResults = [];\\n \\n Object.entries(executionResults).forEach(([coin, result]) => {\\n if (result.score > 0) {\\n positiveResults.push([coin, result]);\\n } else if (result.score < 0) {\\n negativeResults.push([coin, result]);\\n } else {\\n // score=0的归入其他\\n positiveResults.push([coin, result]);\\n }\\n });\\n \\n // 格式化行的函数\\n const formatRow = (coin, result, groupIcon) => {\\n // 决策显示\\n let decisionDisplay = result.decision || result.signal || \\\"未知\\\";\\n let decisionEmoji = \\\"⏸️\\\";\\n \\n switch (result.decision || result.originalDecision) {\\n case '开多':\\n decisionDisplay = \\\"🟢 开多\\\";\\n decisionEmoji = \\\"📈\\\";\\n break;\\n case '开空':\\n decisionDisplay = \\\"🔴 开空\\\";\\n decisionEmoji = \\\"📉\\\";\\n break;\\n case '平仓':\\n decisionDisplay = \\\"⏹️ 平仓\\\";\\n decisionEmoji = \\\"🔚\\\";\\n break;\\n case '持有':\\n case '观望':\\n decisionDisplay = \\\"⏸️ 持有\\\";\\n decisionEmoji = \\\"⏳\\\";\\n break;\\n case '平多开空':\\n decisionDisplay = \\\"🔄 平多开空\\\";\\n decisionEmoji = \\\"🔁\\\";\\n break;\\n case '平空开多':\\n decisionDisplay = \\\"🔄 平空开多\\\";\\n decisionEmoji = \\\"🔁\\\";\\n break;\\n }\\n \\n // 执行状态\\n let statusDisplay = \\\"\\\";\\n if (result.executed) {\\n statusDisplay = `✅ 已执行 (${result.operations || 1}次)`;\\n } else if (result.skipReason) {\\n statusDisplay = `⏭️ ${result.skipReason}`;\\n } else {\\n statusDisplay = \\\"❌ 失败\\\";\\n }\\n \\n // 评分显示\\n const score = result.score || 0;\\n let scoreDisplay = \\\"-\\\";\\n if (score !== 0) {\\n const scoreAbs = Math.abs(score);\\n if (scoreAbs > 0.1) {\\n scoreDisplay = `🔥 ${score.toFixed(4)}`;\\n } else if (scoreAbs > 0.05) {\\n scoreDisplay = `⚡ ${score.toFixed(4)}`;\\n } else {\\n scoreDisplay = `${score.toFixed(4)}`;\\n }\\n }\\n \\n // 新闻分析(截取前25字符)\\n const newsAnalysis = result.newsAnalysis || \\\"-\\\";\\n \\n // 综合判断(截取前25字符)\\n const overallJudgment = result.overallJudgment || \\\"-\\\";\\n \\n return [\\n groupIcon,\\n `💎 ${coin}`,\\n `${decisionEmoji} ${decisionDisplay}`,\\n statusDisplay,\\n scoreDisplay,\\n newsAnalysis,\\n overallJudgment\\n ];\\n };\\n \\n // 添加正向组\\n if (positiveResults.length > 0) {\\n positiveResults\\n .sort((a, b) => (b[1].score || 0) - (a[1].score || 0))\\n .forEach(([coin, result]) => {\\n aiTable.rows.push(formatRow(coin, result, \\\"📈\\\"));\\n });\\n }\\n \\n // 添加负向组\\n if (negativeResults.length > 0) {\\n negativeResults\\n .sort((a, b) => (a[1].score || 0) - (b[1].score || 0))\\n .forEach(([coin, result]) => {\\n aiTable.rows.push(formatRow(coin, result, \\\"📉\\\"));\\n });\\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 = Object.keys(executionResults).length;\\n \\n aiTable.rows.push([\\n \\\"📊\\\",\\n `汇总: ${totalSignals}信号`,\\n `📈${positiveResults.length} 📉${negativeResults.length}`,\\n `✅ ${executedCount}已执行`,\\n \\\"-\\\",\\n \\\"-\\\",\\n `更新: ${executionTime}`\\n ]);\\n }\\n }\\n \\n } catch (e) {\\n Log(`❌ 创建AI决策表失败: ${e.message}`);\\n aiTable.rows.push([\\n \\\"❌\\\",\\n \\\"错误\\\",\\n \\\"获取AI信号失败\\\",\\n \\\"🚨\\\",\\n \\\"-\\\",\\n e.message.substring(0, 20),\\n \\\"-\\\"\\n ]);\\n }\\n \\n return aiTable;\\n}\\n\\n/**\\n * 创建实时持仓表 - 按正负向分组\\n */\\nfunction createRealtimePositionTable() {\\n const positionTable = {\\n type: \\\"table\\\",\\n title: \\\"💼 实时持仓监控\\\",\\n cols: [\\\"分组\\\", \\\"币种\\\", \\\"方向\\\", \\\"数量\\\", \\\"入场价\\\", \\\"当前价\\\", \\\"当前盈亏%\\\", \\\"最高盈亏%\\\", \\\"回撤%\\\", \\\"状态\\\"],\\n rows: []\\n };\\n \\n try {\\n const positions = exchange.GetPositions();\\n let totalCurrentPnl = 0;\\n let positionCount = 0;\\n \\n // 分组存储\\n const longPositions = [];\\n const shortPositions = [];\\n \\n if (positions && positions.length > 0) {\\n positions.forEach(pos => {\\n if (Math.abs(pos.Amount) > 0) {\\n const coinMatch = pos.Symbol.match(/^(.+)_USDT/);\\n if (!coinMatch) return;\\n \\n const coin = coinMatch[1];\\n const symbolKey = `${coin}_USDT.swap_maxprofit`;\\n \\n // 获取当前价格\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n const ticker = exchange.GetTicker();\\n \\n if (ticker) {\\n const isLong = pos.Type === PD_LONG || pos.Type === 0;\\n const currentPrice = ticker.Last;\\n const entryPrice = pos.Price;\\n \\n // 当前盈亏\\n const currentPnl = ((currentPrice - entryPrice) * (isLong ? 1 : -1) / entryPrice * 100);\\n \\n // 最高盈亏\\n const maxProfit = _G(symbolKey);\\n const maxPnlPercent = maxProfit !== null ? (maxProfit * 100) : currentPnl;\\n \\n // 回撤\\n const drawdown = maxProfit !== null ? ((maxProfit * 100) - currentPnl) : 0;\\n \\n // 当前盈亏显示\\n let currentPnlDisplay = \\\"\\\";\\n if (currentPnl > 0) {\\n currentPnlDisplay = `🟢 +${currentPnl.toFixed(2)}%`;\\n } else if (currentPnl < 0) {\\n currentPnlDisplay = `🔴 ${currentPnl.toFixed(2)}%`;\\n } else {\\n currentPnlDisplay = `⚪ ${currentPnl.toFixed(2)}%`;\\n }\\n \\n // 最高盈亏显示\\n let maxPnlDisplay = \\\"\\\";\\n if (maxPnlPercent > 0) {\\n maxPnlDisplay = `🔥 +${maxPnlPercent.toFixed(2)}%`;\\n } else if (maxPnlPercent < 0) {\\n maxPnlDisplay = `${maxPnlPercent.toFixed(2)}%`;\\n } else {\\n maxPnlDisplay = `${maxPnlPercent.toFixed(2)}%`;\\n }\\n \\n // 回撤显示\\n let drawdownDisplay = \\\"\\\";\\n if (drawdown > 0) {\\n const warningLevel = (CONFIG.TRAILING_STOP_PERCENT * 100);\\n if (drawdown >= warningLevel * 0.8) {\\n drawdownDisplay = `🔴 -${drawdown.toFixed(2)}%`;\\n } else if (drawdown >= warningLevel * 0.5) {\\n drawdownDisplay = `🟡 -${drawdown.toFixed(2)}%`;\\n } else {\\n drawdownDisplay = `🟢 -${drawdown.toFixed(2)}%`;\\n }\\n } else {\\n drawdownDisplay = `⚪ 0%`;\\n }\\n \\n // 状态\\n let statusDisplay = \\\"✅ 监控中\\\";\\n if (maxProfit !== null) {\\n const stopLevel = (CONFIG.TRAILING_STOP_PERCENT * 100);\\n if (drawdown >= stopLevel * 0.9) {\\n statusDisplay = \\\"⚠️ 即将止损\\\";\\n } else if (drawdown >= stopLevel * 0.7) {\\n statusDisplay = \\\"🟡 警戒中\\\";\\n } else {\\n statusDisplay = \\\"✅ 持续监控\\\";\\n }\\n }\\n \\n const positionRow = [\\n isLong ? \\\"📈\\\" : \\\"📉\\\",\\n `💎 ${coin}`,\\n isLong ? \\\"📈 多\\\" : \\\"📉 空\\\",\\n _N(Math.abs(pos.Amount), 4),\\n `$${_N(entryPrice, 4)}`,\\n `$${_N(currentPrice, 4)}`,\\n currentPnlDisplay,\\n maxPnlDisplay,\\n drawdownDisplay,\\n statusDisplay\\n ];\\n \\n if (isLong) {\\n longPositions.push(positionRow);\\n } else {\\n shortPositions.push(positionRow);\\n }\\n \\n totalCurrentPnl += currentPnl;\\n positionCount++;\\n }\\n \\n Sleep(200);\\n }\\n });\\n }\\n \\n // 先添加多仓\\n longPositions.forEach(row => positionTable.rows.push(row));\\n \\n // 再添加空仓\\n shortPositions.forEach(row => positionTable.rows.push(row));\\n \\n // 添加汇总行\\n if (positionCount > 0) {\\n const avgPnl = totalCurrentPnl / positionCount;\\n positionTable.rows.push([\\n `📊`,\\n `汇总: ${positionCount}仓`,\\n `📈${longPositions.length} 📉${shortPositions.length}`,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n avgPnl > 0 ? `🟢 +${_N(avgPnl, 2)}%` : `🔴 ${_N(avgPnl, 2)}%`,\\n \\\"-\\\",\\n \\\"-\\\",\\n `移动止损: ${(CONFIG.TRAILING_STOP_PERCENT * 100).toFixed(1)}%`\\n ]);\\n } else {\\n positionTable.rows.push([\\n \\\"📭\\\",\\n \\\"空仓\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\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 \\\"-\\\",\\n \\\"-\\\",\\n \\\"🚨\\\"\\n ]);\\n }\\n \\n return positionTable;\\n}\\n\\n/**\\n * 显示完整监控仪表板\\n */\\nfunction displayMonitoringDashboard() {\\n try {\\n // 1. 账户概览\\n const accountTable = createAccountOverviewTable();\\n \\n // 2. AI决策\\n const aiTable = createAIDecisionTable();\\n \\n // 3. 实时持仓\\n const positionTable = createRealtimePositionTable();\\n \\n // 组合显示\\n const dashboardDisplay = \\n '`' + JSON.stringify(accountTable) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(aiTable) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(positionTable) + '`';\\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 \\n const monitoringResults = {};\\n let totalActions = 0;\\n \\n // 获取所有持仓进行监控\\n const positions = exchange.GetPositions();\\n \\n if (positions && positions.length > 0) {\\n \\n positions.forEach(pos => {\\n if (Math.abs(pos.Amount) > 0) {\\n const coinMatch = pos.Symbol.match(/^(.+)_USDT/);\\n if (!coinMatch) return;\\n \\n const coin = coinMatch[1];\\n \\n const result = monitorPositionWithTrailingStop(coin);\\n monitoringResults[coin] = result;\\n \\n if (result.status === \\\"closed\\\") {\\n Log(`🎯 ${coin} 已平仓 - 移动止损触发`);\\n totalActions++;\\n }\\n \\n Sleep(CONFIG.CHECK_INTERVAL);\\n }\\n });\\n } \\n \\n // 显示监控仪表板\\n displayMonitoringDashboard();\\n \\n \\n return {\\n monitoringResults: monitoringResults,\\n totalActions: totalActions,\\n timestamp: Date.now()\\n };\\n \\n } catch (e) {\\n Log(`❌ 主执行流程失败: ${e.message}`);\\n Log(`❌ 错误堆栈: ${e.stack}`);\\n LogStatus('❌ 系统错误: ' + e.message);\\n return { error: e.message };\\n }\\n}\\n\\n// 执行主函数并返回结果\\nreturn main();\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-304,304],\"id\":\"9b7ab016-fd6c-4905-af6e-301f11bdbaee\",\"name\":\"止盈止损监控\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"if (_G('initmoney') === null) {\\n const initAccount = _C(exchange.GetAccount);\\n _G('initmoney', initAccount.Balance);\\n\\n const market = _C(exchange.GetMarkets)\\n _G('markets', market)\\n}\\n\\nreturn {}\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-288,16],\"id\":\"88a9366c-3224-47e9-85b3-1b41c3f92196\",\"name\":\"初始设置\"},{\"parameters\":{\"method\":\"GET\",\"url\":\"https://api.binance.com/api/v3/ticker/24hr\",\"authentication\":\"none\",\"sendQuery\":false,\"sendHeaders\":false,\"sendBody\":false,\"options\":{},\"infoMessage\":\"\"},\"type\":\"n8n-nodes-base.httpRequest\",\"typeVersion\":4.2,\"position\":[-64,16],\"id\":\"ac617285-231c-4538-bff5-92a365226ddf\",\"name\":\"币种查询\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"const symbols = $json.data\\n .filter(item => {\\n if (!item.symbol.endsWith('USDT')) return false;\\n if (item.symbol.startsWith('USDC')) return false;\\n \\n const symbol = item.symbol.replace('USDT', '_USDT.swap');\\n const markets = _G('markets');\\n \\n if (markets && !markets[symbol]) return false;\\n \\n return true;\\n })\\n .sort((a, b) => parseFloat(b.quoteVolume) - parseFloat(a.quoteVolume))\\n .slice(0, $vars['coinNumber'])\\n .map(item => item.symbol);\\n\\nLog(`✅ 筛选出 ${symbols.length} 个币种: ${symbols.join(', ')}`);\\n\\nreturn { symbol: symbols };\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[160,16],\"id\":\"d0212102-292f-4999-93e0-192376fe856b\",\"name\":\"高流动币种查询\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ==================== 多币种均线综合评分与排名 ====================\\n\\n// 1. 获取参数\\nvar wheelPeriod = $vars['wheelPeriod'];\\nvar meanPeriod = $vars['meanPeriod'];\\n\\n// 2. 获取输入的币种数组\\nvar inputSymbols = $input.first().json.symbol; // [\\\"BTCUSDT\\\", \\\"ETHUSDT\\\", ...]\\n\\nLog(`📥 收到 ${inputSymbols.length} 个高流动币种`);\\n\\n// ==================== 先获取持仓信息 ====================\\nLog(\\\"📦 开始获取持仓信息...\\\");\\n\\nconst positions = exchange.GetPositions();\\nconst positionMap = {};\\nconst positionCoins = new Set();\\nconst positionSymbols = [];\\n\\nif (positions && positions.length > 0) {\\n positions.forEach(pos => {\\n if (Math.abs(pos.Amount) > 0) {\\n const coin = pos.Symbol.replace('_USDT.swap', '');\\n\\n if (coin && coin !== pos.Symbol) {\\n positionCoins.add(coin);\\n const symbol = coin + 'USDT';\\n positionSymbols.push(symbol);\\n \\n positionMap[coin] = {\\n symbol: symbol,\\n type: pos.Type, // 0=多仓, 1=空仓\\n amount: pos.Amount,\\n price: pos.Price,\\n profit: pos.Profit,\\n positionStatus: pos.Type === 0 || pos.Type === PD_LONG ? '持有多仓' : '持有空仓'\\n };\\n Log(`📍 持仓: ${coin} ${positionMap[coin].positionStatus} 盈亏=${pos.Profit}`);\\n }\\n }\\n });\\n}\\n\\nLog(`✅ 共发现 ${positionCoins.size} 个持仓币种`);\\n\\n// ==================== 检查持仓币种是否在输入列表中,不在则添加 ====================\\nconst inputSymbolSet = new Set(inputSymbols);\\nconst missingPositionSymbols = positionSymbols.filter(symbol => !inputSymbolSet.has(symbol));\\n\\nif (missingPositionSymbols.length > 0) {\\n Log(`➕ 补充 ${missingPositionSymbols.length} 个持仓币种到计算列表: ${missingPositionSymbols.join(', ')}`);\\n inputSymbols = inputSymbols.concat(missingPositionSymbols);\\n}\\n\\n// 存储所有币种的评分结果\\nvar allResults = [];\\n\\n// 3. 遍历每个币种进行计算\\nfor (var i = 0; i < inputSymbols.length; i++) {\\n \\n var originalSymbol = inputSymbols[i];\\n Log('处理币种:', originalSymbol);\\n \\n var processedSymbol = originalSymbol.replace('USDT', '_USDT.swap');\\n \\n try {\\n // 获取不同周期的K线数据\\n var s = exchange.GetRecords(processedSymbol, wheelPeriod / 4);\\n var ms = exchange.GetRecords(processedSymbol, wheelPeriod / 2);\\n var ml = exchange.GetRecords(processedSymbol, wheelPeriod * 2);\\n var ls = exchange.GetRecords(processedSymbol, wheelPeriod * 4);\\n \\n if (!s || !ms || !ml || !ls || ls.length < meanPeriod + 2) {\\n Log(`⚠️ ${originalSymbol} 数据不足,跳过`);\\n continue;\\n }\\n \\n // 计算各周期均线\\n var sMA = TA.EMA(s, meanPeriod);\\n var msMA = TA.EMA(ms, meanPeriod);\\n var mlMA = TA.EMA(ml, meanPeriod);\\n var lMA = TA.EMA(ls, meanPeriod);\\n \\n if (!sMA || !msMA || !mlMA || !lMA || sMA.length < 2) {\\n Log(`⚠️ ${originalSymbol} 均线计算失败,跳过`);\\n continue;\\n }\\n \\n var len_s = sMA.length;\\n var len_ms = msMA.length;\\n var len_ml = mlMA.length;\\n var len_l = lMA.length;\\n \\n var PS = sMA[len_s - 1];\\n var PMS = msMA[len_ms - 1];\\n var PML = mlMA[len_ml - 1];\\n var PL = lMA[len_l - 1];\\n \\n var PS_prev = sMA[len_s - 2];\\n var PMS_prev = msMA[len_ms - 2];\\n var PML_prev = mlMA[len_ml - 2];\\n var PL_prev = lMA[len_l - 2];\\n \\n // ==================== 指标1:均线排列形态 ====================\\n var bullCount = 0;\\n var bearCount = 0;\\n \\n var compare1 = PS > PMS ? 1 : (PS < PMS ? -1 : 0);\\n var compare2 = PMS > PML ? 1 : (PMS < PML ? -1 : 0);\\n var compare3 = PML > PL ? 1 : (PML < PL ? -1 : 0);\\n \\n if (compare1 > 0) bullCount++;\\n if (compare2 > 0) bullCount++;\\n if (compare3 > 0) bullCount++;\\n if (compare1 < 0) bearCount++;\\n if (compare2 < 0) bearCount++;\\n if (compare3 < 0) bearCount++;\\n \\n var arrangementScore = 0;\\n if (bullCount == 3) {\\n arrangementScore = 4;\\n } else if (bearCount == 3) {\\n arrangementScore = -4;\\n } else if (bullCount == 2) {\\n if ((compare1 > 0 && compare2 > 0) || (compare2 > 0 && compare3 > 0)) {\\n arrangementScore = 3;\\n } else {\\n arrangementScore = 2;\\n }\\n } else if (bearCount == 2) {\\n if ((compare1 < 0 && compare2 < 0) || (compare2 < 0 && compare3 < 0)) {\\n arrangementScore = -3;\\n } else {\\n arrangementScore = -2;\\n }\\n } else if (bullCount == 1) {\\n arrangementScore = 1;\\n } else if (bearCount == 1) {\\n arrangementScore = -1;\\n }\\n \\n // ==================== 指标2:均线扩散间距 ====================\\n var gap1 = PS / PMS - 1;\\n var gap2 = PMS / PML - 1;\\n var gap3 = PML / PL - 1;\\n var gapScore = (gap1 + gap2 + gap3) / 3;\\n \\n // ==================== 指标3:均线时序变化 ====================\\n var riseCount = 0;\\n var fallCount = 0;\\n \\n if (PS > PS_prev) riseCount++;\\n if (PMS > PMS_prev) riseCount++;\\n if (PML > PML_prev) riseCount++;\\n if (PL > PL_prev) riseCount++;\\n \\n if (PS < PS_prev) fallCount++;\\n if (PMS < PMS_prev) fallCount++;\\n if (PML < PML_prev) fallCount++;\\n if (PL < PL_prev) fallCount++;\\n \\n var timeSeriesScore = 0;\\n if (riseCount > fallCount) {\\n timeSeriesScore = riseCount;\\n } else if (fallCount > riseCount) {\\n timeSeriesScore = -fallCount;\\n }\\n \\n // ==================== 合成均线综合得分 ====================\\n var comprehensiveScore = 0;\\n if (gapScore > 0) {\\n comprehensiveScore = gapScore * arrangementScore * timeSeriesScore;\\n } else if (gapScore < 0) {\\n comprehensiveScore = gapScore * Math.abs(arrangementScore) * Math.abs(timeSeriesScore);\\n }\\n \\n const coin = originalSymbol.replace('USDT', '');\\n const isPosition = positionCoins.has(coin);\\n const positionTag = isPosition ? ' [持仓]' : '';\\n \\n Log(\\\"🍃\\\", originalSymbol + positionTag, \\\"综合得分:\\\", comprehensiveScore.toFixed(6), \\\"| 排列:\\\", arrangementScore, \\\"扩散:\\\", gapScore.toFixed(6), \\\"时序:\\\", timeSeriesScore, \\\"| 多头:\\\", bullCount, \\\"空头:\\\", bearCount);\\n \\n allResults.push({\\n originalSymbol: originalSymbol,\\n processedSymbol: processedSymbol,\\n score: comprehensiveScore,\\n arrangementScore: arrangementScore,\\n gapScore: gapScore,\\n timeSeriesScore: timeSeriesScore,\\n bullCount: bullCount,\\n bearCount: bearCount,\\n isPosition: isPosition,\\n coin: coin\\n });\\n \\n } catch (e) {\\n Log(`❌ ${originalSymbol} 计算异常:`, e.message);\\n continue;\\n }\\n}\\n\\n// 4. 筛选正分和负分币种\\nvar positiveResults = allResults.filter(item => item.score > 0);\\nvar negativeResults = allResults.filter(item => item.score < 0);\\n\\n// 5. 排序\\npositiveResults.sort((a, b) => b.score - a.score);\\nnegativeResults.sort((a, b) => a.score - b.score);\\n\\n// ==================== 选取结果:前5 + 持仓多仓(type=0) ====================\\nvar topPositive = [];\\nvar topNegative = [];\\nvar supplementedLongPositions = new Set(); // 记录补充的多仓\\nvar supplementedShortPositions = new Set(); // 记录补充的空仓\\n\\n// 正向组:前5\\nfor (let i = 0; i < Math.min(5, positiveResults.length); i++) {\\n topPositive.push(positiveResults[i]);\\n}\\n\\n// 检查多仓(type=0)是否在前5中,不在则添加(不管score多少)\\npositionCoins.forEach(coin => {\\n const posInfo = positionMap[coin];\\n if (posInfo && (posInfo.type === 0 || posInfo.type === PD_LONG)) {\\n // 是多仓\\n const symbol = coin + 'USDT';\\n const inTop5 = topPositive.some(r => r.originalSymbol === symbol);\\n \\n if (!inTop5) {\\n // 不在前5,查找这个币种的数据\\n const coinData = allResults.find(r => r.originalSymbol === symbol);\\n if (coinData) {\\n topPositive.push(coinData);\\n supplementedLongPositions.add(coin); // 标记为补充的\\n Log(`➕ 补充多仓到正向组: ${coin} (score=${coinData.score.toFixed(6)}, type=0, 不在前5)`);\\n }\\n }\\n }\\n});\\n\\n// 负向组:后5\\nfor (let i = 0; i < Math.min(5, negativeResults.length); i++) {\\n topNegative.push(negativeResults[i]);\\n}\\n\\n// 检查空仓(type=1)是否在后5中,不在则添加(不管score多少)\\npositionCoins.forEach(coin => {\\n const posInfo = positionMap[coin];\\n if (posInfo && posInfo.type === 1) {\\n // 是空仓\\n const symbol = coin + 'USDT';\\n const inTop5 = topNegative.some(r => r.originalSymbol === symbol);\\n \\n if (!inTop5) {\\n // 不在后5,查找这个币种的数据\\n const coinData = allResults.find(r => r.originalSymbol === symbol);\\n if (coinData) {\\n topNegative.push(coinData);\\n supplementedShortPositions.add(coin); // 标记为补充的\\n Log(`➕ 补充空仓到负向组: ${coin} (score=${coinData.score.toFixed(6)}, type=1, 不在后5)`);\\n }\\n }\\n }\\n});\\n\\nLog(`📊 最终正向组: ${topPositive.length}个 (前5 + 所有多仓)`);\\nLog(`📊 最终负向组: ${topNegative.length}个 (后5 + 所有空仓)`);\\n\\n// 7. 格式化输出 - 修改positionStatus\\nvar positiveGroup = topPositive.map(item => {\\n const coin = item.coin;\\n let status = '无持仓';\\n \\n if (positionMap[coin]) {\\n // 有持仓\\n if (supplementedLongPositions.has(coin)) {\\n // 是补充进来的多仓(不在前5)\\n status = '持有多仓但指标强度不在前列';\\n } else {\\n // 在前5的多仓\\n status = positionMap[coin].positionStatus;\\n }\\n }\\n \\n return {\\n symbol: item.originalSymbol,\\n processedSymbol: item.processedSymbol,\\n score: item.score,\\n arrangementScore: item.arrangementScore,\\n gapScore: item.gapScore,\\n timeSeriesScore: item.timeSeriesScore,\\n bullCount: item.bullCount,\\n positionStatus: status,\\n positionInfo: positionMap[coin] || null\\n };\\n});\\n\\nvar negativeGroup = topNegative.map(item => {\\n const coin = item.coin;\\n let status = '无持仓';\\n \\n if (positionMap[coin]) {\\n // 有持仓\\n if (supplementedShortPositions.has(coin)) {\\n // 是补充进来的空仓(不在后5)\\n status = '持有空仓但指标强度不在前列';\\n } else {\\n // 在后5的空仓\\n status = positionMap[coin].positionStatus;\\n }\\n }\\n \\n return {\\n symbol: item.originalSymbol,\\n processedSymbol: item.processedSymbol,\\n score: item.score,\\n arrangementScore: item.arrangementScore,\\n gapScore: item.gapScore,\\n timeSeriesScore: item.timeSeriesScore,\\n bearCount: item.bearCount,\\n positionStatus: status,\\n positionInfo: positionMap[coin] || null\\n };\\n});\\n\\nLog(\\\"📈 正向组币种:\\\", positiveGroup.map(item => `${item.symbol}(${item.positionStatus})`).join(\\\", \\\"));\\nLog(\\\"📉 负向组币种:\\\", negativeGroup.map(item => `${item.symbol}(${item.positionStatus})`).join(\\\", \\\"));\\n\\n// 8. 返回结果\\nreturn {\\n positive: positiveGroup,\\n negative: negativeGroup,\\n statistics: {\\n totalSymbols: inputSymbols.length,\\n validSymbols: allResults.length,\\n positiveCount: positiveResults.length,\\n negativeCount: negativeResults.length,\\n neutralCount: allResults.length - positiveResults.length - negativeResults.length,\\n positionCount: positionCoins.size,\\n supplementedPositions: missingPositionSymbols.length\\n },\\n timestamp: new Date().toISOString()\\n};\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[384,16],\"id\":\"676bcfe0-d4ee-4750-8dfd-d905948a6876\",\"name\":\"趋势强度识别\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 1. 获取上一步的结果(已包含持仓信息)\\nvar inputData = $input.first().json;\\nvar positiveGroup = inputData.positive || [];\\nvar negativeGroup = inputData.negative || [];\\n\\n\\nLog(`📨 收到 ${positiveGroup.length} 个正向币种, ${negativeGroup.length} 个负向币种`);\\n\\n// 2. 提取币种代码的辅助函数(去掉USDT后缀)\\nfunction extractCurrency(symbol) {\\n var currency = symbol.replace('USDT', '');\\n return currency;\\n}\\n\\n// 3. 获取币种新闻的函数\\nfunction getCryptoNews(currency) {\\n try {\\n var url = \\\"https://min-api.cryptocompare.com/data/v2/news/?categories=\\\" + currency;\\n var response = HttpQuery(url);\\n \\n if (!response) {\\n Log(\\\"⚠️ 获取新闻失败: \\\" + currency + \\\" - 无响应\\\");\\n return [];\\n }\\n \\n var info = JSON.parse(response);\\n \\n if (info.Message === \\\"News list successfully returned\\\") {\\n var news = [];\\n var count = Math.min(5, info.Data.length);\\n \\n for (var i = 0; i < count; i++) {\\n news.push({\\n title: info.Data[i].title\\n });\\n }\\n Sleep(1000);\\n \\n return news;\\n } else {\\n Log(\\\"⚠️ 获取新闻失败:\\\", currency, info.Message);\\n return [];\\n }\\n } catch (e) {\\n Log(\\\"❌ 获取新闻异常:\\\", currency, e.message || e);\\n return [];\\n }\\n}\\n\\n// 4. 处理positive组(直接使用已有的持仓信息)\\nvar positiveWithNews = positiveGroup.map(function(coin) {\\n try {\\n var currency = extractCurrency(coin.symbol);\\n Log(\\\"正在获取\\\", currency, \\\"的新闻...\\\");\\n \\n var news = getCryptoNews(currency);\\n \\n return {\\n symbol: coin.symbol,\\n processedSymbol: coin.processedSymbol,\\n score: coin.score,\\n arrangementScore: coin.arrangementScore,\\n gapScore: coin.gapScore,\\n timeSeriesScore: coin.timeSeriesScore,\\n bullCount: coin.bullCount,\\n recentNews: news,\\n positionStatus: coin.positionStatus,\\n positionInfo: coin.positionInfo\\n };\\n } catch (e) {\\n Log(\\\"❌ 处理正向币种异常:\\\", coin.symbol, e.message || e);\\n // 返回基础数据,不包含新闻\\n return {\\n symbol: coin.symbol,\\n processedSymbol: coin.processedSymbol,\\n score: coin.score,\\n arrangementScore: coin.arrangementScore,\\n gapScore: coin.gapScore,\\n timeSeriesScore: coin.timeSeriesScore,\\n bullCount: coin.bullCount,\\n recentNews: [],\\n positionStatus: coin.positionStatus,\\n positionInfo: coin.positionInfo\\n };\\n }\\n});\\n\\n// 5. 处理negative组\\nvar negativeWithNews = negativeGroup.map(function(coin) {\\n try {\\n var currency = extractCurrency(coin.symbol);\\n Log(\\\"正在获取\\\", currency, \\\"的新闻...\\\");\\n \\n var news = getCryptoNews(currency);\\n \\n return {\\n symbol: coin.symbol,\\n processedSymbol: coin.processedSymbol,\\n score: coin.score,\\n arrangementScore: coin.arrangementScore,\\n gapScore: coin.gapScore,\\n timeSeriesScore: coin.timeSeriesScore,\\n bearCount: coin.bearCount,\\n recentNews: news,\\n newsCount: news.length,\\n positionStatus: coin.positionStatus,\\n positionInfo: coin.positionInfo\\n };\\n } catch (e) {\\n Log(\\\"❌ 处理负向币种异常:\\\", coin.symbol, e.message || e);\\n // 返回基础数据,不包含新闻\\n return {\\n symbol: coin.symbol,\\n processedSymbol: coin.processedSymbol,\\n score: coin.score,\\n arrangementScore: coin.arrangementScore,\\n gapScore: coin.gapScore,\\n timeSeriesScore: coin.timeSeriesScore,\\n bearCount: coin.bearCount,\\n recentNews: [],\\n newsCount: 0,\\n positionStatus: coin.positionStatus,\\n positionInfo: coin.positionInfo\\n };\\n }\\n});\\n\\n// 6. 返回结果\\nreturn [{\\n positive: positiveWithNews,\\n negative: negativeWithNews,\\n statistics: inputData.statistics,\\n timestamp: new Date().toISOString()\\n}];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[608,16],\"id\":\"448bdb05-b9d2-4f04-b4ee-dad3c0273b81\",\"name\":\"实时新闻获取\"},{\"parameters\":{\"conditions\":{\"options\":{\"caseSensitive\":true,\"leftValue\":\"\",\"typeValidation\":\"loose\",\"version\":2},\"conditions\":[{\"id\":\"50b098c4-4f2d-4006-b76b-eba20c0c0ed5\",\"leftValue\":\"={{ $json.statistics.positiveCount + $json.statistics.negativeCount}}\",\"rightValue\":\"0\",\"operator\":{\"type\":\"string\",\"operation\":\"equals\",\"name\":\"filter.operator.equals\"}}],\"combinator\":\"and\"},\"looseTypeValidation\":true,\"options\":{}},\"type\":\"n8n-nodes-base.if\",\"typeVersion\":2.2,\"position\":[832,16],\"id\":\"f23167b5-c514-4783-b928-a9b2fb56bef0\",\"name\":\"交易判断\"},{\"parameters\":{\"logAll\":false,\"output\":\"🌈:不存在任何分组,不进行处理 :)\"},\"type\":\"n8n-nodes-base.log\",\"typeVersion\":1,\"position\":[1152,-144],\"id\":\"e9875f03-75af-4ee4-a541-40695c410380\",\"name\":\"无交易处理\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 极简版交易执行系统 ==========\\n// 功能:解析AI决策、执行开平仓、U本位固定金额交易、安全反手操作\\n\\n// ========== 配置参数 ==========\\nconst CONFIG = {\\n FIXED_AMOUNT_USD: $vars.Amount, // 固定交易金额(U)\\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};\\n\\n// ========== 工具函数 ==========\\n\\n/**\\n * 解析AI输出 - 提取币种和决策\\n */\\nfunction parseAIOutput(output) {\\n try {\\n Log('📥 开始解析AI输出...');\\n \\n // 如果是字符串,解析为JSON\\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 // 检查是否是数组\\n if (!Array.isArray(parsed)) {\\n Log('⚠️ AI输出不是数组格式');\\n return [];\\n }\\n \\n // 转换为标准信号格式\\n const signals = [];\\n \\n for (let item of parsed) {\\n // 提取币种 (SOLUSDT -> SOL)\\n const coin = item.symbol.replace('USDT', '');\\n \\n // 映射决策\\n let signal;\\n switch (item.decision) {\\n case '开多':\\n signal = 'buy';\\n break;\\n case '开空':\\n signal = 'sell';\\n break;\\n case '平仓':\\n signal = 'close';\\n break;\\n case '平多开空':\\n signal = 'close_then_sell';\\n break;\\n case '平空开多':\\n signal = 'close_then_buy';\\n break;\\n case '持有':\\n case '观望':\\n signal = 'hold';\\n break;\\n default:\\n signal = 'hold';\\n }\\n \\n signals.push({\\n coin: coin,\\n signal: signal,\\n originalDecision: item.decision,\\n newsAnalysis: item.newsAnalysis,\\n overallJudgment: item.overallJudgment,\\n symbol: item.symbol,\\n score: item.score || 0,\\n currentPosition: item.currentPosition || '未知'\\n });\\n }\\n \\n Log(`✅ 成功解析 ${signals.length} 个AI信号`);\\n return signals;\\n \\n } catch (e) {\\n Log(`❌ parseAIOutput失败: ${e.message}`);\\n Log(`原始输出: ${JSON.stringify(output)}`);\\n return [];\\n }\\n}\\n\\n/**\\n * 持续获取市场信息直到成功\\n */\\nfunction getMarketInfo(coin) {\\n try {\\n const symbol = coin + '_USDT.swap';\\n let attempts = 0;\\n \\n Log(`🔍 ${coin}: 开始获取市场信息...`);\\n \\n while (attempts < CONFIG.MAX_MARKET_RETRY) {\\n attempts++;\\n \\n try {\\n // 先从缓存获取\\n const markets = _G('markets');\\n if (markets && markets[symbol]) {\\n return markets[symbol];\\n }\\n \\n // 缓存没有,实时获取\\n const market = exchange.GetMarkets();\\n \\n if (market && market[symbol]) {\\n // 更新缓存\\n let allMarkets = _G('markets') || {};\\n allMarkets[symbol] = market[symbol];\\n _G('markets', allMarkets);\\n \\n return market[symbol];\\n }\\n \\n Log(`⚠️ ${coin}: 第${attempts}次获取市场信息失败,重试中...`);\\n Sleep(CONFIG.MARKET_RETRY_INTERVAL);\\n \\n } catch (e) {\\n Log(`⚠️ ${coin}: 第${attempts}次获取失败: ${e.message}`);\\n Sleep(CONFIG.MARKET_RETRY_INTERVAL);\\n }\\n }\\n \\n Log(`❌ ${coin}: 获取市场信息失败,已重试${attempts}次`);\\n return null;\\n \\n } catch (e) {\\n Log(`❌ ${coin}: getMarketInfo异常: ${e.message}`);\\n return null;\\n }\\n}\\n\\n/**\\n * 获取当前价格\\n */\\nfunction getCurrentPrice(coin) {\\n try {\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n \\n const ticker = exchange.GetTicker();\\n if (!ticker) {\\n Log(`❌ ${coin}: 获取价格失败`);\\n return 0;\\n }\\n \\n return ticker.Last;\\n } catch (e) {\\n Log(`❌ ${coin}: 获取价格异常: ${e.message}`);\\n return 0;\\n }\\n}\\n\\n/**\\n * 计算合约张数\\n * 公式:张数 = 金额(U) / 当前价格 / 合约面值(CtVal)\\n */\\nfunction calculateContractAmount(amountUSD, currentPrice, market) {\\n try {\\n if (!market || !market.CtVal) {\\n Log(`❌ 缺少市场信息或合约面值`);\\n return 0;\\n }\\n \\n // 计算币的数量\\n const coinAmount = amountUSD / currentPrice;\\n \\n // 转换为合约张数\\n let contractAmount = coinAmount / market.CtVal;\\n \\n // 应用数量精度\\n contractAmount = _N(contractAmount, market.AmountPrecision);\\n \\n // 检查最小数量\\n if (contractAmount < market.MinQty) {\\n Log(`⚠️ 计算张数 ${contractAmount} 小于最小值 ${market.MinQty}`);\\n return 0;\\n }\\n \\n // 检查最大数量\\n if (contractAmount > market.MaxQty) {\\n Log(`⚠️ 计算张数 ${contractAmount} 超过最大值 ${market.MaxQty},调整为最大值`);\\n contractAmount = market.MaxQty;\\n }\\n \\n Log(`💡 金额$${amountUSD} / 价格$${currentPrice} / 面值${market.CtVal} = ${contractAmount}张`);\\n \\n return contractAmount;\\n \\n } catch (e) {\\n Log(`❌ 计算合约张数失败: ${e.message}`);\\n return 0;\\n }\\n}\\n\\n/**\\n * 检查是否有持仓\\n */\\nfunction hasPosition(coin) {\\n try {\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n const positions = exchange.GetPositions();\\n return positions && positions.some(p => p.Symbol.includes(coin) && Math.abs(p.Amount) > 0);\\n } catch (e) {\\n Log(`⚠️ 检查${coin}持仓失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 获取当前持仓信息\\n */\\nfunction getPosition(coin) {\\n try {\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n const positions = exchange.GetPositions();\\n return positions && positions.find(p => p.Symbol.includes(coin) && Math.abs(p.Amount) > 0);\\n } catch (e) {\\n return null;\\n }\\n}\\n\\n/**\\n * 等待持仓清空 - 平仓后验证\\n */\\nfunction waitForPositionClear(coin) {\\n try {\\n Log(`⏳ ${coin}: 等待持仓清空...`);\\n \\n let attempts = 0;\\n \\n while (attempts < CONFIG.MAX_CLOSE_WAIT) {\\n Sleep(CONFIG.CLOSE_CHECK_INTERVAL);\\n \\n const hasPos = hasPosition(coin);\\n \\n if (!hasPos) {\\n Log(`✅ ${coin}: 持仓已清空`);\\n return true;\\n }\\n \\n attempts++;\\n Log(`⏳ ${coin}: 第${attempts}次检查,持仓仍存在...`);\\n }\\n \\n Log(`❌ ${coin}: 等待持仓清空超时`);\\n return false;\\n \\n } catch (e) {\\n Log(`❌ ${coin}: 验证持仓清空失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n// ========== 交易执行函数 ==========\\n\\n/**\\n * 执行平仓\\n */\\nfunction executeClose(coin, reason = \\\"AI信号\\\") {\\n try {\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\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 });\\n }\\n \\n // 获取持仓\\n const pos = getPosition(coin);\\n \\n if (!pos) {\\n Log(`⚠️ ${coin}: 未找到持仓`);\\n return false;\\n }\\n \\n // 获取市场信息(用于精度)\\n const market = getMarketInfo(coin);\\n if (!market) {\\n Log(`❌ ${coin}: 无法获取市场信息,平仓失败`);\\n return false;\\n }\\n \\n const isLong = pos.Type === PD_LONG || pos.Type === 0;\\n const closeAmount = _N(Math.abs(pos.Amount), market.AmountPrecision);\\n \\n Log(`📤 ${coin}: 准备平仓 方向=${isLong ? '多' : '空'} 数量=${closeAmount}张`);\\n \\n // 设置平仓方向\\n exchange.SetDirection(isLong ? \\\"closebuy\\\" : \\\"closesell\\\");\\n \\n // 执行平仓(市价)\\n const orderId = isLong ? exchange.Sell(-1, closeAmount) : exchange.Buy(-1, closeAmount);\\n \\n if (orderId) {\\n const dirStr = isLong ? '多' : '空';\\n Log(`✅ ${coin}: 平${dirStr}订单已提交 (${reason}) OrderID=${orderId}`);\\n return true;\\n } else {\\n Log(`❌ ${coin}: 平仓订单提交失败`);\\n return false;\\n }\\n } catch (e) {\\n Log(`❌ ${coin} 平仓执行失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 执行开仓 - U本位下单\\n */\\nfunction executeEntry(coin, isLong) {\\n try {\\n exchange.SetCurrency(coin + '_USDT');\\n exchange.SetContractType(\\\"swap\\\");\\n \\n // 设置杠杆\\n exchange.SetMarginLevel(CONFIG.DEFAULT_LEVERAGE);\\n \\n // 获取市场信息\\n const market = getMarketInfo(coin);\\n if (!market) {\\n Log(`❌ ${coin}: 无法获取市场信息,开仓失败`);\\n return false;\\n }\\n \\n // 获取当前价格\\n const currentPrice = getCurrentPrice(coin);\\n if (currentPrice <= 0) {\\n Log(`❌ ${coin}: 无法获取当前价格,开仓失败`);\\n return false;\\n }\\n \\n // 计算合约张数\\n const contractAmount = calculateContractAmount(\\n CONFIG.FIXED_AMOUNT_USD, \\n currentPrice, \\n market\\n );\\n \\n if (contractAmount <= 0) {\\n Log(`⚠️ ${coin}: 计算合约张数无效,开仓失败`);\\n return false;\\n }\\n \\n Log(`📥 ${coin}: 准备开仓`);\\n Log(` 方向: ${isLong ? '多' : '空'}`);\\n Log(` 金额: $${CONFIG.FIXED_AMOUNT_USD}`);\\n Log(` 价格: $${currentPrice}`);\\n Log(` 面值: ${market.CtVal}`);\\n Log(` 张数: ${contractAmount}`);\\n Log(` 杠杆: ${CONFIG.DEFAULT_LEVERAGE}x`);\\n \\n // 设置方向\\n exchange.SetDirection(isLong ? \\\"buy\\\" : \\\"sell\\\");\\n \\n // 执行开仓(市价)\\n const orderId = isLong ? exchange.Buy(-1, contractAmount) : exchange.Sell(-1, contractAmount);\\n \\n if (orderId) {\\n const dirStr = isLong ? '多' : '空';\\n const symbolKey = `${coin}_USDT.swap_maxprofit`;\\n _G(symbolKey, 0);\\n Log(`✅ ${coin}: 开${dirStr}订单已提交 OrderID=${orderId}`);\\n return true;\\n } else {\\n Log(`❌ ${coin}: 开仓订单提交失败`);\\n return false;\\n }\\n } catch (e) {\\n Log(`❌ ${coin} 开仓执行失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n/**\\n * 执行反手操作 - 平仓后验证再开仓\\n */\\nfunction executeReverse(coin, isLong, reason) {\\n try {\\n Log(`🔄 ${coin}: 开始反手操作 - ${reason}`);\\n \\n // 步骤1: 先平仓\\n const closeSuccess = executeClose(coin, reason);\\n \\n if (!closeSuccess) {\\n Log(`❌ ${coin}: 平仓失败,取消反手操作`);\\n return false;\\n }\\n \\n // 步骤2: 等待并验证持仓已清空\\n const positionCleared = waitForPositionClear(coin);\\n \\n if (!positionCleared) {\\n Log(`❌ ${coin}: 持仓未清空,取消开新仓`);\\n return false;\\n }\\n \\n // 步骤3: 再次确认无持仓\\n const stillHasPosition = hasPosition(coin);\\n \\n if (stillHasPosition) {\\n Log(`❌ ${coin}: 最终检查发现仍有持仓,取消开新仓`);\\n return false;\\n }\\n \\n Log(`✅ ${coin}: 持仓已确认清空,开始开新仓`);\\n \\n // 步骤4: 执行开新仓\\n const entrySuccess = executeEntry(coin, isLong);\\n \\n if (entrySuccess) {\\n Log(`✅ ${coin}: 反手操作完成 - 新方向=${isLong ? '多' : '空'}`);\\n return true;\\n } else {\\n Log(`❌ ${coin}: 开新仓失败,反手操作未完成`);\\n return false;\\n }\\n \\n } catch (e) {\\n Log(`❌ ${coin}: 反手操作失败: ${e.message}`);\\n return false;\\n }\\n}\\n\\n// ========== 主执行逻辑 ==========\\n\\nfunction main() {\\n // 获取AI输出\\n const aiOutput = $input.first().json.output;\\n \\n Log('📨 收到AI输出');\\n \\n // 解析信号\\n const signals = parseAIOutput(aiOutput);\\n \\n if (!signals || signals.length === 0) {\\n Log('⚠️ 未获取到有效的AI交易信号');\\n return { json: { processed: false, reason: 'No valid signals' } };\\n }\\n \\n Log(`🤖 解析到 ${signals.length} 个交易信号`);\\n Log(`💰 固定交易金额: $${CONFIG.FIXED_AMOUNT_USD}`);\\n Log(`📊 默认杠杆: ${CONFIG.DEFAULT_LEVERAGE}x`);\\n \\n // 执行结果统计\\n const executionResults = {};\\n let totalExecutions = 0;\\n \\n // 处理每个信号\\n for (let signalInfo of signals) {\\n const coin = signalInfo.coin;\\n const signal = signalInfo.signal;\\n \\n Log(`\\\\n${'='.repeat(60)}`);\\n Log(`🎯 处理 ${coin} - 决策: ${signalInfo.originalDecision}`);\\n Log(` 新闻参考: ${signalInfo.newsAnalysis}`);\\n Log(` 决策理由: ${signalInfo.overallJudgment}`);\\n Log(` 评分: ${signalInfo.score}`);\\n Log(` 原有持仓: ${signalInfo.currentPosition}`);\\n \\n const hasPos = hasPosition(coin);\\n let executed = false;\\n let skipReason = '';\\n let operations = 0; // 记录操作次数(反手=2次)\\n \\n // 根据信号执行操作\\n switch (signal) {\\n case 'buy':\\n if (hasPos) {\\n skipReason = '已有持仓';\\n } else {\\n executed = executeEntry(coin, true);\\n if (executed) operations = 1;\\n }\\n break;\\n \\n case 'sell':\\n if (hasPos) {\\n skipReason = '已有持仓';\\n } else {\\n executed = executeEntry(coin, false);\\n if (executed) operations = 1;\\n }\\n break;\\n \\n case 'close':\\n if (!hasPos) {\\n skipReason = '无持仓';\\n } else {\\n executed = executeClose(coin, 'AI平仓信号');\\n if (executed) operations = 1;\\n }\\n break;\\n \\n case 'close_then_buy':\\n if (!hasPos) {\\n skipReason = '无持仓无法反手';\\n } else {\\n executed = executeReverse(coin, true, '平空开多');\\n if (executed) operations = 2; // 平仓+开仓\\n }\\n break;\\n \\n case 'close_then_sell':\\n if (!hasPos) {\\n skipReason = '无持仓无法反手';\\n } else {\\n executed = executeReverse(coin, false, '平多开空');\\n if (executed) operations = 2; // 平仓+开仓\\n }\\n break;\\n \\n case 'hold':\\n skipReason = hasPos ? '持仓观望' : '空仓等待';\\n break;\\n \\n default:\\n skipReason = '未知信号';\\n }\\n \\n // 累计执行次数\\n totalExecutions += operations;\\n \\n // 记录结果\\n executionResults[coin] = {\\n signal: signal,\\n decision: signalInfo.originalDecision,\\n newsAnalysis: signalInfo.newsAnalysis,\\n overallJudgment: signalInfo.overallJudgment,\\n executed: executed,\\n operations: operations,\\n skipReason: skipReason,\\n score: signalInfo.score,\\n timestamp: Date.now()\\n };\\n \\n // 输出结果\\n if (executed) {\\n Log(`✅ ${coin}: ${signalInfo.originalDecision} 执行成功 (操作${operations}次)`);\\n } else if (skipReason) {\\n Log(`⏭️ ${coin}: ${signalInfo.originalDecision} 跳过 - ${skipReason}`);\\n } else {\\n Log(`❌ ${coin}: ${signalInfo.originalDecision} 执行失败`);\\n }\\n \\n Sleep(500);\\n }\\n \\n // 保存执行结果\\n const summary = {\\n results: executionResults,\\n timestamp: Date.now(),\\n totalSignals: signals.length,\\n totalExecutions: totalExecutions,\\n fixedAmountUSD: CONFIG.FIXED_AMOUNT_USD,\\n leverage: CONFIG.DEFAULT_LEVERAGE\\n };\\n \\n _G('latestExecutionResults', summary);\\n \\n Log(`\\\\n${'='.repeat(60)}`);\\n Log(`📊 执行总结:`);\\n Log(` 信号总数: ${signals.length}`);\\n Log(` 执行次数: ${totalExecutions}`);\\n Log(` 固定金额: $${CONFIG.FIXED_AMOUNT_USD}`);\\n Log(` 杠杆倍数: ${CONFIG.DEFAULT_LEVERAGE}x`);\\n Log(`${'='.repeat(60)}`);\\n \\n return { \\n json: { \\n processed: true, \\n totalSignals: signals.length,\\n totalExecutions: totalExecutions,\\n results: executionResults\\n } \\n };\\n \\n \\n}\\n\\n// 执行主函数\\nreturn main();\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[1408,160],\"id\":\"628efea5-55c9-4b0d-8fe6-1f9ac91b0813\",\"name\":\"交易执行\"},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"seconds\",\"secondsInterval\":15}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-512,304],\"id\":\"280457ba-a4b7-4722-95b9-034400b7adcd\",\"name\":\"止盈止损触发器\",\"notesInFlow\":false,\"logLevel\":0}],\"pinData\":{},\"connections\":{\"定时触发器\":{\"main\":[[{\"node\":\"初始设置\",\"type\":\"main\",\"index\":0}]]},\"OpenAI 模型\":{\"ai_languageModel\":[[{\"node\":\"AI 智能体\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"AI 智能体\":{\"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}]]},\"实时新闻获取\":{\"main\":[[{\"node\":\"交易判断\",\"type\":\"main\",\"index\":0}]]},\"交易判断\":{\"main\":[[{\"node\":\"无交易处理\",\"type\":\"main\",\"index\":0}],[{\"node\":\"AI 智能体\",\"type\":\"main\",\"index\":0}]]},\"止盈止损触发器\":{\"main\":[[{\"node\":\"止盈止损监控\",\"type\":\"main\",\"index\":0}]]}},\"active\":false,\"settings\":{\"timezone\":\"Asia/Shanghai\",\"executionOrder\":\"v1\"},\"tags\":[],\"meta\":{\"templateCredsSetupCompleted\":true},\"credentials\":{},\"id\":\"c24195f3-b2f5-47a1-a0aa-5acf1a5566f9\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"定时触发器\"}}"}