Divergence Trading - Spot vs Poly Price Lag Detects when Binance spot prices move but Polymarket 15-minute binary markets haven't caught up yet. Buys the lagging side and sells when it corrects. Strategy tags match the CLAUDE.md encoding: (0.12-0.14% move in 15s window). Starts in dry-run mode by default (no real orders). Quick Start Commands Start / Stop Monitor Configure Detection Algorithm For each spot tick, across all configured windows (5s, 10s, 15s, 30s, 60s, 90s, 120s): 1. Look up spot price N seconds ago via binary search 2. Calculate 3. If move = threshold AND poly is fresh (< 5s st…

+ Math.abs(n).toFixed(2); }\nfunction fmtPct(n: number): string { return (n >= 0 ? '+' : '') + n.toFixed(1) + '%'; }\n\n// ── Command Handler ─────────────────────────────────────────────────────────\n\nasync function execute(args: string): Promise\u003cstring> {\n const parts = args.trim().split(/\\s+/);\n const cmd = parts[0]?.toLowerCase() || 'help';\n\n switch (cmd) {\n case 'start': {\n if (engine) return 'Already running. `/div stop` first.';\n\n const feed = await getFeed();\n if (!feed) return 'Crypto feed not available. Check that Binance WS is reachable.';\n const exec = await getExecution();\n\n const assetArg = parts[1] && !parts[1].startsWith('-') ? parts[1] : null;\n const assets = assetArg ? assetArg.toUpperCase().split(',') : ['BTC', 'ETH', 'SOL', 'XRP'];\n const dryRun = args.includes('--dry-run') || args.includes('--dry') || !exec;\n const sizeMatch = args.match(/--size\\s+(\\d+)/);\n const defaultSizeUsd = sizeMatch ? parseInt(sizeMatch[1], 10) : 20;\n\n const { createHftDivergenceEngine } = await import('../../../strategies/hft-divergence/strategy.js');\n engine = createHftDivergenceEngine(feed, exec, { assets, defaultSizeUsd, dryRun });\n await engine.start();\n\n const mode = dryRun ? 'DRY RUN' : 'LIVE';\n const cfg = engine.getConfig();\n return [\n `**Divergence Trading Started [${mode}]**`,\n `Assets: ${assets.join(', ')}`,\n `Size: ${defaultSizeUsd}/trade | Windows: ${cfg.windows.join(', ')}s`,\n `TP: ${cfg.takeProfitPct}% | SL: ${cfg.stopLossPct}% | Trailing: ${cfg.trailingStopPct}% (at +${cfg.trailingActivationPct}%)`,\n `Min spot move: ${cfg.minSpotMovePct}% | Max poly stale: ${cfg.maxPolyFreshnessSec}s`,\n ].join('\\n');\n }\n\n case 'stop': {\n if (!engine) return 'Not running.';\n const stats = engine.getStats();\n engine.stop();\n engine = null;\n return `Stopped. ${stats.totalTrades} trades, ${fmtUsd(stats.netPnlUsd)} net, ${stats.winRate.toFixed(0)}% WR`;\n }\n\n case 'status': {\n if (!engine) return 'Not running. `/div start`';\n const s = engine.getStats();\n const r = engine.getRoundInfo();\n const p = engine.getPositions();\n\n let out = `**Divergence Status**\\n`;\n out += `Round: #${r.slot} | ${r.timeLeftSec.toFixed(0)}s left\\n`;\n out += `Markets: ${engine.getMarkets().length} | Open: ${s.openPositions}\\n`;\n out += `Trades: ${s.totalTrades} (${s.wins}W/${s.losses}L) ${s.winRate.toFixed(0)}% WR\\n`;\n out += `PnL: ${fmtUsd(s.netPnlUsd)} | Today: ${fmtUsd(s.dailyPnlUsd)}\\n`;\n out += `Best: ${fmtPct(s.bestTradePct)} | Worst: ${fmtPct(s.worstTradePct)}\\n`;\n\n if (Object.keys(s.signalCounts).length > 0) {\n const top = Object.entries(s.signalCounts).sort((a, b) => b[1] - a[1]).slice(0, 5);\n out += `Top tags: ${top.map(([k, v]) => `${k}(${v})`).join(', ')}\\n`;\n }\n\n if (p.length > 0) {\n out += `\\n**Open Positions:**\\n`;\n for (const pos of p) {\n const pnl = ((pos.currentPrice - pos.entryPrice) / pos.entryPrice) * 100;\n const secsLeft = Math.max(0, (pos.expiresAt - Date.now()) / 1000);\n out += ` ${pos.asset} ${pos.direction.toUpperCase()} @ ${pos.entryPrice.toFixed(2)} -> ${pos.currentPrice.toFixed(2)} (${fmtPct(pnl)}) [${pos.strategyTag}] ${secsLeft.toFixed(0)}s left\\n`;\n }\n }\n return out;\n }\n\n case 'positions': {\n if (!engine) return 'Not running.';\n const closed = engine.getClosed().slice(-20);\n if (closed.length === 0) return 'No closed trades yet.';\n\n let out = `**Last ${closed.length} Trades:**\\n`;\n for (const c of [...closed].reverse()) {\n out += ` ${c.asset} ${c.direction.toUpperCase()} ${fmtPct(c.pnlPct)} (${fmtUsd(c.pnlUsd)}) [${c.strategyTag}] ${c.exitReason} ${c.holdTimeSec.toFixed(0)}s\\n`;\n }\n return out;\n }\n\n case 'markets': {\n if (!engine) {\n // Allow market check without running engine\n const { createMarketRotator } = await import('../../../strategies/hft-divergence/market-rotator.js');\n const defaultCfg = {\n assets: parts[1] ? parts[1].toUpperCase().split(',') : ['BTC', 'ETH', 'SOL', 'XRP'],\n marketDurationSec: 900,\n windows: [], thresholdBuckets: [], minSpotMovePct: 0.08,\n maxPolyFreshnessSec: 5, maxPolyMidForEntry: 0.85,\n defaultSizeUsd: 20, maxPositionSizeUsd: 100, maxConcurrentPositions: 3,\n preferMaker: true, makerTimeoutMs: 15000, takerBufferCents: 0.01, negRisk: true,\n takeProfitPct: 15, stopLossPct: 25, trailingStopPct: 8, trailingActivationPct: 10,\n forceExitSec: 30, timeExitSec: 120, maxDailyLossUsd: 200,\n cooldownAfterLossSec: 30, cooldownAfterExitSec: 15, dryRun: true,\n };\n const rotator = createMarketRotator(() => defaultCfg);\n const markets = await rotator.refresh();\n if (markets.length === 0) return 'No active 15-min crypto markets.';\n let out = `**Active Markets (${markets.length}):**\\n`;\n for (const m of markets) {\n const secsLeft = ((m.expiresAt - Date.now()) / 1000).toFixed(0);\n out += ` ${m.asset}: UP ${m.upPrice.toFixed(2)} / DOWN ${m.downPrice.toFixed(2)} -- ${secsLeft}s left\\n`;\n }\n return out;\n }\n\n const markets = engine.getMarkets();\n if (markets.length === 0) return 'No active markets.';\n let out = `**Active Markets (${markets.length}):**\\n`;\n for (const m of markets) {\n const secsLeft = ((m.expiresAt - Date.now()) / 1000).toFixed(0);\n out += ` ${m.asset}: UP ${m.upPrice.toFixed(2)} / DOWN ${m.downPrice.toFixed(2)} -- ${secsLeft}s left\\n`;\n }\n return out;\n }\n\n case 'config': {\n if (!engine) return 'Not running.';\n\n const updates: Record\u003cstring, any> = {};\n const pairs: Array\u003c[RegExp, string, (v: string) => any]> = [\n [/--tp\\s+(\\d+)/, 'takeProfitPct', Number],\n [/--sl\\s+(\\d+)/, 'stopLossPct', Number],\n [/--size\\s+(\\d+)/, 'defaultSizeUsd', Number],\n [/--max-pos\\s+(\\d+)/, 'maxConcurrentPositions', Number],\n [/--max-loss\\s+(\\d+)/, 'maxDailyLossUsd', Number],\n [/--trailing\\s+(\\d+)/, 'trailingStopPct', Number],\n ];\n\n // Special: --windows 5,10,30\n const winMatch = args.match(/--windows\\s+([\\d,]+)/);\n if (winMatch) {\n updates.windows = winMatch[1].split(',').map(Number).filter((n) => n > 0);\n }\n\n for (const [re, key, transform] of pairs) {\n const m = args.match(re);\n if (m) updates[key] = transform(m[1]);\n }\n\n if (Object.keys(updates).length === 0) {\n const c = engine.getConfig();\n return [\n '**Current Config:**',\n `Size: ${c.defaultSizeUsd} | Max Pos: ${c.maxConcurrentPositions} | Max Loss: ${c.maxDailyLossUsd}`,\n `TP: ${c.takeProfitPct}% | SL: ${c.stopLossPct}%`,\n `Trailing: ${c.trailingStopPct}% (activates at +${c.trailingActivationPct}%)`,\n `Windows: ${c.windows.join(', ')}s`,\n `Min spot move: ${c.minSpotMovePct}% | Max poly stale: ${c.maxPolyFreshnessSec}s`,\n '',\n 'Set: `/div config --tp 20 --sl 30 --windows 5,10,30`',\n ].join('\\n');\n }\n\n engine.updateConfig(updates);\n return `Updated: ${Object.entries(updates).map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(',') : v}`).join(', ')}`;\n }\n\n default:\n return [\n '**Divergence Trading -- Spot vs Poly Price Lag**',\n '',\n '**Start/Stop:**',\n ' `/div start [BTC,ETH] [--size 20] [--dry-run]`',\n ' `/div stop`',\n '',\n '**Monitor:**',\n ' `/div status` -- Stats, open positions, signal counts',\n ' `/div positions` -- Recent closed trades with strategy tags',\n ' `/div markets` -- Active 15-min markets',\n '',\n '**Configure:**',\n ' `/div config [--tp N] [--sl N] [--size N] [--windows 5,10,30]`',\n '',\n '**Strategy tags:** `BTC_DOWN_s12-14_w15` = 0.12-0.14% spot drop over 15s window',\n ].join('\\n');\n }\n}\n\n// ── Skill Registration ──────────────────────────────────────────────────────\n\nexport default {\n name: 'divergence',\n description: 'Spot vs Polymarket divergence trading on 15-minute crypto markets',\n commands: ['/divergence', '/div'],\n handle: execute,\n};\n","content_type":"text/typescript; charset=utf-8","language":"typescript","size":10939,"content_sha256":"25b6230c6e17216003384be2db9409b60207077ca470be2cbd7841546da2909f"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Divergence Trading - Spot vs Poly Price Lag","type":"text"}]},{"type":"paragraph","content":[{"text":"Detects when Binance spot prices move but Polymarket 15-minute binary markets haven't caught up yet. Buys the lagging side and sells when it corrects.","type":"text"}]},{"type":"paragraph","content":[{"text":"Strategy tags match the CLAUDE.md encoding: ","type":"text"},{"text":"BTC_DOWN_s12-14_w15","type":"text","marks":[{"type":"code_inline"}]},{"text":" (0.12-0.14% move in 15s window).","type":"text"}]},{"type":"paragraph","content":[{"text":"Starts in ","type":"text"},{"text":"dry-run mode","type":"text","marks":[{"type":"strong"}]},{"text":" by default (no real orders).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/div start # Dry-run on BTC,ETH,SOL,XRP\n/div start BTC,ETH --size 30 # Specific assets + size\n/div status # Stats, open positions\n/div stop # Stop and show summary","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Commands","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Start / Stop","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/div start [ASSETS] [--size N] [--dry-run]\n/div stop","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Monitor","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/div status Stats + open positions + round info\n/div positions Last 20 closed trades with strategy tags\n/div markets Active 15-min markets from Gamma API","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Configure","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/div config Show current config\n/div config --tp 20 --sl 30 Set TP/SL %\n/div config --size 50 Set trade size\n/div config --windows 5,10,30 Set detection windows","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Detection Algorithm","type":"text"}]},{"type":"paragraph","content":[{"text":"For each spot tick, across all configured windows (5s, 10s, 15s, 30s, 60s, 90s, 120s):","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Look up spot price N seconds ago via binary search","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Calculate ","type":"text"},{"text":"spotMovePct = (now - then) / then * 100","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If move >= threshold AND poly is fresh (\u003c 5s stale):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate signal with strategy tag: ","type":"text"},{"text":"{ASSET}_{DIR}_s{bucket}_w{window}","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"e.g., ","type":"text"},{"text":"BTC_DOWN_s12-14_w15","type":"text","marks":[{"type":"code_inline"}]},{"text":" = 0.12-0.14% spot drop over 15s","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Threshold Buckets","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bucket","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Spot Move Range","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s08-10","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.08% - 0.10%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s10-12","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.10% - 0.12%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s12-14","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.12% - 0.14%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s14-16","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.14% - 0.16%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s16-20","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.16% - 0.20%","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"s20+","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"0.20%+","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Exit Logic","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Force exit","type":"text","marks":[{"type":"strong"}]},{"text":" — \u003c 30s before market expiry","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Take profit","type":"text","marks":[{"type":"strong"}]},{"text":" — PnL >= 15% (configurable)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop loss","type":"text","marks":[{"type":"strong"}]},{"text":" — PnL \u003c= -25% (configurable)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Trailing stop","type":"text","marks":[{"type":"strong"}]},{"text":" — Activated at +10%, exits if drops 8% from HWM","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Time exit","type":"text","marks":[{"type":"strong"}]},{"text":" — \u003c 2min before expiry","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"divergence","gates":{"envs":["POLY_PRIVATE_KEY","POLY_FUNDER_ADDRESS","POLY_API_KEY","POLY_API_SECRET","POLY_API_PASSPHRASE"]},"author":"@skillopedia","source":{"stars":306,"repo_name":"cloddsbot","origin_url":"https://github.com/alsk1992/cloddsbot/blob/HEAD/src/skills/bundled/divergence/SKILL.md","repo_owner":"alsk1992","body_sha256":"2cfb2504a8079452679c9ed6ca0656f03d5756aa5a8fad5606596ae65496b381","cluster_key":"c4b94acc4b647d70e9fa59e68d948fc3018536bd38dd4237ad24b99d88ffa103","clean_bundle":{"format":"clean-skill-bundle-v1","source":"alsk1992/cloddsbot/src/skills/bundled/divergence/SKILL.md","attachments":[{"id":"d914faf3-928e-5191-81b7-c30d09290e31","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d914faf3-928e-5191-81b7-c30d09290e31/attachment.ts","path":"index.ts","size":10939,"sha256":"25b6230c6e17216003384be2db9409b60207077ca470be2cbd7841546da2909f","contentType":"text/typescript; charset=utf-8"}],"bundle_sha256":"77ed3bd352770fc722d2d924ad8a5f747070feef85f18375d389b76c49c2af18","attachment_count":1,"text_attachments":1,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"src/skills/bundled/divergence/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"finance-legal-compliance","category_label":"Finance"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"finance-legal-compliance","commands":["/divergence","/div"],"import_tag":"clean-skills-v1","description":"Spot vs Polymarket divergence trading on 15-minute crypto markets"}},"renderedAt":1782986933208}

Divergence Trading - Spot vs Poly Price Lag Detects when Binance spot prices move but Polymarket 15-minute binary markets haven't caught up yet. Buys the lagging side and sells when it corrects. Strategy tags match the CLAUDE.md encoding: (0.12-0.14% move in 15s window). Starts in dry-run mode by default (no real orders). Quick Start Commands Start / Stop Monitor Configure Detection Algorithm For each spot tick, across all configured windows (5s, 10s, 15s, 30s, 60s, 90s, 120s): 1. Look up spot price N seconds ago via binary search 2. Calculate 3. If move = threshold AND poly is fresh (< 5s st…