Polymarket 多角色智能分析交易策略


创建日期: 2026-02-26 15:46:39 最后修改: 2026-03-09 11:08:53
复制: 22 点击次数: 178
avatar of ianzeng123 ianzeng123
2
关注
413
关注者

策略概述

本策略专为 Polymarket 预测市场设计,融合 K 线技术分析、新闻舆论判断与 AI 多角色决策框架,自动识别被市场低估的事件概率标的,执行买入并通过动态移动止损保护利润。


核心逻辑

一、信号生成(主线,每10分钟运行)

策略主线按以下三个步骤依次运行:

Step 1:初步筛选

从交易所获取全量市场标的,通过以下硬性条件过滤:

过滤条件 默认阈值
最低流动性(USDC) 10,000
24小时最低成交量(USDC) 100
最大价差比例 15%
市场活跃度(competitive) < 90%
价格区间(隐含概率) 5% ~ 50%

对同一基础事件取价格最低方向(寻找低价标的),并按流动性/成交量比(LV Ratio)降序排列。


Step 2:K 线异常检测

对通过初步筛选的标的,获取 60 分钟 K 线(最近 240 根),通过算法识别以下异常形态,进行叠加评分(最低 2 分且至少两种叠加方可进入 AI 分析):

异常类型 检测方法
缓慢爬升 近 120 根总涨幅 > 5%,单根最大涨幅 < 1.5%
成交量线性增长 近 60 根成交量做线性回归,斜率 > 0 且 R² > 0.5
回调收窄 历史回调幅度后段均值 < 前段均值 × 60%
横盘突破 MA60 与 MA120 偏差 < 2%,当前价 > MA60 × 1.03
成交量突增 近 5 根均量 > 前 60 根基准均量 2.5 倍

Step 3:AI 多角色分析(DeepSeek V3)

对通过 K 线筛选的标的,调用 AI 依次扮演四个专家角色:

  • 角色1 — K 线资金分析师:判断是否有真实资金持续吸筹,输出资金信号强度(强/中/基准)。
  • 角色2 — 新闻舆论分析师:通过 Brave Search 搜索相关新闻,判断信息面是否支撑价格上涨,输出新闻支撑等级(强/弱/无)。
  • 角色3 — 价格评估分析师:综合资金信号与新闻,估算真实概率区间,结合到期日规则输出价格评估(严重低估/低估/合理/高估)。
  • 角色4 — 最终决策分析师:按决策矩阵汇总,输出买入/观望/不操作及置信度(高/中)。

决策矩阵:

资金信号 \ 新闻支撑 强验证 弱验证 无验证
强(score 4~5) 买入,confidence=高 买入,confidence=中 不操作
中(score 3) 买入,confidence=中 观望 不操作
基准(score 2) 观望 不操作 不操作

到期日限制规则(硬性优先): - 距今 > 7天:正常评估 - 距今 4~7天:低估幅度超过市场价50%自动降级 - 距今 ≤ 3天 且价格 > 0.15:禁止操作(价格已收敛) - 距今 ≤ 3天 且价格 ≤ 0.15:允许参与,置信度上限为「中」


二、交易执行(主线延续)

当 AI 给出买入信号后,从所有买入候选中选出唯一最优标的执行交易,优先级规则如下:

  1. 置信度高 > 中
  2. 价格评估:严重低估 > 低估
  3. 真实概率区间越窄越确定

执行时通过 GetDepth 获取实时 Ask 价格,检查账户余额,按配置份额下单(最小下单金额 1 USDC)。订单超时 30 秒后自动撤单。


三、风险管理(副线,每30秒运行)

副线独立运行,负责:

持仓止损监控(移动止损):

  • 实时获取持仓 Bid 价,维护历史最高价(只涨不跌)
  • 计算移动止损价 = 历史最高价 - 止损绝对跌幅(默认 0.03 USDC)
  • 计算绝对兜底价 = 入场价 × 50%
  • 取两者较高值作为实际止损价
  • 触及止损线时以市价卖出

自动 Redeem: - 每轮检查持仓中标记为 redeemable 的已结算头寸,自动赎回。

可视化仪表盘(实时刷新): - 账户概览表:余额、总盈亏、参数配置 - AI 决策信号表:本轮所有标的的分析结论与执行状态 - 持仓监控表:每个持仓的当前盈亏、最高盈利、回撤、止损价、止损模式


参数说明

参数名 说明 默认值
Shares 每次买入份额 自定义
lossAmount 移动止损绝对跌幅(USDC) 0.03
MinLiquidity 最低流动性门槛(USDC) 10,000
MinVolume24hr 24小时最低成交量(USDC) 100
MaxSpreadPct 最大允许价差比例 0.15
MaxCompetitive 最大市场竞争度 0.90
MinTruePrice 最低价格(隐含概率下限) 0.05
MaxTruePrice 最高价格(隐含概率上限) 0.50
BraveApiKey Brave Search API Key 需自行配置

策略特点

  • 三重筛选:硬性条件 → K 线量化异常 → AI 多角色定性分析,层层过滤噪音
  • 只做最优:每轮即使有多个买入信号,也只执行置信度最高的一个,控制风险
  • 动态止损:移动止损 + 绝对兜底双重保护,充分锁利且防止极端亏损
  • 到期日感知:越临近到期越保守,避免末日博弈陷阱
  • 自动 Redeem:结算后头寸自动赎回,无需人工干预

风险提示

  1. 预测市场存在流动性不足风险,大单可能无法成交。
  2. AI 分析依赖新闻质量,突发事件可能导致判断滞后。
  3. 到期日临近时市场价格收敛加速,持仓风险显著上升。
  4. 本策略仅适用于支持预测市场交易的发明者平台接入。
  5. 过往 K 线异常信号不代表未来价格必然上涨,存在亏损可能。

视频链接:6万个预测合约怎么选?AI帮我分析

