币安打新智能AI策略


创建日期: 2026-03-13 14:58:54 最后修改: 2026-03-20 17:27:47
复制: 1 点击次数: 5
avatar of ianzeng123 ianzeng123
2
关注
423
关注者

策略简介

本策略专注于币安永续合约新币上线打新机会。通过自动监听币安官方公告,提前发现即将上线的新合约,在上线前持续采集多交易所数据、新闻舆情与链上基本面,由 AI 大模型进行带历史记忆的多轮渐进分析,在币安合约实际开盘的第一时间自动执行入场,并全程托管止盈止损。

核心优势在于:AI 不会每次独立判断,而是像真实分析师一样积累记忆,随着数据增多持续修正判断,开盘日给出最终精确的入场执行指令。


核心模块

① 公告监听 + 队列维护(每15分钟) 自动拉取币安官方公告,识别”Binance Futures Will Launch … Perpetual Contract”类型的新合约公告,解析上线日期与币种名称,加入追踪队列。队列自动维护状态流转:预追踪 → 开盘日 → 交易中 → 已完成,过期条目自动清理。

② 多维数据采集 每次分析前对队列中的币种进行全量数据采集,涵盖四个维度:

  • CoinMarketCap:CMC 排名、流通比、市值、完全稀释市值、现货价格与涨跌幅
  • Brave 新闻搜索:团队背景、融资情况、锁仓解锁、上线情绪等三类关键词定向搜索
  • 四大交易所合约数据:Bybit、OKX、Gate、HTX 的实时价格、资金费率、持仓量、买卖盘深度
  • 价差与溢价分析:多交易所横向对比,辅助估算币安开盘价区间

③ AI 带记忆的渐进分析(核心) 使用大模型进行分析,每次分析均注入完整历史记录,实现真正的趋势记忆:

  • 上线前每小时分析一次,判断方向趋势与风险
  • 开盘日切换为每15分钟一次高频分析
  • 历史方向一致时置信度随次数递增;出现反转时必须区分”真实信号”还是”短期噪音”
  • 最终输出结构化 JSON:方向、置信度、入场时机、价格区间、杠杆建议、止损止盈位

④ 上线检测 + 入场执行(每1分钟) 快速循环检测追踪队列中的币种是否已在币安开盘(GetMarkets检测),一旦上线立即读取最终 AI 策略并执行入场。支持两种入场模式:

  • 立即市价入场(immediate):开盘后直接以市价建仓
  • 等待回调入场(drawdown_N):开盘后等价格回调 N% 再触发,降低追高风险

入场前自动执行多重安全检查:置信度门槛过滤、高风险拦截、观望状态跳过、重复持仓检测,超过2小时未入场自动放弃。

⑤ 止盈止损全程托管 支持双重退出机制,无需人工值守:

  • 固定止损:亏损达到 AI 建议止损比例自动平仓
  • 固定止盈:浮盈达到 AI 建议止盈比例自动平仓
  • 移动止盈:浮盈超过30%后自动激活,从最高点回撤超过8%触发平仓,保护利润

可配置参数

参数 说明 默认值
initmoney 初始资金(用于收益统计) 自动读取账户余额
positionAmount 每笔开仓金额(USDT) 500
maxLeverage 最大允许杠杆倍数 10
minConfidence 最低入场置信度阈值 60%
cmcApiKey CoinMarketCap API Key 必填
braveKey Brave Search API Key 选填(无则跳过新闻)

仪表板说明

策略运行后实时展示四张可交互表格:

  • 账户概览:运行时长、初始权益、当前权益、总收益曲线
  • 追踪队列:所有监控币种的状态、AI方向、置信度,支持一键清除
  • AI分析摘要:每个币种最新一次 AI 分析的完整结论,含趋势轨迹、入场时机
  • 实时持仓监控:浮盈、最高浮盈、移动止盈状态,支持手动平仓与动态修改止损

策略适用场景

  • 有稳定资金、希望系统性捕捉新合约上线初期高波动行情的用户
  • 无法全天候值守,需要 AI 全程自动分析+执行的量化用户
  • 具备一定合约交易基础的进阶用户

风险提示

  • 新币上线初期流动性极差,价格波动可达数十至数百个百分点,风险极高
  • AI 判断基于历史数据推演,无法预测黑天鹅事件,不构成投资建议
  • 建议单笔开仓金额控制在总资金的 10%~20% 以内
  • 强烈建议先在模拟盘运行至少一个完整新币上线周期,熟悉策略行为后再切换实盘
  • 杠杆会成倍放大损失,请根据自身风险承受能力谨慎设置

视频链接:从平台公告到标的上线打新,AI替你跑全程

