本策略专为 Polymarket 预测市场设计,融合 K 线技术分析、新闻舆论判断与 AI 多角色决策框架,自动识别被市场低估的事件概率标的,执行买入并通过动态移动止损保护利润。
策略主线按以下三个步骤依次运行:
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 依次扮演四个专家角色:
决策矩阵:
| 资金信号 \ 新闻支撑 | 强验证 | 弱验证 | 无验证 |
|---|---|---|---|
| 强(score 4~5) | 买入,confidence=高 | 买入,confidence=中 | 不操作 |
| 中(score 3) | 买入,confidence=中 | 观望 | 不操作 |
| 基准(score 2) | 观望 | 不操作 | 不操作 |
到期日限制规则(硬性优先): - 距今 > 7天:正常评估 - 距今 4~7天:低估幅度超过市场价50%自动降级 - 距今 ≤ 3天 且价格 > 0.15:禁止操作(价格已收敛) - 距今 ≤ 3天 且价格 ≤ 0.15:允许参与,置信度上限为「中」
当 AI 给出买入信号后,从所有买入候选中选出唯一最优标的执行交易,优先级规则如下:
执行时通过 GetDepth 获取实时 Ask 价格,检查账户余额,按配置份额下单(最小下单金额 1 USDC)。订单超时 30 秒后自动撤单。
副线独立运行,负责:
持仓止损监控(移动止损):
自动 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 | 需自行配置 |
视频链接: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\":\"交易主线\"}}"}