策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"splitInBatchesNotice\":\"\",\"batchSize\":12,\"options\":{}},\"type\":\"n8n-nodes-base.splitInBatches\",\"typeVersion\":3,\"position\":[-336,-48],\"id\":\"22be1bd3-4941-40c0-8fe5-7b127da831ba\",\"name\":\"循环处理项目\"},{\"parameters\":{\"text\":\"=你是一个预测市场分析团队,核心任务只有一个:判断当前市场价格是否被低估,值得买入等待价格上涨。\\n由四个专家角色依次完成分析,每个角色只做自己最擅长的判断。\\n\\n## 输入数据\\n市场标识:{{$json.symbol}}\\n交易方向:{{$json.direction}}\\n当前价格:{{$json.price}}(即市场对该事件发生的隐含概率)\\n流动性:{{$json.liquidity}} USDC\\n24小时成交量:{{$json.volume24hr}} USDC\\n流动性/成交量比:{{$json.lvRatio}}\\n到期日:{{$json.endDate}}\\nK线异常评分:{{$json.anomalyScore}}/5(已经过初步筛选,最低2分且至少两种异常叠加)\\nK线异常详情:{{$json.anomalyDetails}}\\n相关新闻:{{JSON.stringify($json.news)}}\\n\\n## 四位专家依次分析(内部推理,不输出过程)\\n\\n**【角色1】K线资金分析师**\\n专注判断资金行为,不看新闻,只看价格和成交量异常信号。\\n核心问题:是否有真实资金在持续买入这个价格?\\n\\n根据anomalyDetails判断异常类型:\\n- 「缓慢爬升 + 成交量线性增长」→ 资金持续隐秘吸筹,认为当前价格低估\\n- 「横盘突破 + 成交量突增」→ 资金加速入场,突破前期压力位\\n- 「回调收窄」叠加其他异常 → 筹码趋于稳定,抛压减少\\n- 异常种类越多叠加 → 资金行为可信度越高\\n\\n评级标准:\\n- score 4~5 → 强:资金行为明确,持续性高\\n- score 3 → 中:资金行为存在但需验证\\n- score 2 → 基准:资金行为较弱,需强新闻支撑\\n\\n核心输出:资金信号强度(强/中/基准)、资金是否认为当前价格被低估\\n\\n**【角色2】新闻舆论分析师**\\n专注从新闻中挖掘信息,不看K线,只判断信息面。\\n核心问题:有没有信息支撑这个价格应该更高?\\n\\n- 是否有直接利好该方向的最新事件,但价格尚未反应?\\n- 是否有蛛丝马迹(政策信号、人事变动、行业动态)暗示结果概率正在上升?\\n- 这些信息是否已被市场充分定价,还是仍存在滞后空间?\\n- 新闻时效性如何,是否足够新鲜?\\n\\n核心输出:新闻支撑等级(强/弱/无)、信息是否支撑价格继续上涨、市场是否存在定价滞后\\n\\n**【角色3】价格评估分析师**\\n综合角色1和角色2的结论,评估当前价格的合理性和上涨空间。\\n核心问题:当前价格处于什么位置,还有多少上涨空间?\\n\\n判断维度:\\n- 结合历史同类事件基准,当前价格是否明显偏低?\\n- 资金持续买入 + 新闻支撑 → 价格大概率继续上涨\\n- 资金买入但新闻滞后 → 价格可能已部分反应,空间有限\\n- 估算真实概率区间(true_probability)作为价格合理性的参考锚点\\n- 给出明确的价格评估:严重低估 / 低估 / 合理 / 高估\\n\\n**到期日权重规则(必须严格执行,优先级最高):**\\n首先根据当前日期与endDate计算距今剩余天数,然后执行对应规则:\\n\\n- 距今 > 7天:正常估算,不受限制\\n\\n- 距今 4~7天:\\n  · 市场价格 > 0.10:区间收窄至±8%以内,低估幅度超过市场价格50%自动降级为「合理」\\n  · 市场价格 ≤ 0.10:极低赔率区间,豁免降级限制,允许正常评估低估程度,但必须有明确的资金或新闻依据\\n\\n- 距今 ≤ 3天:\\n  · 市场价格 > 0.15:价格已充分收敛,市场参与者掌握最新信息比AI更可信,true_probability区间强制收窄至市场价±3%以内,禁止输出「严重低估」,价格评估最高只能给「合理」\\n  · 市场价格 ≤ 0.15:极低赔率的高风险博弈窗口,反而需要重点评估\\n    - 有强资金信号(score 4~5)且新闻强验证 → 允许给出「严重低估」,true_probability可正常估算\\n    - 有中等资金信号(score 3)或弱新闻支撑其中之一 → 最高给「低估」\\n    - 资金基准且无新闻 → 维持「合理」\\n    - 无论何种情况,必须在price_gap中明确说明到期临近的风险\\n\\n核心输出:true_probability区间、价格评估结论、预估上涨空间\\n\\n**【角色4】最终决策分析师**\\n汇总前三位专家结论,做出最终买入决策。\\n核心原则:资金行为是前提,新闻是验证,价格低估是必要条件,三者缺一不可。\\n\\n决策矩阵:\\n| 资金信号 \\\\ 新闻支撑 | 强验证              | 弱验证              | 无验证  |\\n|---|---|---|---|\\n| 强(score 4~5)     | 买入,confidence=高 | 买入,confidence=中 | 不操作  |\\n| 中(score 3)       | 买入,confidence=中 | 观望                | 不操作  |\\n| 基准(score 2)     | 观望                | 不操作              | 不操作  |\\n\\n硬性门槛(任一不满足→不操作):\\n① 价格评估为「低估」或「严重低估」\\n② 到期日规则:\\n   - 距今 > 3天:正常参与\\n   - 距今 ≤ 3天且市场价格 > 0.15:不操作(价格已收敛,无套利空间)\\n   - 距今 ≤ 3天且市场价格 ≤ 0.15:允许参与,但confidence最高只能为「中」\\n\\n## 输出格式\\n严格返回以下JSON结构,不要输出任何其他内容,不要有markdown代码块:\\n\\n{\\n  \\\"symbol\\\": \\\"市场标识\\\",\\n  \\\"event_summary\\\": \\\"一句话说明该市场在预测什么\\\",\\n  \\\"kline_interpretation\\\": \\\"角色1结论:资金信号强度,资金是否认为当前价格被低估\\\",\\n  \\\"news_interpretation\\\": \\\"角色2结论:新闻支撑等级,信息是否支撑价格继续上涨,无支撑写null\\\",\\n  \\\"true_probability\\\": {\\n    \\\"min\\\": 0.00,\\n    \\\"max\\\": 0.00\\n  },\\n  \\\"price_assessment\\\": \\\"严重低估/低估/合理/高估\\\",\\n  \\\"market_price\\\": 0.00,\\n  \\\"price_gap\\\": \\\"角色3结论:当前价格与真实概率区间差距及预估上涨空间,到期日≤3天时必须说明到期临近风险\\\",\\n  \\\"mispriced\\\": true,\\n  \\\"action\\\": \\\"买入/观望/不操作\\\",\\n  \\\"action_reason\\\": \\\"角色4结论:一句话说明资金、新闻、价格三者如何共同支撑决策,触发硬性门槛时需明确说明原因\\\",\\n  \\\"confidence\\\": \\\"高/中/低\\\",\\n  \\\"risks\\\": [\\\"风险1\\\", \\\"风险2\\\"]\\n}\",\"options\":{}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[-112,64],\"id\":\"a60458e0-683f-469d-ba1e-8a2cdb68222e\",\"name\":\"AI 智能体\",\"retryOnFail\":true},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"deepseek/deepseek-v3.2\",\"mode\":\"list\",\"cachedResultName\":\"deepseek/deepseek-v3.2\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[-16,288],\"id\":\"f6d4e500-5e05-4102-85ff-9697bb405306\",\"name\":\"OpenAI 模型\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 合并节点:初步筛选 + K线异常检测 + 新闻搜索 ==========\\n\\n// -------------------- 读取配置参数 --------------------\\nconst CONFIG = {\\n    // API\\n    BRAVE_API_KEY:    $vars.BraveApiKey,\\n\\n    // 初步筛选标准(策略参数)\\n    MIN_LIQUIDITY:    parseFloat($vars.MinLiquidity   || 10000),\\n    MIN_VOLUME_24HR:  parseFloat($vars.MinVolume24hr  || 100),\\n    MAX_SPREAD_PCT:   parseFloat($vars.MaxSpreadPct   || 0.15),\\n    MAX_COMPETITIVE:  parseFloat($vars.MaxCompetitive || 0.90),\\n    MIN_TRUE_PRICE:   parseFloat($vars.MinTruePrice   || 0.05),\\n    MAX_TRUE_PRICE:   parseFloat($vars.MaxTruePrice   || 0.50),\\n\\n    // 内部固定参数(不对外暴露)\\n    MIN_ANOMALY_SCORE:  2,\\n    KLINE_INTERVAL:     60,\\n    KLINE_LIMIT:        240,\\n    NEWS_INTERVAL_MS:   1200,\\n}\\n\\n// -------------------- 工具函数 --------------------\\nfunction linearRegression(values) {\\n    let n = values.length\\n    let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0\\n    for (let i = 0; i < n; i++) {\\n        sumX += i; sumY += values[i]\\n        sumXY += i * values[i]; sumX2 += i * i\\n    }\\n    let slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)\\n    let intercept = (sumY - slope * sumX) / n\\n    let meanY = sumY / n\\n    let ssTot = 0, ssRes = 0\\n    for (let i = 0; i < n; i++) {\\n        ssTot += Math.pow(values[i] - meanY, 2)\\n        ssRes += Math.pow(values[i] - (slope * i + intercept), 2)\\n    }\\n    let r2 = ssTot === 0 ? 0 : 1 - ssRes / ssTot\\n    return { slope, intercept, r2 }\\n}\\n\\nfunction detectAnomaly(closes, volumes) {\\n    if (closes.length < 60) return null\\n    let anomalies = [], score = 0\\n\\n    let slice120 = closes.slice(-Math.min(120, closes.length))\\n    let totalChange = (slice120[slice120.length - 1] - slice120[0]) / slice120[0]\\n    let maxSingle = 0\\n    for (let i = 1; i < slice120.length; i++) {\\n        let change = Math.abs(slice120[i] - slice120[i - 1]) / slice120[i - 1]\\n        if (change > maxSingle) maxSingle = change\\n    }\\n    if (totalChange > 0.05 && maxSingle < 0.015) {\\n        score++\\n        anomalies.push(`缓慢爬升 总涨${(totalChange * 100).toFixed(1)}% 单根最大${(maxSingle * 100).toFixed(2)}%`)\\n    }\\n\\n    let volSlice = volumes.slice(-60)\\n    let volReg = linearRegression(volSlice)\\n    if (volReg.slope > 0 && volReg.r2 > 0.5) {\\n        score++\\n        anomalies.push(`成交量线性增长 斜率${volReg.slope.toFixed(4)} R²${volReg.r2.toFixed(2)}`)\\n    }\\n\\n    let pullbacks = [], localHigh = closes[0], inPullback = false, pullbackStart = 0\\n    for (let i = 1; i < slice120.length; i++) {\\n        if (closes[i] > localHigh) {\\n            if (inPullback) { pullbacks.push((localHigh - closes[pullbackStart]) / localHigh); inPullback = false }\\n            localHigh = closes[i]\\n        } else if (closes[i] < localHigh * 0.99) {\\n            if (!inPullback) { inPullback = true; pullbackStart = i }\\n        }\\n    }\\n    if (pullbacks.length >= 3) {\\n        let first = pullbacks.slice(0, Math.floor(pullbacks.length / 2))\\n        let last = pullbacks.slice(Math.floor(pullbacks.length / 2))\\n        let firstAvg = first.reduce((a, b) => a + b, 0) / first.length\\n        let lastAvg = last.reduce((a, b) => a + b, 0) / last.length\\n        if (lastAvg < firstAvg * 0.6) {\\n            score++\\n            anomalies.push(`回调收窄 早期${(firstAvg * 100).toFixed(2)}% 近期${(lastAvg * 100).toFixed(2)}%`)\\n        }\\n    }\\n\\n    let ma60 = closes.slice(-60).reduce((a, b) => a + b, 0) / 60\\n    let ma120 = closes.length >= 120 ? closes.slice(-120).reduce((a, b) => a + b, 0) / 120 : ma60\\n    let currentPrice = closes[closes.length - 1]\\n    if (Math.abs(ma60 - ma120) / ma120 < 0.02 && currentPrice > ma60 * 1.03) {\\n        score++\\n        anomalies.push(`横盘突破 MA60=${ma60.toFixed(3)} MA120=${ma120.toFixed(3)} 当前=${currentPrice.toFixed(3)}`)\\n    }\\n\\n    let recentVol = volumes.slice(-5).reduce((a, b) => a + b, 0) / 5\\n    let baseVol = volumes.slice(-65, -5).reduce((a, b) => a + b, 0) / 60\\n    if (baseVol > 0 && recentVol > baseVol * 2.5) {\\n        score++\\n        anomalies.push(`成交量突增 近5根均量是基准${(recentVol / baseVol).toFixed(1)}倍`)\\n    }\\n\\n    return { score, anomalies }\\n}\\n\\n// -------------------- Step 1: 初步筛选 --------------------\\nlet markets = exchange.GetMarkets()\\nlet marketMap = {}\\n\\nfor (let symbol in markets) {\\n    let market = markets[symbol]\\n    let info = market.Info\\n\\n    let outcomes = [], outcomePrices = []\\n    try {\\n        outcomes = JSON.parse(info.outcomes || '[]')\\n        outcomePrices = JSON.parse(info.outcomePrices || '[]')\\n    } catch (e) { continue }\\n\\n    if (outcomes.length === 0 || outcomePrices.length === 0) continue\\n    if (outcomes.length !== outcomePrices.length) continue\\n\\n    let suffix = symbol.includes('_USDC.') ? symbol.split('_USDC.').pop() : null\\n    if (!suffix) continue\\n\\n    let outcomeIndex = outcomes.findIndex(o => o.toLowerCase() === suffix.toLowerCase())\\n    if (outcomeIndex === -1) continue\\n\\n    let truePrice = parseFloat(outcomePrices[outcomeIndex] || 0)\\n    if (truePrice <= 0) continue\\n    if (truePrice < CONFIG.MIN_TRUE_PRICE || truePrice > CONFIG.MAX_TRUE_PRICE) continue\\n\\n    let isActive = (\\n        info.active === true && info.approved === true &&\\n        info.closed === false && info.archived === false &&\\n        info.acceptingOrders === true && info.pendingDeployment === false\\n    )\\n    if (!isActive) continue\\n\\n    let liquidity = parseFloat(info.liquidity || 0)\\n    if (liquidity < CONFIG.MIN_LIQUIDITY) continue\\n\\n    let ask = parseFloat(info.bestAsk || 0)\\n    let bid = parseFloat(info.bestBid || 0)\\n    if (ask <= 0 || bid <= 0) continue\\n\\n    let spreadPct = (ask - bid) / ask\\n    if (spreadPct > CONFIG.MAX_SPREAD_PCT) continue\\n\\n    let volume24hr = parseFloat(info.volume24hr || 0)\\n    if (volume24hr < CONFIG.MIN_VOLUME_24HR) continue\\n\\n    let competitive = parseFloat(info.competitive || 0)\\n    if (competitive > CONFIG.MAX_COMPETITIVE) continue\\n\\n    let baseSymbol = symbol.split('_USDC.')[0]\\n    let lvRatio = volume24hr > 0 ? liquidity / volume24hr : 999\\n\\n    let entry = {\\n        symbol, direction: suffix, price: truePrice,\\n        ask, bid, spread: spreadPct.toFixed(4),\\n        liquidity, volume24hr,\\n        lvRatio: parseFloat(lvRatio.toFixed(2)),\\n        competitive,\\n        endDate: info.endDate || '无固定到期'\\n    }\\n\\n    if (!marketMap[baseSymbol]) {\\n        marketMap[baseSymbol] = entry\\n    } else if (truePrice < marketMap[baseSymbol].price) {\\n        marketMap[baseSymbol] = entry\\n    }\\n}\\n\\nlet suitable = Object.values(marketMap)\\nsuitable.sort((a, b) => b.lvRatio - a.lvRatio)\\nLog('初步筛选数量:', suitable.length)\\n\\n// -------------------- Step 2: K线异常检测 --------------------\\nlet anomalyResults = []\\n\\nfor (let item of suitable) {\\n    let records\\n    try {\\n        records = exchange.GetRecords(item.symbol, 60, 240)\\n    } catch(e) {\\n        Sleep(200)\\n        continue\\n    }\\n    if (!records || records.length < 60) { Sleep(200); continue }\\n\\n    let closes = records.map(r => r.Close)\\n    let volumes = records.map(r => r.Volume)\\n    let result = detectAnomaly(closes, volumes)\\n    if (!result || result.score < CONFIG.MIN_ANOMALY_SCORE) { Sleep(200); continue }\\n\\n    anomalyResults.push({\\n        symbol:         item.symbol,\\n        direction:      item.direction,\\n        price:          item.price,\\n        liquidity:      item.liquidity,\\n        volume24hr:     item.volume24hr,\\n        lvRatio:        item.lvRatio,\\n        endDate:        item.endDate,\\n        anomalyScore:   result.score,\\n        anomalyDetails: result.anomalies\\n    })\\n    Sleep(200)\\n}\\n\\nanomalyResults.sort((a, b) => b.anomalyScore - a.anomalyScore || b.lvRatio - a.lvRatio)\\nLog('异常市场数量:', anomalyResults.length)\\n\\nLog('========== 异常标的列表 ==========')\\nfor (const item of anomalyResults) {\\n    Log(JSON.stringify({\\n        symbol:         item.symbol,\\n        direction:      item.direction,\\n        price:          item.price,\\n        liquidity:      item.liquidity,\\n        volume24hr:     item.volume24hr,\\n        lvRatio:        item.lvRatio,\\n        endDate:        item.endDate,\\n        anomalyScore:   item.anomalyScore,\\n        anomalyDetails: item.anomalyDetails\\n    }))\\n}\\nLog('==================================')\\n\\n// -------------------- Step 3: 新闻搜索 --------------------\\nconst finalResults = []\\n\\nfor (const item of anomalyResults) {\\n    const q = item.symbol\\n        .replace(\\\"_USDC.No\\\", \\\"\\\")\\n        .replace(\\\"_USDC.Yes\\\", \\\"\\\")\\n        .replace(/-/g, \\\"+\\\")\\n\\n    let news = []\\n    try {\\n        const rawResponse = HttpQuery(\\n            \\\"https://api.search.brave.com/res/v1/web/search?q=\\\" + q,\\n            {\\n                method: \\\"GET\\\",\\n                headers: {\\n                    \\\"X-Subscription-Token\\\": CONFIG.BRAVE_API_KEY,\\n                    \\\"Accept\\\": \\\"application/json\\\"\\n                }\\n            }\\n        )\\n\\n        const data = JSON.parse(rawResponse)\\n\\n        Object.keys(data).forEach(key => {\\n            const section = data[key]\\n            if (section && section.results && Array.isArray(section.results)) {\\n                section.results.forEach(r => {\\n                    if (r.title || r.description || r.age) {\\n                        news.push({\\n                            type:        key,\\n                            title:       r.title       || \\\"\\\",\\n                            description: r.description || \\\"\\\",\\n                            age:         r.age         || \\\"\\\"\\n                        })\\n                    }\\n                })\\n            }\\n        })\\n    } catch (e) {\\n        Log(`⚠️ 新闻搜索失败 ${item.symbol}: ${e.message}`)\\n    }\\n\\n    Sleep(CONFIG.NEWS_INTERVAL_MS)\\n    finalResults.push({ ...item, news })\\n}\\n\\n\\n\\nLog('最终输出数量:', finalResults.length)\\nreturn finalResults\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-560,-48],\"id\":\"735acc0e-c3e2-49d5-8000-ecbaa37621fe\",\"name\":\"筛选标的\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== PM预测市场交易执行系统 v4 ==========\\nconst CONFIG = {\\n    SHARES:               $vars.Shares,\\n    MAX_ORDER_RETRY:      3,\\n    ORDER_RETRY_INTERVAL: 1000,\\n    ORDER_TIMEOUT_S:      30,\\n    MIN_ORDER_USDC:       1  // 最小下单金额\\n}\\n\\nfunction parseAIOutput(output) {\\n    try {\\n        Log('📥 开始解析AI输出...')\\n\\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        const items = Array.isArray(parsed) ? parsed : [parsed]\\n        const signals = []\\n\\n        for (let item of items) {\\n            if (!item.symbol || !item.action) {\\n                Log(`⚠️ 跳过无效条目: ${JSON.stringify(item)}`)\\n                continue\\n            }\\n\\n            let signal\\n            switch (item.action) {\\n                case '买入': signal = 'buy'; break\\n                default:     signal = 'skip'\\n            }\\n\\n            signals.push({\\n                symbol:          item.symbol,\\n                eventSummary:    item.event_summary    || '',\\n                actionReason:    item.action_reason    || '',\\n                confidence:      item.confidence       || '',\\n                marketPrice:     item.market_price     || 0,\\n                trueProb:        item.true_probability || {},\\n                priceAssessment: item.price_assessment || '',\\n                priceGap:        item.price_gap        || '',\\n                mispriced:       item.mispriced        || false,\\n                signal,\\n                originalAction:  item.action\\n            })\\n        }\\n\\n        Log(`✅ 成功解析 ${signals.length} 个PM信号`)\\n        return signals\\n\\n    } catch (e) {\\n        Log(`❌ parseAIOutput失败: ${e.message}`)\\n        Log(`原始输出: ${JSON.stringify(output)}`)\\n        return []\\n    }\\n}\\n\\nfunction getAskPrice(symbol) {\\n    try {\\n        const depth = exchange.GetDepth(symbol)\\n        if (!depth || !depth.Asks || depth.Asks.length === 0) {\\n            Log(`❌ ${symbol}: GetDepth 返回无效数据`)\\n            return null\\n        }\\n        const ask = depth.Asks[0].Price\\n        Log(`📊 ${symbol}: Ask = ${ask}`)\\n        return ask\\n    } catch (e) {\\n        Log(`❌ ${symbol}: GetDepth 异常: ${e.message}`)\\n        return null\\n    }\\n}\\n\\n// ========== 计算实际下单份额 ==========\\nfunction calcShares(askPrice, configShares) {\\n    const usdcValue = askPrice * configShares\\n    if (usdcValue >= CONFIG.MIN_ORDER_USDC) {\\n        // 正常情况,使用配置份额\\n        Log(`📐 下单份额: ${configShares} shares | 金额: ${usdcValue.toFixed(4)} USDC`)\\n        return configShares\\n    }\\n\\n    const minShares = Math.ceil(1 / askPrice)\\n    Log(`📐 ask(${askPrice}) × shares(${configShares}) = ${usdcValue.toFixed(4)} USDC < ${CONFIG.MIN_ORDER_USDC}u`)\\n    Log(`📐 自动调整为最小下单份额: floor(1/${askPrice}) = ${minShares} shares`)\\n    return minShares\\n}\\n\\nfunction checkBalance(askPrice, shares) {\\n    const required = shares * askPrice\\n    let balance = 0\\n    try {\\n        const account = exchange.GetAccount()\\n        balance = account ? (account.Balance || 0) : 0\\n    } catch (e) {\\n        Log(`⚠️ GetAccount 异常: ${e.message}`)\\n    }\\n    return { ok: balance >= required, required, balance }\\n}\\n\\nfunction placeOrderAndConfirm(symbol, price, shares) {\\n    Log(`📋 下单 | BUY ${symbol} | 价格: ${price === -1 ? '市价' : price} | 份额: ${shares}`)\\n\\n    let orderId = null\\n    let attempts = 0\\n\\n    while (attempts < CONFIG.MAX_ORDER_RETRY) {\\n        attempts++\\n        try {\\n            exchange.SetDirection('buy')\\n            orderId = exchange.CreateOrder(symbol, 'buy', price, shares)\\n            if (orderId) break\\n            Log(`⚠️ 第${attempts}次下单未获得orderId,重试...`)\\n        } catch (e) {\\n            Log(`⚠️ 第${attempts}次下单异常: ${e.message}`)\\n        }\\n        Sleep(CONFIG.ORDER_RETRY_INTERVAL)\\n    }\\n\\n    if (!orderId) {\\n        Log(`❌ ${symbol}: 下单失败,已重试${attempts}次`)\\n        return { orderId: null, avgPrice: null }\\n    }\\n\\n    Log(`   orderId: ${orderId} | 开始轮询确认(超时: ${CONFIG.ORDER_TIMEOUT_S}s)`)\\n\\n    const deadline = Date.now() + CONFIG.ORDER_TIMEOUT_S * 1000\\n    while (Date.now() < deadline) {\\n        try {\\n            const order = exchange.GetOrder(orderId)\\n            if (!order) {\\n                Log(`   ⚠️ GetOrder 返回空,继续等待...`)\\n            } else {\\n                Log(`   状态: ${order.Status} | 已成交: ${order.DealAmount} | 均价: ${order.AvgPrice}`)\\n                if (order.Status === 1) {\\n                    Log(`   ✅ 订单完全成交 | 均价: ${order.AvgPrice}`)\\n                    return { orderId, avgPrice: order.AvgPrice }\\n                }\\n                if (order.Status === 2 || order.Status === 4) {\\n                    Log(`   ❌ 订单已撤销/失败 Status: ${order.Status}`)\\n                    return { orderId, avgPrice: null }\\n                }\\n            }\\n        } catch (e) {\\n            Log(`   ⚠️ GetOrder 异常: ${e.message}`)\\n        }\\n        Sleep(1000)\\n    }\\n\\n    Log(`   ⏰ 超时未成交,执行撤单`)\\n    try { exchange.CancelOrder(orderId) } catch (e) { Log(`   ⚠️ 撤单异常: ${e.message}`) }\\n\\n    while (true) {\\n        try {\\n            const finalOrder = exchange.GetOrder(orderId)\\n            if (finalOrder) {\\n                if (finalOrder.Status === 1) {\\n                    Log(`   ⚠️ 撤单时已成交 | 均价: ${finalOrder.AvgPrice}`)\\n                    return { orderId, avgPrice: finalOrder.AvgPrice }\\n                }\\n                if (finalOrder.Status === 2 || finalOrder.Status === 4) {\\n                    Log(`   ✅ 撤单确认成功`)\\n                    return { orderId, avgPrice: null }\\n                }\\n            }\\n        } catch (e) {\\n            Log(`   ⚠️ 撤单后查询异常: ${e.message}`)\\n        }\\n        Sleep(1000)\\n    }\\n}\\n\\nfunction main() {\\n    Log('📨 收到AI输出')\\n    Log(`📦 配置份额: ${CONFIG.SHARES} shares | 最小下单: ${CONFIG.MIN_ORDER_USDC} USDC`)\\n\\n    const allOutputs = $input.all().map(item => item.json.output)\\n\\n    const signals = []\\n    for (const aiOutput of allOutputs) {\\n        const parsed = parseAIOutput(aiOutput)\\n        signals.push(...parsed)\\n    }\\n\\n    if (!signals || signals.length === 0) {\\n        Log('⚠️ 未获取到有效的PM交易信号')\\n        return { json: { processed: false, reason: 'No valid signals' } }\\n    }\\n\\n    Log(`🤖 解析到 ${signals.length} 个PM信号`)\\n\\n    // ========== 只选最优一个买入信号 ==========\\n    const confidenceRank = { '高': 3, '中': 2, '低': 1 }\\n    const assessmentRank = { '严重低估': 2, '低估': 1 }\\n\\n    const buySignals = signals.filter(s => s.signal === 'buy')\\n\\n    let bestSignal = null\\n    if (buySignals.length > 0) {\\n        bestSignal = buySignals.sort((a, b) => {\\n            // 第一优先级:confidence\\n            const confDiff = (confidenceRank[b.confidence] || 0)\\n                           - (confidenceRank[a.confidence] || 0)\\n            if (confDiff !== 0) return confDiff\\n\\n            // 第二优先级:price_assessment\\n            const assessDiff = (assessmentRank[b.priceAssessment] || 0)\\n                             - (assessmentRank[a.priceAssessment] || 0)\\n            if (assessDiff !== 0) return assessDiff\\n\\n            // 第三优先级:true_probability 区间越窄越确定\\n            const aWidth = (a.trueProb.max || 0) - (a.trueProb.min || 0)\\n            const bWidth = (b.trueProb.max || 0) - (b.trueProb.min || 0)\\n            return aWidth - bWidth\\n        })[0]\\n\\n        Log(`🏆 最优买入标的: ${bestSignal.symbol}`)\\n        Log(`   confidence: ${bestSignal.confidence}`)\\n        Log(`   价格评估: ${bestSignal.priceAssessment}`)\\n    } else {\\n        Log('⚠️ 本轮无买入信号,全部观望或不操作')\\n    }\\n\\n    const executionResults = {}\\n    let totalExecuted = 0\\n\\n    for (const signalInfo of signals) {\\n        const { symbol, signal, originalAction } = signalInfo\\n        const isBest = bestSignal && symbol === bestSignal.symbol\\n\\n        Log(`\\\\n${'='.repeat(60)}`)\\n        Log(`🎯 处理: ${symbol}`)\\n        Log(`   事件: ${signalInfo.eventSummary}`)\\n        Log(`   决策: ${originalAction} | 信心: ${signalInfo.confidence}`)\\n        Log(`   市场价: ${signalInfo.marketPrice}`)\\n        Log(`   真实概率: ${JSON.stringify(signalInfo.trueProb)}`)\\n        Log(`   价格评估: ${signalInfo.priceAssessment}`)\\n        Log(`   价差描述: ${signalInfo.priceGap}`)\\n        Log(`   理由: ${signalInfo.actionReason}`)\\n        if (isBest) Log(`   ⭐ 本轮最优标的,执行交易`)\\n\\n        let executed   = false\\n        let skipReason = ''\\n        let avgPrice   = null\\n        let actualShares = CONFIG.SHARES\\n\\n        if (!isBest || signal !== 'buy') {\\n            skipReason = isBest\\n                ? `非买入信号(${originalAction})`\\n                : `非本轮最优标的,跳过(${originalAction})`\\n        } else {\\n            const askPrice = getAskPrice(symbol)\\n            if (!askPrice) {\\n                skipReason = 'GetDepth 获取Ask失败'\\n            } else {\\n                // 计算实际下单份额\\n                actualShares = calcShares(askPrice, CONFIG.SHARES)\\n                if (actualShares <= 0) {\\n                    skipReason = `份额计算异常: askPrice=${askPrice}`\\n                } else {\\n                    const balCheck = checkBalance(askPrice, actualShares)\\n                    if (!balCheck.ok) {\\n                        skipReason = `余额不足 | 需要 ${balCheck.required.toFixed(4)} USDC | 当前余额 ${balCheck.balance.toFixed(4)} USDC`\\n                        Log(`⚠️ ${symbol}: ${skipReason}`)\\n                    } else {\\n                        Log(`💰 ${symbol}: 余额充足 | 需要 ${balCheck.required.toFixed(4)} USDC | 当前余额 ${balCheck.balance.toFixed(4)} USDC`)\\n                        Log(`📥 ${symbol}: 准备买入 | Ask: ${askPrice} | 份额: ${actualShares}`)\\n                        const result = placeOrderAndConfirm(symbol, askPrice, actualShares)\\n                        if (result.avgPrice !== null) {\\n                            executed = true\\n                            avgPrice = result.avgPrice\\n                            totalExecuted++\\n                        } else {\\n                            skipReason = '订单未成交或已撤销'\\n                        }\\n                    }\\n                }\\n            }\\n        }\\n\\n        executionResults[symbol] = {\\n            decision:        originalAction,\\n            originalAction,\\n            signal,\\n            isBest:          isBest || false,\\n            shares:          actualShares,\\n            avgPrice,\\n            executed,\\n            skipReason,\\n            timestamp:       Date.now(),\\n            marketPrice:     signalInfo.marketPrice,\\n            trueProb:        signalInfo.trueProb,\\n            priceAssessment: signalInfo.priceAssessment,\\n            priceGap:        signalInfo.priceGap,\\n            mispriced:       signalInfo.mispriced,\\n            actionReason:    signalInfo.actionReason,\\n            confidence:      signalInfo.confidence\\n        }\\n\\n        if (executed) {\\n            Log(`✅ ${symbol}: 买入成功 | 均价: ${avgPrice} | ${actualShares} shares`)\\n        } else {\\n            Log(`⏭️ ${symbol}: 跳过 - ${skipReason}`)\\n        }\\n\\n        Sleep(500)\\n    }\\n\\n    _G('latestPMResults', {\\n        results:        executionResults,\\n        timestamp:      Date.now(),\\n        totalSignals:   signals.length,\\n        totalExecuted,\\n        shares:         CONFIG.SHARES\\n    })\\n\\n    Log(`\\\\n${'='.repeat(60)}`)\\n    Log(`📊 执行总结:`)\\n    Log(`   信号总数: ${signals.length}`)\\n    Log(`   买入成功: ${totalExecuted}`)\\n    Log(`   配置份额: ${CONFIG.SHARES} | 最小下单: ${CONFIG.MIN_ORDER_USDC} USDC`)\\n    Log(`${'='.repeat(60)}`)\\n\\n    return {\\n        json: {\\n            processed:    true,\\n            totalSignals: signals.length,\\n            totalExecuted,\\n            results:      executionResults\\n        }\\n    }\\n}\\n\\nreturn main()\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-32,-240],\"id\":\"77ea5ccd-a0b1-4594-85cb-e61ec2fefe06\",\"name\":\"交易执行\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 配置参数 ==========\\nconst CONFIG = {\\n    LOSS_AMOUNT:         parseFloat($vars.lossAmount || 0.03), // 移动止损绝对跌幅(USDC)\\n    FALLBACK_STOP_RATIO: 0.50,  // 绝对兜底:入场价 × 50%\\n    CHECK_INTERVAL:      500    // 每个持仓检查间隔(ms)\\n};\\n\\n// ========== Redeem 可赎回持仓 ==========\\nfunction doRedeem() {\\n    try {\\n        var positions = exchange.GetPositions();\\n        var count = 0;\\n        for (var i = 0; i < positions.length; i++) {\\n            var pos = positions[i];\\n            if (pos.Info && pos.Info.redeemable) {\\n                Log(\\\"💰 Redeem | Symbol:\\\", pos.Symbol,\\n                    \\\"| 数量:\\\", pos.Amount,\\n                    \\\"| eventSlug:\\\", pos.Info.eventSlug);\\n                var result = exchange.IO(\\\"redeem\\\", pos.Symbol, true);\\n                Log(\\\"Redeem 结果:\\\", result);\\n                count++;\\n                Sleep(300);\\n            }\\n        }\\n\\n        return count;\\n    } catch (e) {\\n        Log(\\\"❌ Redeem 异常:\\\", e);\\n        return 0;\\n    }\\n}\\n\\n// ========== GetDepth 获取 Bid/Ask ==========\\nfunction getDepthPrice(symbol) {\\n    try {\\n        const depth = exchange.GetDepth(symbol);\\n        if (!depth) {\\n            Log(`⚠️ ${symbol}: GetDepth 返回空`);\\n            return null;\\n        }\\n        const bid = (depth.Bids && depth.Bids.length > 0) ? depth.Bids[0].Price : 0;\\n        const ask = (depth.Asks && depth.Asks.length > 0) ? depth.Asks[0].Price : 0;\\n        if (bid <= 0 && ask <= 0) {\\n            Log(`⚠️ ${symbol}: bid/ask 均为0`);\\n            return null;\\n        }\\n        return { bid, ask };\\n    } catch (e) {\\n        Log(`⚠️ ${symbol}: GetDepth 异常: ${e.message}`);\\n        return null;\\n    }\\n}\\n\\n// ========== 计算动态止损价 ==========\\n/**\\n * @param {number} entryPrice  入场价\\n * @param {number} maxPrice    历史最高 bid\\n * @returns {{\\n *   effectiveStopPrice: number,  实际止损价\\n *   trailingStopPrice:  number,  移动止损价 = maxPrice - LOSS_AMOUNT\\n *   absoluteStopPrice:  number,  绝对兜底价 = entryPrice × 50%\\n *   usingFallback:      boolean  是否使用兜底\\n * }}\\n */\\nfunction calcStopPrice(entryPrice, maxPrice) {\\n    const trailingStopPrice  = maxPrice - CONFIG.LOSS_AMOUNT;\\n    const absoluteStopPrice  = entryPrice * CONFIG.FALLBACK_STOP_RATIO;\\n    const usingFallback      = trailingStopPrice < absoluteStopPrice;\\n    const effectiveStopPrice = usingFallback ? absoluteStopPrice : trailingStopPrice;\\n\\n    return { effectiveStopPrice, trailingStopPrice, absoluteStopPrice, usingFallback };\\n}\\n\\n// ========== 卖出平仓 ==========\\nfunction sellPosition(symbol, amount, reason, currentPrice, maxPrice, entryPrice) {\\n    try {\\n        Log(`🔔 触发${reason} | ${symbol}`);\\n        Log(`   入场价: ${entryPrice.toFixed(4)} | 最高价: ${maxPrice.toFixed(4)} | 当前价: ${currentPrice.toFixed(4)}`);\\n        Log(`   盈亏: ${(currentPrice - entryPrice).toFixed(4)} USDC | 卖出 ${amount} shares`);\\n\\n        const orderId = exchange.Sell(-1, amount, symbol);\\n\\n        if (orderId) {\\n            Log(`✅ ${symbol} 卖出成功 | orderId: ${orderId}`);\\n            _G(`pm_maxprice_${symbol}`, null);\\n            return true;\\n        } else {\\n            Log(`❌ ${symbol} 卖出失败,未获得 orderId`);\\n            return false;\\n        }\\n    } catch (e) {\\n        Log(`❌ ${symbol} 卖出异常: ${e.message}`);\\n        return false;\\n    }\\n}\\n\\n// ========== 移动止损监控核心 ==========\\nfunction monitorPMPosition(pos) {\\n    const symbol     = pos.Symbol;\\n    const amount     = pos.Amount;\\n    const entryPrice = pos.Price;\\n\\n    if (!amount || amount <= 0) {\\n        return { status: \\\"empty\\\", symbol };\\n    }\\n    if (!entryPrice || entryPrice <= 0) {\\n        return { status: \\\"invalid_entry\\\", symbol };\\n    }\\n\\n    // 1. GetDepth 获取实时 bid/ask\\n    const depthPrice = getDepthPrice(symbol);\\n    if (!depthPrice) {\\n        return { status: \\\"no_depth\\\", symbol };\\n    }\\n    const { bid, ask } = depthPrice;\\n    const currentPrice = bid > 0 ? bid : ask;\\n\\n    // 2. 更新历史最高 bid(只涨不跌)\\n    const cacheKey = `pm_maxprice_${symbol}`;\\n    let maxPrice = _G(cacheKey);\\n\\n    if (maxPrice === null) {\\n        maxPrice = currentPrice;\\n        _G(cacheKey, maxPrice);\\n        Log(`🆕 ${symbol} 初始化最高价: ${maxPrice.toFixed(4)}`);\\n    } else if (currentPrice > maxPrice + 0.001) {\\n        maxPrice = currentPrice;\\n        _G(cacheKey, maxPrice);\\n        Log(`📈 ${symbol} 更新最高价: ${maxPrice.toFixed(4)}`);\\n    }\\n\\n    // 3. 计算动态止损价\\n    const stopInfo = calcStopPrice(entryPrice, maxPrice);\\n\\n    // 4. 盈亏统计(USDC 绝对值)\\n    const currentPnlAbs = currentPrice - entryPrice;\\n    const maxPnlAbs     = maxPrice - entryPrice;\\n    const drawdownAbs   = maxPrice - currentPrice;\\n\\n    Log(`🔍 ${symbol} | bid:${bid.toFixed(4)} ask:${ask.toFixed(4)} | maxPrice:${maxPrice.toFixed(4)} | 止损价:${stopInfo.effectiveStopPrice.toFixed(4)} | 回撤:${drawdownAbs.toFixed(4)} | fallback:${stopInfo.usingFallback}`);\\n\\n    // 5. 判断移动止损\\n    if (currentPrice <= stopInfo.effectiveStopPrice) {\\n        const triggerType = stopInfo.usingFallback\\n            ? `🔐 绝对底部止损(入场价×50%=${stopInfo.absoluteStopPrice.toFixed(4)})`\\n            : `🛡️ 移动止损(最高价${maxPrice.toFixed(4)}-${CONFIG.LOSS_AMOUNT}=${stopInfo.trailingStopPrice.toFixed(4)})`;\\n        Log(`🔻 ${symbol} 触发${triggerType}`);\\n        const ok = sellPosition(symbol, amount, triggerType, currentPrice, maxPrice, entryPrice);\\n        return {\\n            status: ok ? \\\"closed_ts\\\" : \\\"close_failed\\\",\\n            symbol, amount, entryPrice, bid, ask, currentPrice, maxPrice,\\n            currentPnlAbs, maxPnlAbs, drawdownAbs,\\n            trigger: stopInfo.usingFallback ? \\\"absolute_stop\\\" : \\\"trailing_stop\\\",\\n            stopInfo\\n        };\\n    }\\n\\n    return {\\n        status: \\\"monitoring\\\",\\n        symbol, amount, entryPrice, bid, ask,\\n        currentPrice, maxPrice,\\n        currentPnlAbs, maxPnlAbs, drawdownAbs,\\n        stopInfo\\n    };\\n}\\n\\n// ========== 可视化:账户概览表 ==========\\nfunction createAccountTable() {\\n    const table = {\\n        type: \\\"table\\\",\\n        title: \\\"💰 账户概览\\\",\\n        cols: [\\\"初始余额(USDC)\\\", \\\"当前余额(USDC)\\\", \\\"总盈亏(USDC)\\\", \\\"总盈亏(%)\\\", \\\"移动止损跌幅(USDC)\\\", \\\"绝对兜底规则\\\"],\\n        rows: []\\n    };\\n\\n    try {\\n        const acc       = exchange.GetAccount();\\n        const initMoney = _G(\\\"initmoney\\\") || acc.Equity;\\n        const equity    = acc.Equity;\\n        const profit    = equity - initMoney;\\n        const profitPct = (profit / initMoney) * 100;\\n\\n        table.rows.push([\\n            `$${_N(initMoney, 2)}`,\\n            `$${_N(equity, 2)}`,\\n            profit >= 0 ? `🟢 +$${_N(profit, 2)}` : `🔴 $${_N(profit, 2)}`,\\n            profitPct >= 0 ? `🟢 +${_N(profitPct, 2)}%` : `🔴 ${_N(profitPct, 2)}%`,\\n            `🛡️ ${CONFIG.LOSS_AMOUNT}`,\\n            `🔐 入场价 × 50%`\\n        ]);\\n\\n        LogProfit(profit, \\\"&\\\");\\n    } catch (e) {\\n        Log(`❌ createAccountTable: ${e.message}`);\\n        table.rows.push([\\\"❌ 获取失败\\\", e.message.substring(0, 20), \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\"]);\\n    }\\n\\n    return table;\\n}\\n\\n// ========== 可视化:AI决策信号表 ==========\\nfunction createAIDecisionTable() {\\n    const table = {\\n        type: \\\"table\\\",\\n        title: \\\"🤖 AI 决策信号\\\",\\n        cols: [\\\"Symbol\\\", \\\"方向\\\", \\\"市场价\\\", \\\"AI真实概率\\\", \\\"价格评估\\\", \\\"行动\\\", \\\"执行结果\\\", \\\"核心理由\\\"],\\n        rows: []\\n    }\\n\\n    try {\\n        const pmData = _G(\\\"latestPMResults\\\")\\n        if (!pmData || !pmData.results || Object.keys(pmData.results).length === 0) {\\n            table.rows.push([\\\"📭 暂无AI决策\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"等待AI分析完成\\\"])\\n            return table\\n        }\\n\\n        const results    = pmData.results\\n        const updateTime = pmData.timestamp\\n            ? new Date(pmData.timestamp).toLocaleTimeString()\\n            : \\\"-\\\"\\n\\n        for (const [symbol, r] of Object.entries(results)) {\\n            const action = r.originalAction || r.decision || \\\"-\\\"\\n            let actionDisplay = action\\n            if (action === \\\"买入\\\")        actionDisplay = \\\"🟢 买入\\\"\\n            else if (action === \\\"观望\\\")   actionDisplay = \\\"⏸️ 观望\\\"\\n            else if (action === \\\"不操作\\\") actionDisplay = \\\"⏭️ 不操作\\\"\\n\\n            let execDisplay = \\\"-\\\"\\n            if (r.executed)        execDisplay = `✅ 成交 @ ${r.avgPrice ? _N(r.avgPrice, 4) : \\\"?\\\"}`\\n            else if (r.skipReason) execDisplay = `⏭️ ${r.skipReason}`\\n            else                   execDisplay = \\\"❌ 失败\\\"\\n\\n            const dir         = symbol.endsWith(\\\".Yes\\\") ? \\\"🟩 Yes\\\" : symbol.endsWith(\\\".No\\\") ? \\\"🟥 No\\\" : \\\"-\\\"\\n            const shortSymbol = symbol.replace(/_USDC\\\\.(Yes|No)$/, \\\"[$1]\\\")\\n            const symbolDisplay = r.isBest ? `⭐ ${shortSymbol}` : shortSymbol\\n\\n            const trueProb = r.trueProb\\n                ? `${_N((r.trueProb.min || 0) * 100, 1)}~${_N((r.trueProb.max || 0) * 100, 1)}%`\\n                : \\\"-\\\"\\n\\n            let assessDisplay = \\\"-\\\"\\n            if (r.priceAssessment) {\\n                if (r.priceAssessment.includes(\\\"严重低估\\\"))     assessDisplay = \\\"🔥 严重低估\\\"\\n                else if (r.priceAssessment.includes(\\\"低估\\\"))    assessDisplay = \\\"🟢 低估\\\"\\n                else if (r.priceAssessment.includes(\\\"合理\\\"))    assessDisplay = \\\"🟡 合理\\\"\\n                else if (r.priceAssessment.includes(\\\"高估\\\"))    assessDisplay = \\\"🔴 高估\\\"\\n                else                                            assessDisplay = r.priceAssessment\\n            }\\n\\n            table.rows.push([\\n                symbolDisplay,\\n                dir,\\n                r.marketPrice ? _N(r.marketPrice, 4) : \\\"-\\\",\\n                trueProb,\\n                assessDisplay,\\n                actionDisplay,\\n                execDisplay,\\n                r.actionReason || \\\"-\\\"\\n            ])\\n        }\\n\\n        const totalExecuted = Object.values(results).filter(r => r.executed).length\\n        table.rows.push([\\n            `📊 共 ${Object.keys(results).length} 个信号`,\\n            \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\",\\n            `✅ ${totalExecuted} 已买入`,\\n            `更新: ${updateTime}`,\\n            `份额/单: ${pmData.shares || \\\"-\\\"}`\\n        ])\\n\\n    } catch (e) {\\n        Log(`❌ createAIDecisionTable: ${e.message}`)\\n        table.rows.push([\\\"❌ 读取失败\\\", e.message.substring(0, 40), \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\"])\\n    }\\n\\n    return table\\n}\\n\\n// ========== 可视化:持仓监控表 ==========\\nfunction createPositionTable(monitorResults) {\\n    const table = {\\n        type: \\\"table\\\",\\n        title: \\\"📊 持仓监控\\\",\\n        cols: [\\n            \\\"Symbol\\\",\\n            \\\"份额\\\",\\n            \\\"入场价\\\",\\n            \\\"Bid / Ask\\\",\\n            \\\"当前盈亏\\\",\\n            \\\"最高盈利\\\",\\n            \\\"当前回撤\\\",\\n            \\\"实际止损价\\\",\\n            \\\"止损模式\\\",\\n            \\\"状态\\\"\\n        ],\\n        rows: []\\n    };\\n\\n    let activeCount = 0;\\n    let closedCount = 0;\\n\\n    for (const r of monitorResults) {\\n        const shortSymbol = r.symbol\\n            ? r.symbol.replace(/_USDC\\\\.(Yes|No)$/, \\\"[$1]\\\")\\n            : \\\"-\\\";\\n\\n        let row;\\n\\n        if (r.status === \\\"monitoring\\\") {\\n            activeCount++;\\n\\n            // 盈亏显示(USDC 绝对值)\\n            const pnlDisplay = r.currentPnlAbs >= 0\\n                ? `🟢 +${_N(r.currentPnlAbs, 4)}`\\n                : `🔴 ${_N(r.currentPnlAbs, 4)}`;\\n\\n            const maxDisplay = r.maxPnlAbs >= 0\\n                ? `🔥 +${_N(r.maxPnlAbs, 4)}`\\n                : `${_N(r.maxPnlAbs, 4)}`;\\n\\n            // 回撤显示:距离止损价还剩多少\\n            const distToStop = r.currentPrice - r.stopInfo.effectiveStopPrice;\\n            let ddDisplay;\\n            if (distToStop <= CONFIG.LOSS_AMOUNT * 0.1) {\\n                ddDisplay = `🔴 ${_N(r.drawdownAbs, 4)}`;\\n            } else if (distToStop <= CONFIG.LOSS_AMOUNT * 0.4) {\\n                ddDisplay = `🟡 ${_N(r.drawdownAbs, 4)}`;\\n            } else {\\n                ddDisplay = `🟢 ${_N(r.drawdownAbs, 4)}`;\\n            }\\n\\n            // 止损模式\\n            const stopMode = r.stopInfo.usingFallback\\n                ? `🔐 兜底`\\n                : `🛡️ 移动`;\\n\\n            // 状态\\n            let statusDisplay;\\n            if (distToStop <= CONFIG.LOSS_AMOUNT * 0.1)       statusDisplay = \\\"⚠️ 警戒\\\";\\n            else if (distToStop <= CONFIG.LOSS_AMOUNT * 0.4)  statusDisplay = \\\"🟡 观察\\\";\\n            else                                               statusDisplay = \\\"✅ 持仓中\\\";\\n\\n            row = [\\n                shortSymbol,\\n                _N(r.amount, 4),\\n                `$${_N(r.entryPrice, 4)}`,\\n                `${_N(r.bid, 4)} / ${_N(r.ask, 4)}`,\\n                pnlDisplay,\\n                maxDisplay,\\n                ddDisplay,\\n                `$${_N(r.stopInfo.effectiveStopPrice, 4)}`,\\n                stopMode,\\n                statusDisplay\\n            ];\\n\\n        } else if (r.status === \\\"closed_ts\\\") {\\n            closedCount++;\\n            const tag = r.trigger === \\\"absolute_stop\\\" ? \\\"🔐 绝对底止损\\\" : \\\"🛡️ 移动止损\\\";\\n            row = [\\n                shortSymbol,\\n                _N(r.amount, 4),\\n                `$${_N(r.entryPrice, 4)}`,\\n                `${_N(r.bid, 4)} / ${_N(r.ask, 4)}`,\\n                `${_N(r.currentPnlAbs, 4)}`,\\n                `+${_N(r.maxPnlAbs, 4)}`,\\n                `${_N(r.drawdownAbs, 4)}`,\\n                `$${_N(r.stopInfo.effectiveStopPrice, 4)}`,\\n                \\\"-\\\",\\n                tag\\n            ];\\n\\n        } else if (r.status === \\\"close_failed\\\") {\\n            row = [shortSymbol, \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"❌ 平仓失败\\\"];\\n        } else {\\n            row = [shortSymbol, \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", `⚠️ ${r.status}`];\\n        }\\n\\n        table.rows.push(row);\\n    }\\n\\n    // 汇总行\\n    if (monitorResults.length > 0) {\\n        table.rows.push([\\n            `📊 汇总: ${monitorResults.length} 个`,\\n            `✅ ${activeCount} 监控中`,\\n            `🔻 ${closedCount} 已平仓`,\\n            \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\",\\n            `止损跌幅: ${CONFIG.LOSS_AMOUNT}`,\\n            `兜底: 入场×50%`,\\n            \\\"-\\\"\\n        ]);\\n    } else {\\n        table.rows.push([\\\"📭 当前无持仓\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"等待开仓\\\"]);\\n    }\\n\\n    return table;\\n}\\n\\n// ========== 主执行逻辑 ==========\\nfunction main() {\\n    //Log(\\\"========== PM 移动止损监控 v3 启动 ==========\\\");\\n    //Log(`配置 | 止损跌幅: ${CONFIG.LOSS_AMOUNT} USDC | 绝对兜底: 入场价×50%`);\\n\\n    // Step 1: Redeem 可赎回持仓\\n    const redeemCount = doRedeem();\\n\\n    // Step 2: 获取持仓\\n    let positions = [];\\n    try {\\n        positions = exchange.GetPositions() || [];\\n    } catch (e) {\\n        Log(`❌ GetPositions 失败: ${e.message}`);\\n    }\\n\\n    const activePositions = positions.filter(p => p.Amount && p.Amount > 0);\\n\\n    // Step 3: 逐一监控\\n    const monitorResults = [];\\n    for (const pos of activePositions) {\\n        const result = monitorPMPosition(pos);\\n        monitorResults.push(result);\\n        Sleep(CONFIG.CHECK_INTERVAL);\\n    }\\n\\n    // Step 4: 统计\\n    const closed     = monitorResults.filter(r => r.status === \\\"closed_ts\\\").length;\\n    const failed     = monitorResults.filter(r => r.status === \\\"close_failed\\\").length;\\n    const monitoring = monitorResults.filter(r => r.status === \\\"monitoring\\\").length;\\n\\n    // Step 5: 更新仪表板\\n    const accountTable  = createAccountTable();\\n    const aiTable       = createAIDecisionTable();\\n    const positionTable = createPositionTable(monitorResults);\\n\\n    const redeemNote = redeemCount > 0 ? `\\\\n💰 本轮 Redeem: ${redeemCount} 个持仓` : \\\"\\\";\\n\\n    LogStatus(\\n        '`' + JSON.stringify(accountTable)  + '`\\\\n\\\\n' +\\n        '`' + JSON.stringify(aiTable)        + '`\\\\n\\\\n' +\\n        '`' + JSON.stringify(positionTable)  + '`' +\\n        redeemNote\\n    );\\n\\n    //Log(`📊 本轮结束 | 监控中: ${monitoring} | 止损平仓: ${closed} | 失败: ${failed} | Redeem: ${redeemCount}`);\\n\\n    return {\\n        json: {\\n            processed: true,\\n            monitoring, closed, failed, redeemCount,\\n            timestamp: Date.now(),\\n            results: monitorResults\\n        }\\n    };\\n}\\n\\nreturn main();\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-560,384],\"id\":\"4a53adf5-d087-4260-9537-101ac7b02c08\",\"name\":\"止盈止损\"},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":10}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-784,-48],\"id\":\"e8d496a8-9915-46cd-9296-6e80ea53a9f2\",\"name\":\"交易主线\"},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"seconds\",\"secondsInterval\":30}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-784,384],\"id\":\"74726e8a-a57e-4646-97b0-ead527b34154\",\"name\":\"交易副线\",\"logLevel\":0}],\"pinData\":{},\"connections\":{\"循环处理项目\":{\"main\":[[{\"node\":\"交易执行\",\"type\":\"main\",\"index\":0}],[{\"node\":\"AI 智能体\",\"type\":\"main\",\"index\":0}]]},\"AI 智能体\":{\"main\":[[{\"node\":\"循环处理项目\",\"type\":\"main\",\"index\":0}]]},\"OpenAI 模型\":{\"ai_languageModel\":[[{\"node\":\"AI 智能体\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"筛选标的\":{\"main\":[[{\"node\":\"循环处理项目\",\"type\":\"main\",\"index\":0}]]},\"交易主线\":{\"main\":[[{\"node\":\"筛选标的\",\"type\":\"main\",\"index\":0}]]},\"交易副线\":{\"main\":[[{\"node\":\"止盈止损\",\"type\":\"main\",\"index\":0}]]}},\"active\":false,\"settings\":{\"timezone\":\"Asia/Shanghai\",\"executionOrder\":\"v1\"},\"tags\":[],\"meta\":{\"templateCredsSetupCompleted\":true},\"credentials\":{},\"id\":\"56b087f6-1632-4371-9a1a-684e80c14e4b\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"交易主线\"}}"}