{
  "parameters": {
    "jsCode": "// ============================================================\n// PICK BEST CANDIDATE \u2014 scores from ticker data only\n// ============================================================\n\nconst MIN_VOLUME_USDT = 200000;\nconst MIN_CHANGE_PCT  = 0.3;\nconst MAX_CHANGE_PCT  = 50.0;\nconst MIN_PRICE       = 0.00001;\n\nconst EXCLUDE = new Set([\n  'USDC','BUSD','TUSD','DAI','FDUSD','USDP','USDS','USDD','GUSD','FRAX',\n  'USTC','USDN','LUSD','SUSD','CUSD','ZUSD','EURS','EURT','GYEN','BIDR',\n  'BVND','IDRT','USDJ','UST','BTC','ETH','BNB'\n]);\n\n// Handle both: single item with full array, and n8n splitting array into multiple items\nconst allItems = $input.all();\nlet tickers;\nif (allItems.length > 1) {\n  // n8n split the HTTP response array into individual items\n  tickers = allItems.map(item => item.json).filter(t => t && t.symbol);\n} else {\n  const raw = allItems[0].json;\n  if (Array.isArray(raw)) {\n    tickers = raw;\n  } else {\n    const vals = Object.values(raw);\n    tickers = (vals.length > 0 && vals[0] && typeof vals[0] === 'object' && vals[0].symbol) ? vals : [];\n  }\n}\n\nconst sd = $getWorkflowStaticData('global');\nconst heldSymbol = sd.position ? sd.position.symbol : null;\nlet heldCurrentPrice = sd.position ? sd.position.entry : 0;\n\nconst candidates = [];\nfor (const t of tickers) {\n  const symbol    = t.symbol || '';\n  const change24h = parseFloat(t.priceChangePercent || 0);\n  const volume    = parseFloat(t.quoteVolume || 0);\n  const price     = parseFloat(t.lastPrice || 0);\n  const high24h   = parseFloat(t.highPrice  || price);\n  const low24h    = parseFloat(t.lowPrice   || price);\n\n  if (symbol === heldSymbol) heldCurrentPrice = price;  // always capture current price of held coin\n\n  if (!symbol.endsWith('USDT')) continue;\n  const base = symbol.replace('USDT', '');\n  let excluded = false;\n  for (const ex of EXCLUDE) {\n    if (base === ex || base.startsWith(ex) || base.endsWith(ex)) { excluded = true; break; }\n  }\n  if (excluded) continue;\n  if (!/^[A-Z0-9]+$/.test(base)) continue;  // reject non-ASCII/unicode symbols (scam tokens)\n  if (price < MIN_PRICE || volume < MIN_VOLUME_USDT) continue;\n  if (change24h < MIN_CHANGE_PCT || change24h > MAX_CHANGE_PCT) continue;\n\n  const ch = Math.abs(change24h);\n  let stageScore = 0;\n  if      (ch >= 3  && ch < 8)  stageScore = 25;\n  else if (ch >= 8  && ch < 15) stageScore = 20;\n  else if (ch >= 15 && ch < 25) stageScore = 12;\n  else if (ch >= 25 && ch < 40) stageScore = 5;\n  else if (ch >= 0.5)           stageScore = 8;\n\n  const range = high24h - low24h;\n  const rangePct = range > 0 ? (price - low24h) / range : 0.5;\n  let rangeScore = 0;\n  if      (rangePct < 0.40) rangeScore = 15;\n  else if (rangePct < 0.60) rangeScore = 12;\n  else if (rangePct < 0.75) rangeScore = 6;\n  else if (rangePct < 0.90) rangeScore = 2;\n\n  let volScore = 0;\n  if      (volume >= 10000000) volScore = 10;\n  else if (volume >=  5000000) volScore = 8;\n  else if (volume >=  2000000) volScore = 6;\n  else if (volume >=   500000) volScore = 3;\n\n  const score    = stageScore + rangeScore + volScore;\n  const preScore = score * Math.log10(Math.max(volume, 1));\n\n  candidates.push({\n    symbol, change24h, volume, price,\n    high24h, low24h, rangePct: +rangePct.toFixed(3),\n    score, preScore,\n    scoringDetail: { stageScore, rangeScore, volScore, change24h, rangePct: +rangePct.toFixed(3) }\n  });\n}\n\ncandidates.sort((a, b) => b.preScore - a.preScore);\n\nconst scanSummary = candidates.slice(0, 8).map(c => ({\n  symbol: c.symbol, score: c.score,\n  change24h: c.change24h, rangePct: c.rangePct, price: c.price\n}));\n\nif (candidates.length === 0) {\n  return [{ json: {\n    bestSymbol: heldSymbol || 'BTCUSDT',\n    noCandidates: true,\n    heldSymbol,\n    heldCurrentPrice,\n    scanSummary: []\n  }}];\n}\n\nconst best = candidates[0];\n\nconst roomToHigh = best.high24h > best.price ? (best.high24h - best.price) / best.price : 0.02;\nconst tpPct = Math.max(Math.min(roomToHigh * 0.6, 0.05), 0.015);\nconst slPct = 0.01;\n\nreturn [{ json: {\n  bestSymbol:       best.symbol,\n  bestCandidate:    best,\n  heldSymbol,\n  heldCurrentPrice,\n  tpPct:            +tpPct.toFixed(4),\n  slPct:            +slPct.toFixed(4),\n  noCandidates:     false,\n  scanSummary\n}}];"
  },
  "id": "7aefddac-d805-4f6b-a2db-381df4ebf297",
  "name": "Pick Best Candidate",
  "type": "n8n-nodes-base.code",
  "typeVersion": 2,
  "position": [
    9056,
    -752
  ]
}