策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":15}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-16,-16],\"id\":\"9473656e-d40d-435c-9ee6-141b6bcbe12b\",\"name\":\"慢触发器(15min)\"},{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":1}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-32,416],\"id\":\"9986289c-da1f-443a-bb44-89aa1f66c774\",\"name\":\"快触发器(1min)\",\"logLevel\":0},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 初始化全局状态\\n * FMZ参数:\\n * initmoney: 初始资金\\n * positionAmount: 单币种最大持仓金额(U) 默认500\\n * cmcApiKey: CoinMarketCap API Key\\n * braveKey: Brave Search API Key\\n * maxLeverage: 最大杠杆倍数 默认10\\n * minConfidence: 最低入场置信度 默认60\\n */\\n\\nif (_G('nl_initialized') === null) {\\n  _G('nl_initialized', true);\\n  _G('nl_trackingList', JSON.stringify([]));\\n  _G('nl_STARTTIME', Date.now());\\n  const initAccount = exchange.GetAccount();\\n  _G('nl_initmoney', $vars.initmoney || initAccount.Balance);\\n  _G('nl_invoketime', 0);\\n  Log('初始化完成,初始资金:', _G('nl_initmoney'));\\n}\\n\\nconst invoketime = (_G('nl_invoketime') || 0) + 1;\\n_G('nl_invoketime', invoketime);\\n\\nconst account = exchange.GetAccount();\\nconst initMoney = _G('nl_initmoney');\\nconst equity = account.Equity;\\nconst totalReturn = ((equity - initMoney) / initMoney * 100).toFixed(2);\\nconst trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\n\\nLogProfit(equity - initMoney, '&');\\n\\nLog('策略状态 | 运行次数:', invoketime,\\n  '| 权益:', equity.toFixed(2),\\n  '| 收益率:', totalReturn + '%',\\n  '| 追踪币种:', trackingList.length);\\n\\nreturn [{\\n  json: {\\n    invoketime,\\n    equity: equity.toFixed(2),\\n    availableCash: account.Balance.toFixed(2),\\n    initMoney,\\n    totalReturn: totalReturn + '%',\\n    trackingList\\n  }\\n}];\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[144,-16],\"id\":\"54fc3357-5e1f-40d5-a637-746552fa3f30\",\"name\":\"初始化状态\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 检测币安公告,发现新上线币种,维护追踪队列\\n */\\n\\nconst input = $input.first().json;\\n\\n// 1. 拉取币安新上线合约公告\\nlet catalogs = [];\\ntry {\\n  const raw = HttpQuery(\\n    'https://www.binance.com/bapi/composite/v1/public/cms/article/list/query?type=1&pageNo=1&pageSize=10',\\n    { method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0', 'clienttype': 'web' } }\\n  );\\n  const data = JSON.parse(raw);\\n  catalogs = data.data.catalogs || [];\\n} catch(e) {\\n  Log('拉取币安公告失败:', e.message);\\n}\\n\\n// 2. 读取当前追踪队列\\nlet trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\nconst existingSymbols = trackingList.map(t => t.symbol);\\n\\n// 3. 解析公告,找新币\\nfor (const catalog of catalogs) {\\n  if (catalog.catalogId !== 48) continue;\\n  const articles = catalog.articles || [];\\n\\n  for (const a of articles) {\\n    const title = a.title || '';\\n    if (title.indexOf('Binance Futures Will Launch') === -1) continue;\\n    if (title.indexOf('Perpetual Contract') === -1) continue;\\n\\n    const dateMatch = title.match(/(\\\\d{4}-\\\\d{2}-\\\\d{2})/);\\n    if (!dateMatch) continue;\\n    const launchDate = dateMatch[1];\\n\\n    // 过滤已过期(超过3天前上线的跳过)\\n    const launchTime = new Date(launchDate).getTime();\\n    if (Date.now() - launchTime > 3 * 86400000) continue;\\n\\n    const symbolMatch = title.match(/([\\\\w]+USDT)(?=\\\\s+(?:USDⓈ|Perpetual))/);\\n    if (!symbolMatch) continue;\\n    const symbol = symbolMatch[1];\\n\\n    if (existingSymbols.indexOf(symbol) !== -1) continue;\\n\\n    const now = new Date();\\n    const launch = new Date(launchDate);\\n    const hoursToLaunch = Math.round((launch - now) / 3600000);\\n    const isLaunchDay = now.toDateString() === launch.toDateString();\\n\\n    trackingList.push({\\n      symbol,\\n      launchDate,\\n      status: isLaunchDay ? 'LAUNCH_DAY' : 'PRE_LISTING',\\n      discoveredAt: now.toISOString(),\\n      hoursToLaunch: isLaunchDay ? '今日发行' : hoursToLaunch,\\n      analysisCount: 0,\\n      lastAnalyzedAt: null\\n    });\\n    existingSymbols.push(symbol);\\n    Log('发现新币:', symbol, '| 预计上线:', launchDate, '| 距今:', isLaunchDay ? '今日发行' : hoursToLaunch + '小时');\\n  }\\n  break;\\n}\\n\\n// 4. 更新各币种状态,清理过期项\\nconst now = new Date();\\ntrackingList = trackingList\\n  .map(item => {\\n    if (item.status === 'TRADING' || item.status === 'TRADING_WAIT' || item.status === 'DONE') return item;\\n    const launch = new Date(item.launchDate);\\n    const hoursToLaunch = Math.round((launch - now) / 3600000);\\n    const isLaunchDay = now.toDateString() === launch.toDateString();\\n    return {\\n      ...item,\\n      hoursToLaunch: isLaunchDay ? '今日发行' : hoursToLaunch,\\n      status: isLaunchDay ? 'LAUNCH_DAY' : (hoursToLaunch > 0 ? 'PRE_LISTING' : item.status)\\n    };\\n  })\\n  .filter(item => {\\n    if (item.status === 'DONE') return false;\\n    const launch = new Date(item.launchDate);\\n    return (Date.now() - launch.getTime()) < 3 * 86400000;\\n  });\\n\\n_G('nl_trackingList', JSON.stringify(trackingList));\\n\\n// 5. 筛选需要本次分析的币种\\nconst toAnalyze = trackingList.filter(item => {\\n  if (item.status !== 'PRE_LISTING' && item.status !== 'LAUNCH_DAY') return false;\\n  if (item.status === 'LAUNCH_DAY') return true;\\n  if (!item.lastAnalyzedAt) return true;\\n  const msSince = Date.now() - new Date(item.lastAnalyzedAt).getTime();\\n  return msSince >= 55 * 60 * 1000;\\n});\\n\\nif (toAnalyze.length === 0) {\\n  Log('本次无需分析(未到采集间隔)');\\n  return [];\\n}\\n\\nLog('本次需要分析', toAnalyze.length, '个币种:',\\n  toAnalyze.map(t => t.symbol + '(' + t.status + ')').join(', '));\\n\\nreturn toAnalyze.map(item => ({ json: item }));\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[320,-16],\"id\":\"c9b49012-e01b-4597-9fba-4990aa021ff2\",\"name\":\"检测公告+维护队列\"},{\"parameters\":{\"splitInBatchesNotice\":\"\",\"batchSize\":1,\"options\":{}},\"type\":\"n8n-nodes-base.splitInBatches\",\"typeVersion\":3,\"position\":[496,-16],\"id\":\"807b74b5-a1b6-4106-864d-889bbca2072b\",\"name\":\"逐个处理币种\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 采集当前币种的所有数据:CMC + Brave + 四个交易所\\n */\\n\\nconst item = $input.first().json;\\nLog(item)\\nconst symbol = item.symbol;\\nconst coin = symbol.replace(/USDT$/i, '');\\nLog(coin)\\n\\nLog('========== 采集:', symbol, '阶段:', item.status, '第', (item.analysisCount + 1), '次 ==========');\\n\\n// ==================== CMC ====================\\nfunction fetchCMC(coin) {\\n  const key = $vars.cmcApiKey || '';\\n  if (!key) { Log('未配置 cmcApiKey'); return null; }\\n  try {\\n    const raw = HttpQuery(\\n      'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=' + coin + '&convert=USD',\\n      { method: 'GET', headers: { 'X-CMC_PRO_API_KEY': key, 'Accept': 'application/json' } }\\n    );\\n    const data = JSON.parse(raw);\\n    const c = data.data && data.data[coin];\\n    if (!c) return null;\\n    const q = c.quote.USD;\\n    const supply = c.max_supply || c.total_supply;\\n    return {\\n      name: c.name, rank: c.cmc_rank,\\n      circulatingSupply: c.circulating_supply,\\n      circulationRatio: supply\\n        ? parseFloat((c.circulating_supply / supply * 100).toFixed(2))\\n        : null,\\n      spotPrice: q.price,\\n      change1h: q.percent_change_1h,\\n      change24h: q.percent_change_24h,\\n      change7d: q.percent_change_7d,\\n      volume24h: q.volume_24h,\\n      marketCap: q.market_cap,\\n      fullyDilutedMarketCap: q.fully_diluted_market_cap\\n    };\\n  } catch(e) { Log('CMC 失败:', e.message); return null; }\\n}\\n\\n// ==================== Brave ====================\\nfunction fetchBraveNews(coin) {\\n  const key = $vars.braveKey || '';\\n  if (!key) { Log('未配置 braveKey'); return []; }\\n  const queries = [\\n    '\\\"' + coin + '\\\" token funding team',\\n    '\\\"' + coin + '\\\" vesting unlock schedule',\\n    '\\\"' + coin + '\\\" binance futures listing'\\n  ];\\n  const allNews = [];\\n  const seen = {};\\n  for (const q of queries) {\\n    try {\\n      const raw = HttpQuery(\\n        'https://api.search.brave.com/res/v1/news/search?q=' + encodeURIComponent(q) + '&count=5&freshness=pw',\\n        { method: 'GET', headers: { 'X-Subscription-Token': key, 'Accept': 'application/json' } }\\n      );\\n      const data = JSON.parse(raw);\\n      if (data.results) {\\n        for (const nr of data.results) {\\n          const hit = (nr.title || '').toUpperCase().indexOf(coin.toUpperCase()) !== -1 ||\\n                      (nr.description || '').toUpperCase().indexOf(coin.toUpperCase()) !== -1;\\n          if (!hit) continue;\\n          const k = nr.url || nr.title;\\n          if (!seen[k]) {\\n            seen[k] = true;\\n            allNews.push({ title: nr.title || '', desc: nr.description || '', url: nr.url || '', age: nr.age || '' });\\n          }\\n        }\\n      }\\n    } catch(e) { Log('Brave失败:', e.message); }\\n    Sleep(1200);\\n  }\\n  return allNews;\\n}\\n\\n// ==================== Bybit ====================\\nfunction fetchBybit(coin) {\\n  try {\\n    const raw = HttpQuery('https://api.bybit.com/v5/market/tickers?category=linear&symbol=' + coin + 'USDT',\\n      { method: 'GET', headers: { 'Accept': 'application/json' } });\\n    const parsed = JSON.parse(raw);\\n    const t = parsed.result && parsed.result.list && parsed.result.list[0];\\n    if (!t) return null;\\n    let lastFundingRate = null;\\n    try {\\n      const frRaw = HttpQuery(\\n        'https://api.bybit.com/v5/market/funding/history?category=linear&symbol=' + coin + 'USDT&limit=1',\\n        { method: 'GET', headers: { 'Accept': 'application/json' } });\\n      const frParsed = JSON.parse(frRaw);\\n      const frData = frParsed.result && frParsed.result.list && frParsed.result.list[0];\\n      lastFundingRate = frData ? parseFloat(frData.fundingRate) : null;\\n    } catch(e) {}\\n    return {\\n      price:             parseFloat(t.lastPrice),\\n      markPrice:         parseFloat(t.markPrice),\\n      indexPrice:        parseFloat(t.indexPrice),\\n      bid1:              parseFloat(t.bid1Price),\\n      bid1Size:          parseFloat(t.bid1Size),\\n      ask1:              parseFloat(t.ask1Price),\\n      ask1Size:          parseFloat(t.ask1Size),\\n      spread:            parseFloat((parseFloat(t.ask1Price) - parseFloat(t.bid1Price)).toFixed(8)),\\n      change24h:         parseFloat(t.price24hPcnt) * 100,\\n      high24h:           parseFloat(t.highPrice24h),\\n      low24h:            parseFloat(t.lowPrice24h),\\n      prevPrice24h:      parseFloat(t.prevPrice24h),\\n      volume24h:         parseFloat(t.volume24h),\\n      turnover24h:       parseFloat(t.turnover24h),\\n      openInterest:      parseFloat(t.openInterest),\\n      openInterestValue: parseFloat(t.openInterestValue),\\n      fundingRate:       parseFloat(t.fundingRate),\\n      nextFundingTime:   t.nextFundingTime,\\n      lastFundingRate\\n    };\\n  } catch(e) { Log('Bybit失败:', e.message); return null; }\\n}\\n\\n// ==================== OKX ====================\\nfunction fetchOKX(coin) {\\n  try {\\n    const instId = coin + '-USDT-SWAP';\\n    const tkRaw = HttpQuery('https://www.okx.com/api/v5/market/ticker?instId=' + instId,\\n      { method: 'GET', headers: { 'Accept': 'application/json' } });\\n    const tk = JSON.parse(tkRaw).data && JSON.parse(tkRaw).data[0];\\n    if (!tk) return null;\\n    Sleep(150);\\n    let fundingRate = null, nextFundingRate = null, fundingTime = null, nextFundingTime = null;\\n    try {\\n      const frRaw = HttpQuery('https://www.okx.com/api/v5/public/funding-rate?instId=' + instId,\\n        { method: 'GET', headers: { 'Accept': 'application/json' } });\\n      const fr = JSON.parse(frRaw).data && JSON.parse(frRaw).data[0];\\n      if (fr) { fundingRate = parseFloat(fr.fundingRate); nextFundingRate = parseFloat(fr.nextFundingRate); fundingTime = fr.fundingTime; nextFundingTime = fr.nextFundingTime; }\\n    } catch(e) {}\\n    return {\\n      price: parseFloat(tk.last),\\n      bid1: parseFloat(tk.bidPx), bid1Size: parseFloat(tk.bidSz),\\n      ask1: parseFloat(tk.askPx), ask1Size: parseFloat(tk.askSz),\\n      spread: parseFloat((parseFloat(tk.askPx) - parseFloat(tk.bidPx)).toFixed(8)),\\n      change24h: parseFloat(tk.changeUtc0),\\n      high24h: parseFloat(tk.high24h), low24h: parseFloat(tk.low24h), open24h: parseFloat(tk.open24h),\\n      volume24h: parseFloat(tk.vol24h), volCcy24h: parseFloat(tk.volCcy24h),\\n      openInterest: parseFloat(tk.openInterest || 0),\\n      fundingRate, nextFundingRate, fundingTime, nextFundingTime\\n    };\\n  } catch(e) { Log('OKX失败:', e.message); return null; }\\n}\\n\\n// ==================== Gate ====================\\nfunction fetchGate(coin) {\\n  const result = {};\\n  try {\\n    const sd = JSON.parse(HttpQuery('https://api.gateio.ws/api/v4/spot/tickers?currency_pair=' + coin + '_USDT',\\n      { method: 'GET', headers: { 'Accept': 'application/json' } }));\\n    const s = sd && sd[0];\\n    if (s) result.spot = {\\n      price: parseFloat(s.last), bid1: parseFloat(s.highest_bid), ask1: parseFloat(s.lowest_ask),\\n      spread: parseFloat((parseFloat(s.lowest_ask) - parseFloat(s.highest_bid)).toFixed(8)),\\n      change24h: parseFloat(s.change_percentage),\\n      high24h: parseFloat(s.high_24h), low24h: parseFloat(s.low_24h),\\n      volume24h: parseFloat(s.base_volume), turnover24h: parseFloat(s.quote_volume)\\n    };\\n  } catch(e) {}\\n  Sleep(200);\\n  try {\\n    const fc = JSON.parse(HttpQuery('https://api.gateio.ws/api/v4/futures/usdt/contracts/' + coin + '_USDT',\\n      { method: 'GET', headers: { 'Accept': 'application/json' } }));\\n    if (fc && fc.name) result.futures = {\\n      markPrice: parseFloat(fc.mark_price), indexPrice: parseFloat(fc.index_price),\\n      lastPrice: parseFloat(fc.last_price), fundingRate: parseFloat(fc.funding_rate),\\n      fundingInterval: fc.funding_interval,\\n      openInterest: parseFloat(fc.open_interest),\\n      change24h: parseFloat(fc.change_percentage),\\n      high24h: parseFloat(fc.high_24h), low24h: parseFloat(fc.low_24h),\\n      volume24h: parseFloat(fc.volume_24h), turnover24h: parseFloat(fc.volume_24h_quote),\\n      leverageMax: fc.leverage_max\\n    };\\n  } catch(e) {}\\n  return (result.spot || result.futures) ? result : null;\\n}\\n\\n// ==================== HTX ====================\\nfunction fetchHTX(coin) {\\n  const result = {};\\n  const sym = coin.toLowerCase();\\n  try {\\n    const sd = JSON.parse(HttpQuery('https://api.huobi.pro/market/detail/merged?symbol=' + sym + 'usdt',\\n      { method: 'GET', headers: { 'Accept': 'application/json' } }));\\n    if (sd.status === 'ok' && sd.tick) {\\n      const tk = sd.tick;\\n      result.spot = {\\n        price: parseFloat(tk.close),\\n        bid1: parseFloat(tk.bid[0]), bid1Size: parseFloat(tk.bid[1]),\\n        ask1: parseFloat(tk.ask[0]), ask1Size: parseFloat(tk.ask[1]),\\n        spread: parseFloat((tk.ask[0] - tk.bid[0]).toFixed(8)),\\n        open24h: parseFloat(tk.open), high24h: parseFloat(tk.high), low24h: parseFloat(tk.low),\\n        change24h: parseFloat(((tk.close - tk.open) / tk.open * 100).toFixed(2)),\\n        volume24h: parseFloat(tk.amount), turnover24h: parseFloat(tk.vol)\\n      };\\n    }\\n  } catch(e) {}\\n  Sleep(200);\\n  try {\\n    const fd = JSON.parse(HttpQuery('https://api.hbdm.com/linear-swap-ex/market/detail/merged?contract_code=' + coin + '-USDT',\\n      { method: 'GET', headers: { 'Accept': 'application/json' } }));\\n    if (fd.status === 'ok' && fd.tick) {\\n      const ft = fd.tick;\\n      let fundingRate = null, nextFundingRate = null, markPrice = null, indexPrice = null, fundingTime = null;\\n      try {\\n        const frRaw = JSON.parse(HttpQuery('https://api.hbdm.com/linear-swap-api/v1/swap_funding_rate?contract_code=' + coin + '-USDT',\\n          { method: 'GET', headers: { 'Accept': 'application/json' } }));\\n        const frData = frRaw.data && frRaw.data[0];\\n        if (frData) {\\n          fundingRate = parseFloat(frData.funding_rate);\\n          nextFundingRate = parseFloat(frData.estimated_rate);\\n          markPrice = parseFloat(frData.mark_price);\\n          indexPrice = parseFloat(frData.index_price);\\n          fundingTime = frData.funding_time;\\n        }\\n      } catch(e) {}\\n      result.futures = {\\n        price: parseFloat(ft.close), markPrice, indexPrice,\\n        bid1: parseFloat(ft.bid[0]), bid1Size: parseFloat(ft.bid[1]),\\n        ask1: parseFloat(ft.ask[0]), ask1Size: parseFloat(ft.ask[1]),\\n        spread: parseFloat((ft.ask[0] - ft.bid[0]).toFixed(8)),\\n        open24h: parseFloat(ft.open), high24h: parseFloat(ft.high), low24h: parseFloat(ft.low),\\n        change24h: parseFloat(((ft.close - ft.open) / ft.open * 100).toFixed(2)),\\n        volume24h: parseFloat(ft.amount), turnover24h: parseFloat(ft.vol),\\n        fundingRate, nextFundingRate, fundingTime\\n      };\\n    }\\n  } catch(e) {}\\n  return (result.spot || result.futures) ? result : null;\\n}\\n\\n// ==================== 执行采集 ====================\\nconst cmc   = fetchCMC(coin);    Sleep(300);\\nconst news  = fetchBraveNews(coin); Sleep(300);\\nconst bybit = fetchBybit(coin);  Sleep(200);\\nconst okx   = fetchOKX(coin);   Sleep(200);\\nconst gate  = fetchGate(coin);  Sleep(200);\\nconst htx   = fetchHTX(coin);\\n\\nconst mulexchanges = { bybit, okx, gate, htx };\\n\\nLog('采集完成 | CMC:', cmc ? 'OK' : '无',\\n  '| 新闻:', news.length + '条',\\n  '| Bybit:', bybit ? bybit.price : '未上线',\\n  '| OKX:', okx ? okx.price : '未上线',\\n  '| Gate:', gate ? (gate.spot ? gate.spot.price : gate.futures ? gate.futures.lastPrice : '无') : '未上线',\\n  '| HTX:', htx ? (htx.spot ? htx.spot.price : htx.futures ? htx.futures.price : '无') : '未上线'\\n);\\n\\nreturn [{\\n  json: {\\n    ...item,\\n    currentData: { collectedAt: new Date().toISOString(), cmc, news, mulexchanges }\\n  }\\n}];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[688,-144],\"id\":\"986ddac8-0c78-4db5-86dc-aea8a00a0641\",\"name\":\"采集数据\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 读取历史分析记录,构建带历史记忆的 AI Prompt\\n */\\n\\nconst item = $input.first().json;\\nconst symbol = item.symbol;\\nconst coin = symbol.replace(/USDT$/i, '');\\nconst currentData = item.currentData;\\nconst isLaunchDay = item.status === 'LAUNCH_DAY';\\n\\nconst historyKey = 'nl_history_' + symbol;\\nconst history = JSON.parse(_G(historyKey) || '[]');\\nconst orderNum = history.length + 1;\\n\\n// ===== 构建历史摘要 =====\\nfunction buildHistorySection(history) {\\n  if (history.length === 0) return '(这是对该币种的第一次分析,暂无历史记录)';\\n  return history.map(h => {\\n    const ex = (h.currentData && h.currentData.mulexchanges) || {};\\n    const bybit = ex.bybit;\\n    const okx   = ex.okx;\\n    const ai    = h.aiConclusion;\\n    const lines = [\\n      '### 第' + h.order + '次分析',\\n      '- 时间: ' + h.timestamp + ' | 距上线' + h.hoursToLaunch + ' | 阶段:' + h.phase + ' | 频率:' + h.interval,\\n      '- Bybit: 价格=' + (bybit ? bybit.price : '未上线') + ' / 资金费率=' + (bybit ? bybit.fundingRate : '-') + ' / 持仓量=' + (bybit ? bybit.openInterestValue + 'U' : '-'),\\n      '- OKX: 价格=' + (okx ? okx.price : '未上线') + ' / 资金费率=' + (okx ? okx.fundingRate : '-'),\\n      '- CMC: 排名=' + ((h.currentData && h.currentData.cmc && h.currentData.cmc.rank) || '-') + ' / 流通比=' + ((h.currentData && h.currentData.cmc && h.currentData.cmc.circulationRatio !== null ? h.currentData.cmc.circulationRatio + '%' : '数据缺失')),\\n      '- 结论: ' + ai.direction + ', 置信度' + ai.confidence + '%, 风险' + ai.riskLevel,\\n      '- 趋势: ' + (ai.trendConsistency || '初始') + (ai.reversalType && ai.reversalType !== 'null' ? '(' + ai.reversalType + ')' : ''),\\n      '- 入场时机: ' + (ai.entryTiming || '-'),\\n      '- 关键变化: ' + (ai.keyChanges || '-'),\\n      '- 综合判断: ' + ai.summary\\n    ];\\n    return lines.join('\\\\n');\\n  }).join('\\\\n\\\\n');\\n}\\n\\n// ===== 格式化交易所数据 =====\\nfunction fmtEx(name, data) {\\n  if (!data) return '### ' + name + '\\\\n未上线/无数据';\\n  return [\\n    '### ' + name,\\n    '- 价格/标记/指数: ' + (data.price || data.lastPrice || '-') + ' / ' + (data.markPrice || '-') + ' / ' + (data.indexPrice || '-'),\\n    '- 买一/卖一/价差: ' + (data.bid1 || '-') + ' / ' + (data.ask1 || '-') + ' / ' + (data.spread || '-'),\\n    '- 24h涨跌: ' + (data.change24h !== undefined ? data.change24h + '%' : '-') + ' | 最高: ' + (data.high24h || '-') + ' | 最低: ' + (data.low24h || '-'),\\n    '- 24h成交额: ' + (data.turnover24h || data.volCcy24h || '-') + 'U | 持仓量: ' + (data.openInterest || data.openInterestValue || '-') + 'U',\\n    '- 资金费率(当期/预测): ' + (data.fundingRate !== undefined ? data.fundingRate : '-') + ' / ' + (data.nextFundingRate !== undefined ? data.nextFundingRate : '-')\\n  ].join('\\\\n');\\n}\\n\\nconst ex  = currentData.mulexchanges;\\nconst cmc = currentData.cmc;\\n\\nconst gateStr = ex.gate ? [\\n  '### Gate',\\n  ex.gate.spot    ? '现货: 价格=' + ex.gate.spot.price + ' / 24h涨跌=' + ex.gate.spot.change24h + '%' : '现货: 未上线',\\n  ex.gate.futures ? '合约: 价格=' + ex.gate.futures.lastPrice + ' / 资金费率=' + ex.gate.futures.fundingRate + ' / 持仓量=' + ex.gate.futures.openInterest : '合约: 未上线'\\n].join('\\\\n') : '### Gate\\\\n未上线/无数据';\\n\\nconst htxStr = ex.htx ? [\\n  '### HTX',\\n  ex.htx.spot    ? '现货: 价格=' + ex.htx.spot.price + ' / 24h涨跌=' + ex.htx.spot.change24h + '%' : '现货: 未上线',\\n  ex.htx.futures ? '合约: 价格=' + ex.htx.futures.price + ' / 资金费率=' + ex.htx.futures.fundingRate : '合约: 未上线'\\n].join('\\\\n') : '### HTX\\\\n未上线/无数据';\\n\\nconst newsStr = currentData.news.slice(0, 6).map((n, i) =>\\n  (i + 1) + '. [' + (n.age || '') + '] ' + n.title + '\\\\n   ' + n.desc\\n).join('\\\\n');\\n\\nconst cmcStr = cmc ? [\\n  '- 名称: ' + cmc.name + ' / CMC排名: ' + cmc.rank,\\n  '- 现货价: ' + cmc.spotPrice + ' USD',\\n  '- 涨跌: 1h=' + cmc.change1h + '% / 24h=' + cmc.change24h + '% / 7d=' + cmc.change7d + '%',\\n  '- 流通比: ' + (cmc.circulationRatio !== null ? cmc.circulationRatio + '% (仅供参考)' : '数据缺失,忽略此项'),\\n  '- 市值: ' + cmc.marketCap + ' USD / 完全稀释市值: ' + cmc.fullyDilutedMarketCap + ' USD',\\n  '- 24h成交量: ' + cmc.volume24h + ' USD'\\n].join('\\\\n') : '无CMC数据';\\n\\nconst launchDayNote = isLaunchDay\\n  ? '[今天是上线日,这次分析将作为最终入场依据,请给出精确的入场价格区间和明确的止盈止损位。]'\\n  : '[当前为上线前预判阶段,重点识别方向趋势和风险因素,价格区间可适当宽泛。]';\\n\\nconst promptLines = [\\n  '你是负责跟踪 ' + coin + ' 币安永续合约打新的专职量化分析师。',\\n  '这是你对该币种的第 ' + orderNum + ' 次分析。',\\n  '',\\n  '## 历史分析记录',\\n  buildHistorySection(history),\\n  '',\\n  '---',\\n  '',\\n  '## 当前数据(第' + orderNum + '次 | ' + (isLaunchDay ? '今日发行' : '距上线约' + item.hoursToLaunch + '小时') + ' | 阶段:' + item.status + ' | 频率:' + (isLaunchDay ? '每15分钟' : '每小时') + ')',\\n  '',\\n  '### CMC基本面',\\n  cmcStr,\\n  '',\\n  fmtEx('Bybit合约', ex.bybit),\\n  '',\\n  fmtEx('OKX合约', ex.okx),\\n  '',\\n  gateStr,\\n  '',\\n  htxStr,\\n  '',\\n  '### 近期新闻(共' + currentData.news.length + '条)',\\n  newsStr || '无相关新闻',\\n  '',\\n  '---',\\n  '',\\n  '## 分析指引',\\n  '',\\n  '历史判断原则:',\\n  '1. 历史方向一致 → 置信度应随次数递增,标记趋势为强化或维持',\\n  '2. 出现方向反转 → 必须明确说明是[真实信号](有新的基本面/数据支撑)还是[短期噪音](开盘前情绪波动)',\\n  '3. 若历史判断摇摆不定,置信度应保守,不可盲目给高分',\\n  '4. keyChanges字段必须与上次数据做具体对比',\\n  '',\\n  '数据缺失处理原则:',\\n  '1. 某交易所无数据属正常现象,不作为负面信号',\\n  '2. 流通比、持仓量等字段缺失时忽略该项,不纳入风险判断',\\n  '3. 风险点只列出有实际数据支撑的问题,不列推测性风险',\\n  '',\\n  '价格基准:',\\n  '- 若多交易所均有合约价格,以Bybit标记价为基准估算币安开盘价区间',\\n  '- 若仅有现货,以现货价为参考,开盘价通常有5~20%溢价',\\n  '- 若所有交易所均无数据,基于CMC价格估算,区间要宽泛',\\n  '',\\n  '入场时机说明:',\\n  '- immediate = 开盘立即市价入场',\\n  '- drawdown_N = 等开盘后回调N%再入场(N为数字,如drawdown_5代表等回调5%)',\\n  '- 做多时回调 = 价格下跌N%触发;做空时回调 = 价格上涨N%触发',\\n  '- 新币开盘波动极大,若判断趋势明确建议immediate,若开盘溢价过高可选drawdown',\\n  '',\\n  launchDayNote,\\n  '',\\n  '请严格输出以下JSON,不输出任何其他内容:',\\n  '{',\\n  '  \\\"order\\\": ' + orderNum + ',',\\n  '  \\\"direction\\\": \\\"做多|做空|观望\\\",',\\n  '  \\\"confidence\\\": 0到100的整数,',\\n  '  \\\"trendConsistency\\\": \\\"初始|强化|维持|弱化|反转\\\",',\\n  '  \\\"reversalType\\\": \\\"真实信号|短期噪音|null\\\",',\\n  '  \\\"entryTiming\\\": \\\"immediate|drawdown_N\\\",',\\n  '  \\\"priceRange\\\": { \\\"low\\\": 数字, \\\"high\\\": 数字 },',\\n  '  \\\"leverage\\\": 建议杠杆1到20的整数,',\\n  '  \\\"stopLoss\\\": 止损百分比的数字,',\\n  '  \\\"takeProfit\\\": 止盈百分比的数字,',\\n  '  \\\"riskLevel\\\": \\\"高|中|低\\\",',\\n  '  \\\"riskPoints\\\": [\\\"风险点1\\\", \\\"风险点2\\\"],',\\n  '  \\\"keyChanges\\\": \\\"与上次分析相比最重要的数据变化\\\",',\\n  '  \\\"summary\\\": \\\"100字以内综合判断,需引用历史趋势和当前最关键信号\\\"',\\n  '}'\\n];\\n\\nconst prompt = promptLines.join('\\\\n');\\n\\nreturn [{\\n  json: {\\n    ...item,\\n    currentData,\\n    history,\\n    orderNum,\\n    prompt\\n  }\\n}];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[864,-144],\"id\":\"83802ad8-d19d-42ff-85ac-e2786d85285a\",\"name\":\"读历史+构建Prompt\"},{\"parameters\":{\"text\":\"={{ $json.prompt }}\",\"options\":{\"systemMessage\":\"你是一名专业的加密货币衍生品量化交易员,专注于新币上线打新策略分析。\\n\\n核心原则:\\n1. 始终输出纯JSON,不包含任何解释文字、markdown格式或代码块标记\\n2. 利用历史分析记录建立趋势记忆,不允许每次独立判断\\n3. 方向反转时必须提供明确的基本面或数据支撑理由\\n4. 高风险情形(资金费率异常、无流动性、明确的基本面风险)需在riskPoints列出;流通比等数据缺失时忽略该项,不作为高风险依据\\n5. 置信度要真实反映信息质量,信息不足时不超过60\\n6. 开盘当天输出必须包含可执行的精确价格区间和明确的止盈止损位\\n\\n严格禁止:\\n- 输出JSON以外的任何文字\\n- 使用```json```等代码块包裹\\n- 在信息不足时仍给出高置信度判断\"}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[1024,-144],\"id\":\"5e2f6157-7fb8-4b2d-88de-e9d00a7cda39\",\"name\":\"AI分析智能体\",\"retryOnFail\":true},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"x-ai/grok-4.1-fast\",\"mode\":\"list\",\"cachedResultName\":\"x-ai/grok-4.1-fast\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[1024,32],\"id\":\"26a9693b-88a7-46ba-906c-949ae333498f\",\"name\":\"OpenAI 模型\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 解析AI返回结果,保存历史记录,更新最终策略\\n */\\n\\nconst item = $node[\\\"读历史+构建Prompt\\\"].json;  // 取原始数据\\nconst symbol = item.symbol;\\nconst aiOutput = $input.first().json.output || $input.first().json.text || '';\\n\\nLog(item)\\nLog(symbol)\\nLog(aiOutput)\\n\\nfunction parseAIResult(output) {\\n  try {\\n    let cleaned = output.replace(/```[a-z]*\\\\n?/gi, '').trim();\\n    const match = cleaned.match(/\\\\{[\\\\s\\\\S]*\\\\}/);\\n    if (!match) throw new Error('未找到JSON内容');\\n    return JSON.parse(match[0]);\\n  } catch(e) {\\n    try {\\n      let s = output.replace(/```[a-z]*\\\\n?/gi, '').trim();\\n      const open  = (s.match(/\\\\{/g) || []).length;\\n      const close = (s.match(/\\\\}/g) || []).length;\\n      for (let i = 0; i < open - close; i++) s += '}';\\n      const m = s.match(/\\\\{[\\\\s\\\\S]*\\\\}/);\\n      if (m) return JSON.parse(m[0]);\\n    } catch(e2) {}\\n    Log('AI JSON解析失败:', e.message, '原始输出前300字:', aiOutput.substring(0, 300));\\n    return null;\\n  }\\n}\\n\\nconst aiResult = parseAIResult(aiOutput);\\n\\nif (!aiResult) {\\n  Log(symbol, 'AI分析失败,跳过保存');\\n  return [{ json: { ...item, aiResult: null, saved: false } }];\\n}\\n\\nconst record = {\\n  order:         item.orderNum,\\n  timestamp:     new Date().toISOString(),\\n  phase:         item.status,\\n  interval:      item.status === 'LAUNCH_DAY' ? '每15分钟' : '每小时',\\n  hoursToLaunch: item.hoursToLaunch,\\n  currentData:   item.currentData,\\n  aiConclusion:  aiResult\\n};\\n\\nconst historyKey = 'nl_history_' + symbol;\\nconst history = JSON.parse(_G(historyKey) || '[]');\\nhistory.push(record);\\n_G(historyKey, JSON.stringify(history));\\n\\n_G('nl_strategy_' + symbol, JSON.stringify({\\n  symbol,\\n  updatedAt:     new Date().toISOString(),\\n  phase:         item.status,\\n  hoursToLaunch: item.hoursToLaunch,\\n  historyCount:  history.length,\\n  aiResult\\n}));\\n\\nlet trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\ntrackingList = trackingList.map(t =>\\n  t.symbol === symbol\\n    ? { ...t, analysisCount: history.length, lastAnalyzedAt: new Date().toISOString() }\\n    : t\\n);\\n_G('nl_trackingList', JSON.stringify(trackingList));\\n\\nLog(symbol, '第' + record.order + '次分析保存完成',\\n  '| 方向:', aiResult.direction,\\n  '| 置信度:', aiResult.confidence + '%',\\n  '| 趋势:', aiResult.trendConsistency,\\n  '| 风险:', aiResult.riskLevel\\n);\\nLog(symbol, '判断:', aiResult.summary);\\nif (aiResult.riskPoints && aiResult.riskPoints.length > 0) {\\n  Log(symbol, '风险点:', aiResult.riskPoints.join(' | '));\\n}\\n\\nreturn [{ json: { ...item, aiResult, historyCount: history.length, saved: true } }];\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[1360,0],\"id\":\"0a2e51dc-30b1-4bb7-8484-164f9e5f61f1\",\"name\":\"保存历史+更新策略\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 快触发器主逻辑(每1分钟):\\n * 1. 检查等待回调入场的币种\\n * 2. 遍历追踪队列中 PRE_LISTING / LAUNCH_DAY 状态的币种\\n * 3. GetMarkets() 检测是否已在币安上线\\n * 4. 检测到上线 → 读取最终策略 → 安全检查 → 执行入场\\n */\\n\\nconst CONFIG = {\\n  POSITION_AMOUNT: $vars.positionAmount || 500,\\n  MAX_LEVERAGE:    $vars.maxLeverage    || 10,\\n  MIN_CONFIDENCE:  $vars.minConfidence  || 60,\\n  RETRY:           3,\\n  RETRY_INTERVAL:  500\\n};\\n\\nfunction updateStatus(symbol, status) {\\n  let list = JSON.parse(_G('nl_trackingList') || '[]');\\n  list = list.map(t => t.symbol === symbol ? { ...t, status } : t);\\n  _G('nl_trackingList', JSON.stringify(list));\\n}\\n\\nfunction getMarketInfo(coin) {\\n  const sym = coin + '_USDT.swap';\\n  for (let i = 0; i < CONFIG.RETRY; i++) {\\n    try {\\n      const markets = exchange.GetMarkets();\\n      if (markets && markets[sym]) return markets[sym];\\n    } catch(e) {}\\n    Sleep(CONFIG.RETRY_INTERVAL);\\n  }\\n  return null;\\n}\\n\\nfunction calcContractAmount(amountUSD, price, market) {\\n  if (!market || !market.CtVal || price <= 0) return 0;\\n  let qty = amountUSD / price / market.CtVal;\\n  qty = _N(qty, market.AmountPrecision || 0);\\n  if (market.MinQty && qty < market.MinQty) return 0;\\n  if (market.MaxQty && qty > market.MaxQty) qty = market.MaxQty;\\n  return qty;\\n}\\n\\nfunction hasPosition(coin) {\\n  try {\\n    exchange.SetCurrency(coin + '_USDT');\\n    exchange.SetContractType('swap');\\n    const positions = exchange.GetPositions();\\n    const sym = coin + '_USDT.swap';\\n    return positions && positions.some(p => p.Symbol === sym && Math.abs(p.Amount) > 0);\\n  } catch(e) { return false; }\\n}\\n\\nfunction executeEntry(symbol, ai) {\\n  const coin = symbol.replace(/USDT$/i, '');\\n  try {\\n    exchange.SetCurrency(coin + '_USDT');\\n    exchange.SetContractType('swap');\\n\\n    const leverage = Math.min(ai.leverage || 5, CONFIG.MAX_LEVERAGE);\\n    exchange.SetMarginLevel(leverage);\\n\\n    const market = getMarketInfo(coin);\\n    if (!market) return { success: false, reason: '无法获取市场信息' };\\n\\n    exchange.SetCurrency(coin + '_USDT');\\n    exchange.SetContractType('swap');\\n    const ticker = exchange.GetTicker();\\n    if (!ticker) return { success: false, reason: '无法获取价格' };\\n    const price = ticker.Last;\\n    if (price <= 0) return { success: false, reason: '价格异常' };\\n\\n    const account = exchange.GetAccount();\\n    const allocAmount = Math.min(CONFIG.POSITION_AMOUNT, account.Balance * 0.3);\\n\\n    const qty = calcContractAmount(allocAmount, price, market);\\n    if (qty <= 0) return { success: false, reason: '合约数量计算无效' };\\n\\n    let orderId;\\n    if (ai.direction === '做多') {\\n      exchange.SetDirection('buy');\\n      orderId = exchange.Buy(-1, qty);\\n    } else if (ai.direction === '做空') {\\n      exchange.SetDirection('sell');\\n      orderId = exchange.Sell(-1, qty);\\n    } else {\\n      return { success: false, reason: '方向为观望,不入场' };\\n    }\\n\\n    if (!orderId) return { success: false, reason: '下单失败' };\\n\\n    _G(coin + '_USDT.swap_maxprofit', 0);\\n    _G(coin + '_USDT.swap_maxStopLoss', ai.stopLoss || 10);\\n    _G(coin + '_nl_entry', JSON.stringify({\\n      symbol, coin,\\n      direction:  ai.direction,\\n      entryPrice: price,\\n      leverage,\\n      stopLoss:   ai.stopLoss,\\n      takeProfit: ai.takeProfit,\\n      priceRange: ai.priceRange,\\n      entryTime:  new Date().toISOString(),\\n      orderId\\n    }));\\n\\n    Log('入场成功:', symbol,\\n      '| 方向:', ai.direction,\\n      '| 价格:', price,\\n      '| 杠杆:', leverage + 'x',\\n      '| 数量:', qty + '张',\\n      '| 金额:', allocAmount + 'U',\\n      '| 止损:', ai.stopLoss + '%',\\n      '| 止盈:', ai.takeProfit + '%'\\n    );\\n    return { success: true, price, direction: ai.direction, qty, leverage };\\n  } catch(e) {\\n    Log('入场异常:', symbol, e.message);\\n    return { success: false, reason: e.message };\\n  }\\n}\\n\\n// ==================== 检查等待回调入场 ====================\\n\\nfunction checkPendingEntries(results) {\\n  const trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\n  const waiting = trackingList.filter(t => t.status === 'TRADING_WAIT');\\n\\n  for (const item of waiting) {\\n    const coin = item.symbol.replace(/USDT$/i, '');\\n    const waitRaw = _G(coin + '_nl_waitEntry');\\n\\n    // [BUG1修复] waitEntry 丢失时直接清理,防止状态永久挂起\\n    if (!waitRaw) {\\n      Log(item.symbol, 'waitEntry 数据丢失,强制清理 TRADING_WAIT');\\n      _G(coin + '_nl_listedAt', null);\\n      updateStatus(item.symbol, 'DONE');\\n      results.push({ symbol: item.symbol, action: 'skip', reason: 'waitEntry丢失' });\\n      continue;\\n    }\\n\\n    const wait = JSON.parse(waitRaw);\\n\\n    // [BUG1修复] TRADING_WAIT 独立超时检查(2小时),主循环的超时对此状态无效\\n    const waitStartTime = wait.waitStartTime || Date.now();\\n    if (!wait.waitStartTime) {\\n      // 首次执行时补写 waitStartTime(兼容旧数据)\\n      wait.waitStartTime = waitStartTime;\\n      _G(coin + '_nl_waitEntry', JSON.stringify(wait));\\n    }\\n    const hoursWaiting = (Date.now() - waitStartTime) / 3600000;\\n    if (hoursWaiting > 2) {\\n      Log(item.symbol, '回调等待超过2小时,放弃入场 | 等待时长:',\\n        _N(hoursWaiting * 60, 1) + '分钟',\\n        '| 开盘价:', wait.openPrice,\\n        '| 目标价:', wait.targetPrice\\n      );\\n      _G(coin + '_nl_waitEntry', null);\\n      _G(coin + '_nl_listedAt', null);\\n      updateStatus(item.symbol, 'DONE');\\n      results.push({ symbol: item.symbol, action: 'skip', reason: '回调等待超时' });\\n      continue;\\n    }\\n\\n    try {\\n      exchange.SetCurrency(coin + '_USDT');\\n      exchange.SetContractType('swap');\\n      const ticker = exchange.GetTicker();\\n      if (!ticker) continue;\\n      const curPrice = ticker.Last;\\n\\n      const triggered = wait.ai.direction === '做多'\\n        ? curPrice <= wait.targetPrice\\n        : curPrice >= wait.targetPrice;\\n\\n      if (triggered) {\\n        Log(item.symbol, '回调到位 | 当前价:', curPrice, '| 目标价:', wait.targetPrice);\\n        const execResult = executeEntry(item.symbol, wait.ai);\\n        if (execResult.success) {\\n          _G(coin + '_nl_waitEntry', null);\\n          updateStatus(item.symbol, 'TRADING');\\n          results.push({ symbol: item.symbol, action: 'entered_after_drawdown', ...execResult });\\n        }\\n      } else {\\n        Log(item.symbol, '等待回调中 | 当前价:', curPrice,\\n          '| 目标价:', wait.targetPrice,\\n          '| 还差:', _N(Math.abs(curPrice - wait.targetPrice) / wait.openPrice * 100, 2) + '%',\\n          '| 已等:', _N(hoursWaiting * 60, 1) + '分钟 / 上限120分钟'\\n        );\\n      }\\n    } catch(e) {\\n      Log(item.symbol, '检查等待入场异常:', e.message);\\n    }\\n    Sleep(200);\\n  }\\n}\\n\\n// ==================== 主逻辑 ====================\\n\\nconst trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\nconst results = [];\\n\\ncheckPendingEntries(results);\\n\\nif (trackingList.length === 0) {\\n  return [{ json: { action: 'no_tracking' } }];\\n}\\n\\nlet allMarkets = {};\\ntry { allMarkets = exchange.GetMarkets() || {}; } catch(e) { Log('GetMarkets失败:', e.message); }\\n\\nconst toCheck = trackingList.filter(t => t.status === 'PRE_LISTING' || t.status === 'LAUNCH_DAY');\\n\\nfor (const item of toCheck) {\\n  const symbol = item.symbol;\\n  const coin = symbol.replace(/USDT$/i, '');\\n  const marketKey = coin + '_USDT.swap';\\n  const listedAtKey = coin + '_nl_listedAt';\\n\\n  if (!allMarkets[marketKey]) continue;\\n\\n  // 第一次检测到上线,记录实际上线时间\\n  if (!_G(listedAtKey)) {\\n    _G(listedAtKey, Date.now());\\n    Log(symbol, '首次检测到上线,记录时间:', new Date().toISOString());\\n  }\\n\\n  // 超过2小时未成功入场,视为异常跳过\\n  const listedAt = _G(listedAtKey);\\n  const hoursSinceListed = (Date.now() - listedAt) / 3600000;\\n  if (hoursSinceListed > 2) {\\n    Log(symbol, '自上线已超过2小时仍未入场,视为异常,跳过');\\n    _G(listedAtKey, null);\\n    updateStatus(symbol, 'DONE');\\n    results.push({ symbol, action: 'skip', reason: '超时未入场' });\\n    continue;\\n  }\\n\\n  Log('在币安检测到上线:', symbol,\\n    '| 已上线:', _N(hoursSinceListed * 60, 1) + '分钟'\\n  );\\n\\n  const strategyRaw = _G('nl_strategy_' + symbol);\\n  if (!strategyRaw) {\\n    Log(symbol, '无策略数据,等待下次分析');\\n    continue;\\n  }\\n\\n  const strategy = JSON.parse(strategyRaw);\\n  const ai = strategy.aiResult;\\n\\n  if (!ai) {\\n    Log(symbol, 'AI策略数据异常,跳过');\\n    _G(listedAtKey, null);\\n    updateStatus(symbol, 'DONE');\\n    results.push({ symbol, action: 'skip', reason: 'AI数据异常' });\\n    continue;\\n  }\\n\\n  if (ai.direction === '观望') {\\n    Log(symbol, 'AI建议观望,不入场');\\n    _G(listedAtKey, null);\\n    updateStatus(symbol, 'DONE');\\n    results.push({ symbol, action: 'skip', reason: '观望' });\\n    continue;\\n  }\\n\\n  if (ai.riskLevel === '高') {\\n    Log(symbol, '高风险,不入场 |', (ai.riskPoints || []).join('、'));\\n    _G(listedAtKey, null);\\n    updateStatus(symbol, 'DONE');\\n    results.push({ symbol, action: 'skip', reason: '高风险' });\\n    continue;\\n  }\\n\\n  if (ai.confidence < CONFIG.MIN_CONFIDENCE) {\\n    Log(symbol, '置信度过低(' + ai.confidence + '% < ' + CONFIG.MIN_CONFIDENCE + '%),不入场');\\n    _G(listedAtKey, null);\\n    updateStatus(symbol, 'DONE');\\n    results.push({ symbol, action: 'skip', reason: '置信度不足' });\\n    continue;\\n  }\\n\\n  if (hasPosition(coin)) {\\n    Log(symbol, '已有持仓,标记为TRADING');\\n    _G(listedAtKey, null);\\n    updateStatus(symbol, 'TRADING');\\n    results.push({ symbol, action: 'already_trading' });\\n    continue;\\n  }\\n\\n  const entryTiming = ai.entryTiming || 'immediate';\\n\\n  if (entryTiming === 'immediate') {\\n    const execResult = executeEntry(symbol, ai);\\n    if (execResult.success) {\\n      _G(listedAtKey, null);\\n      updateStatus(symbol, 'TRADING');\\n      results.push({ symbol, action: 'entered', ...execResult });\\n    } else {\\n      // [BUG2修复] 记录失败次数,超过3次放弃,避免无限重试\\n      const retryKey = coin + '_nl_retryCount';\\n      const retryCount = (_G(retryKey) || 0) + 1;\\n      _G(retryKey, retryCount);\\n      Log(symbol, '入场失败(' + retryCount + '/3):', execResult.reason);\\n      if (retryCount >= 3) {\\n        Log(symbol, '连续失败3次,放弃入场');\\n        _G(retryKey, null);\\n        _G(listedAtKey, null);\\n        updateStatus(symbol, 'DONE');\\n        results.push({ symbol, action: 'skip', reason: '重试超限:' + execResult.reason });\\n      } else {\\n        results.push({ symbol, action: 'failed', retryCount, reason: execResult.reason });\\n      }\\n    }\\n  }\\n  else if (entryTiming.startsWith('drawdown_')) {\\n    const pct = parseFloat(entryTiming.split('_')[1]) || 5;\\n    exchange.SetCurrency(coin + '_USDT');\\n    exchange.SetContractType('swap');\\n    const ticker = exchange.GetTicker();\\n    const openPrice = ticker ? ticker.Last : 0;\\n    if (openPrice > 0) {\\n      const targetPrice = ai.direction === '做多'\\n        ? openPrice * (1 - pct / 100)\\n        : openPrice * (1 + pct / 100);\\n      _G(coin + '_nl_waitEntry', JSON.stringify({\\n        type: 'drawdown',\\n        waitStartTime: Date.now(),   // [BUG1修复] 记录开始等待时间,供超时检查使用\\n        openPrice,\\n        targetPrice,\\n        drawdownPct: pct,\\n        ai\\n      }));\\n      updateStatus(symbol, 'TRADING_WAIT');\\n      Log(symbol, '等待回调', pct + '%后入场',\\n        '| 开盘价:', openPrice,\\n        '| 目标价:', targetPrice\\n      );\\n      results.push({ symbol, action: 'waiting_drawdown', openPrice, pct, targetPrice });\\n    }\\n  }\\n\\n  Sleep(300);\\n}\\n\\nreturn [{ json: { checked: toCheck.length, results, timestamp: new Date().toISOString() } }];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[144,416],\"id\":\"c1b3aed2-a972-4f09-9a1f-d6bf1c4b8be9\",\"name\":\"上线检测+入场执行\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"/*\\n * 可视化仪表板 + 止盈止损监控\\n * 1. 账户概览\\n * 2. 追踪队列状态\\n * 3. AI分析摘要\\n * 4. 实时持仓监控(含止盈止损自动执行)\\n */\\n\\nconst TP_SL = {\\n  DEFAULT_SL:             10,\\n  DEFAULT_TP:             25,\\n  TRAILING_TRIGGER:       30,\\n  TRAILING_DRAWDOWN:       8\\n};\\n\\n// ==================== 工具 ====================\\n\\nfunction getStartTime() {\\n  let t = _G('nl_STARTTIME');\\n  if (!t) { t = Date.now(); _G('nl_STARTTIME', t); }\\n  return t;\\n}\\n\\nfunction formatRuntime(ts) {\\n  const d = Date.now() - ts;\\n  const days = Math.floor(d / 86400000);\\n  const h    = Math.floor((d % 86400000) / 3600000);\\n  const m    = Math.floor((d % 3600000) / 60000);\\n  if (days > 0) return days + '天' + h + '时' + m + '分';\\n  if (h > 0)    return h + '时' + m + '分';\\n  return m + '分';\\n}\\n\\nfunction fmtPnl(v) {\\n  if (v > 0) return '🟢 +$' + _N(v, 2);\\n  if (v < 0) return '🔴 $' + _N(v, 2);\\n  return '⚪ $0';\\n}\\n\\nfunction fmtPct(v) {\\n  if (v > 0) return '🟢 +' + _N(v, 2) + '%';\\n  if (v < 0) return '🔴 ' + _N(v, 2) + '%';\\n  return '⚪ 0%';\\n}\\n\\n// ==================== 命令处理 ====================\\n\\nfunction handleCommand() {\\n  const cmd = GetCommand();\\n  if (!cmd) return;\\n  const parts = cmd.split(':');\\n  const action = parts[0];\\n\\n  if (action === '重置日志') {\\n    LogReset();\\n    _G('nl_initmoney', null);\\n    Log('日志和初始资金已重置');\\n  }\\n  else if (action === '清除币种') {\\n    const symbol = parts[1];\\n    if (symbol) {\\n      let list = JSON.parse(_G('nl_trackingList') || '[]');\\n      list = list.filter(t => t.symbol !== symbol);\\n      _G('nl_trackingList', JSON.stringify(list));\\n      _G('nl_history_' + symbol, null);\\n      _G('nl_strategy_' + symbol, null);\\n      Log('已清除币种:', symbol);\\n    }\\n  }\\n  else if (action === '手动平仓') {\\n    const coin = parts[1];\\n    const posType = parseInt(parts[2]);\\n    const confirmed = parts.length >= 4;\\n    if (confirmed && coin) {\\n      try {\\n        exchange.SetCurrency(coin + '_USDT');\\n        exchange.SetContractType('swap');\\n        const positions = exchange.GetPositions();\\n        let targetPos = null;\\n        for (const pos of positions) {\\n          if (pos.Symbol.indexOf(coin) !== -1 && pos.Type === posType) { targetPos = pos; break; }\\n        }\\n        if (targetPos && Math.abs(targetPos.Amount) > 0) {\\n          const isLong = posType === PD_LONG || posType === 0;\\n          if (isLong) { exchange.SetDirection('closebuy');  exchange.Sell(-1, Math.abs(targetPos.Amount)); }\\n          else        { exchange.SetDirection('closesell'); exchange.Buy(-1, Math.abs(targetPos.Amount)); }\\n          Log(coin, '手动平仓成功');\\n          _G(coin + '_USDT.swap_maxprofit', null);\\n          _G(coin + '_USDT.swap_maxStopLoss', null);\\n          _G(coin + '_USDT.swap_tpDrawdown', null);\\n          _G(coin + '_nl_entry', null);\\n          let list = JSON.parse(_G('nl_trackingList') || '[]');\\n          list = list.map(t => t.symbol === coin + 'USDT' ? { ...t, status: 'DONE' } : t);\\n          _G('nl_trackingList', JSON.stringify(list));\\n        } else { Log(coin, '无持仓'); }\\n      } catch(e) { Log(coin, '平仓失败:', e.message); }\\n    }\\n  }\\n  else if (action === '修改止损') {\\n    const coin = parts[1];\\n    const jsonStr = parts.slice(2).join(':');\\n    try {\\n      const params = JSON.parse(jsonStr);\\n      const sl = parseFloat(params['止损%'] || 0);\\n      const tp = parseFloat(params['止盈回撤%'] || 0);\\n      if (sl > 0) { _G(coin + '_USDT.swap_maxStopLoss', sl); Log(coin, '止损更新:', sl + '%'); }\\n      if (tp > 0) { _G(coin + '_USDT.swap_tpDrawdown', tp); Log(coin, '移动止盈回撤:', tp + '%'); }\\n    } catch(e) { Log('修改止损失败:', e.message); }\\n  }\\n}\\n\\n// ==================== 止盈止损监控 ====================\\n\\nfunction monitorPositions(positions, tickers) {\\n  if (!positions || positions.length === 0) return;\\n  for (const pos of positions) {\\n    if (Math.abs(pos.Amount) === 0) continue;\\n    const coinMatch = pos.Symbol.match(/^(.+)_USDT/);\\n    if (!coinMatch) continue;\\n    const coin = coinMatch[1];\\n    const ticker = tickers[coin + '_USDT.swap'];\\n    if (!ticker) continue;\\n\\n    const isLong   = pos.Type === PD_LONG || pos.Type === 0;\\n    const curPrice = ticker.Last;\\n    const entPrice = pos.Price;\\n    const amount   = Math.abs(pos.Amount);\\n    const pnlPct   = ((curPrice - entPrice) * (isLong ? 1 : -1) / entPrice * 100);\\n\\n    const maxProfitKey = coin + '_USDT.swap_maxprofit';\\n    const slKey        = coin + '_USDT.swap_maxStopLoss';\\n    const tpDrawKey    = coin + '_USDT.swap_tpDrawdown';\\n\\n    let maxProfit = _G(maxProfitKey);\\n    if (maxProfit === null) { maxProfit = pnlPct / 100; _G(maxProfitKey, maxProfit); }\\n    else if (pnlPct / 100 > maxProfit) { maxProfit = pnlPct / 100; _G(maxProfitKey, maxProfit); }\\n    const maxPnlPct = maxProfit * 100;\\n\\n    let tpDrawdown = _G(tpDrawKey) || 0;\\n    if (tpDrawdown === 0 && maxPnlPct >= TP_SL.TRAILING_TRIGGER) {\\n      tpDrawdown = TP_SL.TRAILING_DRAWDOWN;\\n      _G(tpDrawKey, tpDrawdown);\\n      Log(coin, '浮盈达' + _N(maxPnlPct, 2) + '%,自动启用移动止盈,回撤' + tpDrawdown + '%触发');\\n    }\\n\\n    const maxSL   = _G(slKey) || TP_SL.DEFAULT_SL;\\n    const drawdown = maxPnlPct - pnlPct;\\n\\n    let closeReason = null;\\n    if (tpDrawdown > 0 && maxPnlPct > 0 && drawdown >= tpDrawdown) {\\n      closeReason = '移动止盈(回撤' + _N(drawdown, 2) + '%>=' + tpDrawdown + '%)';\\n    }\\n    if (!closeReason && maxSL > 0 && pnlPct <= -maxSL) {\\n      closeReason = '止损(亏损' + _N(Math.abs(pnlPct), 2) + '%>=' + maxSL + '%)';\\n    }\\n    const entryRaw = _G(coin + '_nl_entry');\\n    if (!closeReason && entryRaw) {\\n      const entry = JSON.parse(entryRaw);\\n      if (entry.takeProfit && pnlPct >= entry.takeProfit) {\\n        closeReason = '固定止盈(' + _N(pnlPct, 2) + '%>=' + entry.takeProfit + '%)';\\n      }\\n    }\\n\\n    if (closeReason) {\\n      try {\\n        exchange.SetCurrency(coin + '_USDT');\\n        exchange.SetContractType('swap');\\n        if (isLong) { exchange.SetDirection('closebuy');  exchange.Sell(-1, amount); }\\n        else        { exchange.SetDirection('closesell'); exchange.Buy(-1, amount); }\\n        Log(coin, '触发', closeReason, '自动平仓 | 浮盈:', _N(pnlPct, 2) + '%');\\n        _G(maxProfitKey, null); _G(slKey, null); _G(tpDrawKey, null); _G(coin + '_nl_entry', null);\\n        let list = JSON.parse(_G('nl_trackingList') || '[]');\\n        list = list.map(t => t.symbol === coin + 'USDT' ? { ...t, status: 'DONE' } : t);\\n        _G('nl_trackingList', JSON.stringify(list));\\n      } catch(e) { Log(coin, '自动平仓失败:', e.message); }\\n    }\\n  }\\n}\\n\\n// ==================== 表格:账户概览 ====================\\n\\nfunction createAccountTable(account) {\\n  const table = {\\n    type: 'table', title: '账户概览',\\n    cols: ['运行时间', '初始权益', '当前权益', '总收益', '收益率', '操作'],\\n    rows: []\\n  };\\n  const initMoney = _G('nl_initmoney') || account.Equity;\\n  const profit    = account.Equity - initMoney;\\n  const pct       = ((profit / initMoney) * 100);\\n  LogProfit(profit, '&');\\n  table.rows.push([\\n    formatRuntime(getStartTime()),\\n    '$' + _N(initMoney, 2),\\n    '$' + _N(account.Equity, 2),\\n    fmtPnl(profit),\\n    fmtPct(pct),\\n    [{ type: 'button', cmd: '重置日志', name: '重置日志' }]\\n  ]);\\n  return table;\\n}\\n\\n// ==================== 表格:追踪队列 ====================\\n\\nfunction createTrackingTable() {\\n  const table = {\\n    type: 'table', title: '追踪队列',\\n    cols: ['币种', '状态', '上线日期', '距今(h)', '分析次数', 'AI方向', '置信度', '风险', '操作'],\\n    rows: []\\n  };\\n  const trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\n  if (trackingList.length === 0) {\\n    table.rows.push(['暂无追踪币种', '-', '-', '-', '-', '-', '-', '-', '-']);\\n    return table;\\n  }\\n  for (const item of trackingList) {\\n    const statusMap = {\\n      'PRE_LISTING':  '预追踪',\\n      'LAUNCH_DAY':   '开盘日',\\n      'TRADING_WAIT': '等待回调',  // ← 新增这行\\n      'TRADING':      '交易中',\\n      'DONE':         '已完成'\\n    };\\n    const statusDisplay = statusMap[item.status] || item.status;\\n    const stratRaw = _G('nl_strategy_' + item.symbol);\\n    let direction = '-', confidence = '-', riskLevel = '-';\\n    if (stratRaw) {\\n      const ai = JSON.parse(stratRaw).aiResult;\\n      if (ai) { direction = ai.direction; confidence = ai.confidence + '%'; riskLevel = ai.riskLevel; }\\n    }\\n    table.rows.push([\\n      item.symbol, statusDisplay, item.launchDate,\\n      item.hoursToLaunch !== undefined ? item.hoursToLaunch : '-',\\n      item.analysisCount || 0,\\n      direction, confidence, riskLevel,\\n      [{ type: 'button', cmd: '清除币种:' + item.symbol, name: '清除' }]\\n    ]);\\n  }\\n  return table;\\n}\\n\\n// ==================== 表格:AI分析摘要 ====================\\n\\nfunction createAITable() {\\n  const table = {\\n    type: 'table', title: 'AI分析摘要(最新一次)',\\n    cols: ['币种', '更新时间', '阶段', '距上线', '方向', '置信度', '趋势', '入场时机', '杠杆', '止损', '止盈', '风险', '综合判断'],\\n    rows: []\\n  };\\n  const trackingList = JSON.parse(_G('nl_trackingList') || '[]');\\n  const active = trackingList.filter(t => t.analysisCount > 0);\\n  if (active.length === 0) {\\n    table.rows.push(['暂无', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '等待AI分析']);\\n    return table;\\n  }\\n  for (const item of active) {\\n    const stratRaw = _G('nl_strategy_' + item.symbol);\\n    if (!stratRaw) continue;\\n    const strat = JSON.parse(stratRaw);\\n    const ai = strat.aiResult;\\n    if (!ai) continue;\\n    const updatedAt = strat.updatedAt ? strat.updatedAt.replace('T', ' ').substring(0, 16) : '-';\\n    table.rows.push([\\n      item.symbol, updatedAt, strat.phase || '-',\\n      strat.hoursToLaunch !== undefined ? (typeof strat.hoursToLaunch === 'number' ? strat.hoursToLaunch + 'h' : strat.hoursToLaunch) : '-',\\n      ai.direction, ai.confidence + '%', ai.trendConsistency || '-',\\n      ai.entryTiming || '-',\\n      ai.leverage ? ai.leverage + 'x' : '-',\\n      ai.stopLoss  ? ai.stopLoss  + '%' : '-',\\n      ai.takeProfit ? ai.takeProfit + '%' : '-',\\n      ai.riskLevel || '-',\\n      ai.summary || '-'\\n    ]);\\n  }\\n  return table;\\n}\\n\\n// ==================== 表格:实时持仓 ====================\\n\\nfunction createPositionTable(positions, tickers) {\\n  const table = {\\n    type: 'table', title: '实时持仓监控',\\n    cols: ['币种', '方向', '入场价', '现价', '杠杆', '头寸(U)', '浮盈', '浮盈%', '最高浮盈%', '移动止盈', '止损', '状态', '操作'],\\n    rows: []\\n  };\\n  let hasPos = false;\\n  for (const pos of (positions || [])) {\\n    if (Math.abs(pos.Amount) === 0) continue;\\n    const cm = pos.Symbol.match(/^(.+)_USDT/);\\n    if (!cm) continue;\\n    const coin = cm[1];\\n    const ticker = tickers[coin + '_USDT.swap'];\\n    if (!ticker) continue;\\n    hasPos = true;\\n\\n    const isLong   = pos.Type === PD_LONG || pos.Type === 0;\\n    const curPrice = ticker.Last;\\n    const entPrice = pos.Price;\\n    const amount   = Math.abs(pos.Amount);\\n    const notional = amount * curPrice;\\n    const pnlUsd   = pos.Profit || ((curPrice - entPrice) * (isLong ? 1 : -1) * amount);\\n    const pnlPct   = ((curPrice - entPrice) * (isLong ? 1 : -1) / entPrice * 100);\\n    const maxPnlPct = (_G(coin + '_USDT.swap_maxprofit') || 0) * 100;\\n    const tpDrawdown = _G(coin + '_USDT.swap_tpDrawdown') || 0;\\n    const maxSL      = _G(coin + '_USDT.swap_maxStopLoss') || TP_SL.DEFAULT_SL;\\n    const entryRaw   = _G(coin + '_nl_entry');\\n    const entryInfo  = entryRaw ? JSON.parse(entryRaw) : null;\\n    const tpFixed    = entryInfo ? entryInfo.takeProfit : TP_SL.DEFAULT_TP;\\n    const leverage   = pos.MarginLevel || (entryInfo ? entryInfo.leverage : '-');\\n\\n    let status = '持仓中';\\n    if (tpDrawdown > 0 && maxPnlPct > 0) {\\n      const remain = tpDrawdown - (maxPnlPct - pnlPct);\\n      status = remain <= 1 ? '即将止盈(剩' + _N(remain, 1) + '%)' : '移动止盈' + tpDrawdown + '%';\\n    }\\n    if (pnlPct <= -(maxSL - 2)) status = '接近止损位';\\n\\n    table.rows.push([\\n      coin, isLong ? '多' : '空',\\n      '$' + _N(entPrice, 6), '$' + _N(curPrice, 6),\\n      leverage + 'x', '$' + _N(notional, 2),\\n      fmtPnl(pnlUsd), fmtPct(pnlPct),\\n      maxPnlPct > 0 ? '+' + _N(maxPnlPct, 2) + '%' : _N(maxPnlPct, 2) + '%',\\n      tpDrawdown > 0 ? '回撤' + tpDrawdown + '%' : '未启用',\\n      '-' + maxSL + '% / +' + tpFixed + '%',\\n      status,\\n      [\\n        {\\n          type: 'button', name: '平仓',\\n          cmd: '手动平仓:' + coin + ':' + pos.Type + ':confirm',\\n          input: { type: 'string', defValue: '确认平仓 ' + coin }\\n        },\\n        {\\n          type: 'button', name: '修改止损',\\n          cmd: '修改止损:' + coin,\\n          group: [\\n            { name: '止损%', type: 'number', defValue: maxSL },\\n            { name: '止盈回撤%', type: 'number', defValue: tpDrawdown || TP_SL.TRAILING_DRAWDOWN }\\n          ]\\n        }\\n      ]\\n    ]);\\n  }\\n  if (!hasPos) {\\n    table.rows.push(['当前无持仓', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '等待入场信号', '-']);\\n  }\\n  return table;\\n}\\n\\n// ==================== 主执行 ====================\\n\\nhandleCommand();\\n\\nconst account   = exchange.GetAccount();\\nconst positions = exchange.GetPositions() || [];\\nconst tickers   = {};\\ntry {\\n  const allTickers = exchange.GetTickers() || [];\\n  for (const t of allTickers) tickers[t.Symbol] = t;\\n} catch(e) {}\\n\\nmonitorPositions(positions, tickers);\\n\\nconst t1 = createAccountTable(account);\\nconst t2 = createTrackingTable();\\nconst t3 = createAITable();\\nconst t4 = createPositionTable(positions, tickers);\\n\\nLogStatus(\\n  '`' + JSON.stringify(t1) + '`\\\\n\\\\n' +\\n  '`' + JSON.stringify(t2) + '`\\\\n\\\\n' +\\n  '`' + JSON.stringify(t3) + '`\\\\n\\\\n' +\\n  '`' + JSON.stringify(t4) + '`'\\n);\\n\\nreturn [{ json: { success: true, timestamp: new Date().toISOString() } }];\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[320,416],\"id\":\"d7d2bb2d-9726-4c52-94c4-9c6f0408a6e1\",\"name\":\"可视化仪表板+止盈止损\"}],\"pinData\":{},\"connections\":{\"慢触发器(15min)\":{\"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\":\"读历史+构建Prompt\",\"type\":\"main\",\"index\":0}]]},\"读历史+构建Prompt\":{\"main\":[[{\"node\":\"AI分析智能体\",\"type\":\"main\",\"index\":0}]]},\"AI分析智能体\":{\"main\":[[{\"node\":\"保存历史+更新策略\",\"type\":\"main\",\"index\":0}]]},\"OpenAI 模型\":{\"ai_languageModel\":[[{\"node\":\"AI分析智能体\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"快触发器(1min)\":{\"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\":[],\"credentials\":{},\"id\":\"645b98a6-729a-4e32-8848-45def8709777\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"慢触发器(15min)\"}}"}