import { CONFIG } from "./config.js"; import { fetchKlines, fetchLastPrice } from "./data/binance.js"; import { fetchChainlinkBtcUsd } from "./data/chainlink.js"; import { startChainlinkPriceStream } from "./data/chainlinkWs.js"; import { startPolymarketChainlinkPriceStream } from "./data/polymarketLiveWs.js"; import { fetchMarketBySlug, fetchLiveEventsBySeriesId, flattenEventMarkets, pickLatestLiveMarket, fetchClobPrice, fetchOrderBook, summarizeOrderBook } from "./data/polymarket.js"; import { computeSessionVwap, computeVwapSeries } from "./indicators/vwap.js"; import { computeRsi, sma, slopeLast } from "./indicators/rsi.js"; import { computeMacd } from "./indicators/macd.js"; import { computeHeikenAshi, countConsecutive } from "./indicators/heikenAshi.js"; import { detectRegime } from "./engines/regime.js"; import { scoreDirection, applyTimeAwareness } from "./engines/probability.js"; import { computeEdge, decide } from "./engines/edge.js"; import { appendCsvRow, formatNumber, formatPct, getCandleWindowTiming, sleep } from "./utils.js"; import { startBinanceTradeStream } from "./data/binanceWs.js"; import fs from "node:fs"; import path from "node:path"; import readline from "node:readline"; function countVwapCrosses(closes, vwapSeries, lookback) { if (closes.length < lookback || vwapSeries.length < lookback) return null; let crosses = 0; for (let i = closes.length - lookback + 1; i < closes.length; i += 1) { const prev = closes[i - 1] - vwapSeries[i - 1]; const cur = closes[i] - vwapSeries[i]; if (prev === 0) continue; if ((prev > 0 && cur < 0) || (prev < 0 && cur > 0)) crosses += 1; } return crosses; } function fmtTimeLeft(mins) { const totalSeconds = Math.max(0, Math.floor(mins * 60)); const m = Math.floor(totalSeconds / 60); const s = totalSeconds % 60; return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`; } const ANSI = { reset: "\x1b[0m", red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", lightRed: "\x1b[91m", gray: "\x1b[90m", white: "\x1b[97m", dim: "\x1b[2m" }; function screenWidth() { const w = Number(process.stdout?.columns); return Number.isFinite(w) && w >= 40 ? w : 80; } function sepLine(ch = "─") { const w = screenWidth(); return `${ANSI.white}${ch.repeat(w)}${ANSI.reset}`; } function renderScreen(text) { try { readline.cursorTo(process.stdout, 0, 0); readline.clearScreenDown(process.stdout); } catch { // ignore } process.stdout.write(text); } function stripAnsi(s) { return String(s).replace(/\x1b\[[0-9;]*m/g, ""); } function padLabel(label, width) { const visible = stripAnsi(label).length; if (visible >= width) return label; return label + " ".repeat(width - visible); } function centerText(text, width) { const visible = stripAnsi(text).length; if (visible >= width) return text; const left = Math.floor((width - visible) / 2); const right = width - visible - left; return " ".repeat(left) + text + " ".repeat(right); } const LABEL_W = 16; function kv(label, value) { const l = padLabel(String(label), LABEL_W); return `${l}${value}`; } function section(title) { return `${ANSI.white}${title}${ANSI.reset}`; } function colorPriceLine({ label, price, prevPrice, decimals = 0, prefix = "" }) { if (price === null || price === undefined) { return `${label}: ${ANSI.gray}-${ANSI.reset}`; } const p = Number(price); const prev = prevPrice === null || prevPrice === undefined ? null : Number(prevPrice); let color = ANSI.reset; let arrow = ""; if (prev !== null && Number.isFinite(prev) && Number.isFinite(p) && p !== prev) { if (p > prev) { color = ANSI.green; arrow = " ↑"; } else { color = ANSI.red; arrow = " ↓"; } } const formatted = `${prefix}${formatNumber(p, decimals)}`; return `${label}: ${color}${formatted}${arrow}${ANSI.reset}`; } function formatSignedDelta(delta, base) { if (delta === null || base === null || base === 0) return `${ANSI.gray}-${ANSI.reset}`; const sign = delta > 0 ? "+" : delta < 0 ? "-" : ""; const pct = (Math.abs(delta) / Math.abs(base)) * 100; return `${sign}$${Math.abs(delta).toFixed(2)}, ${sign}${pct.toFixed(2)}%`; } function colorByNarrative(text, narrative) { if (narrative === "LONG") return `${ANSI.green}${text}${ANSI.reset}`; if (narrative === "SHORT") return `${ANSI.red}${text}${ANSI.reset}`; return `${ANSI.gray}${text}${ANSI.reset}`; } function formatNarrativeValue(label, value, narrative) { return `${label}: ${colorByNarrative(value, narrative)}`; } function narrativeFromSign(x) { if (x === null || x === undefined || !Number.isFinite(Number(x)) || Number(x) === 0) return "NEUTRAL"; return Number(x) > 0 ? "LONG" : "SHORT"; } function narrativeFromRsi(rsi) { if (rsi === null || rsi === undefined || !Number.isFinite(Number(rsi))) return "NEUTRAL"; const v = Number(rsi); if (v >= 55) return "LONG"; if (v <= 45) return "SHORT"; return "NEUTRAL"; } function narrativeFromSlope(slope) { if (slope === null || slope === undefined || !Number.isFinite(Number(slope)) || Number(slope) === 0) return "NEUTRAL"; return Number(slope) > 0 ? "LONG" : "SHORT"; } function formatProbPct(p, digits = 0) { if (p === null || p === undefined || !Number.isFinite(Number(p))) return "-"; return `${(Number(p) * 100).toFixed(digits)}%`; } function fmtEtTime(now = new Date()) { try { return new Intl.DateTimeFormat("en-US", { timeZone: "America/New_York", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }).format(now); } catch { return "-"; } } function getBtcSession(now = new Date()) { const h = now.getUTCHours(); const inAsia = h >= 0 && h < 8; const inEurope = h >= 7 && h < 16; const inUs = h >= 13 && h < 22; if (inEurope && inUs) return "Europe/US overlap"; if (inAsia && inEurope) return "Asia/Europe overlap"; if (inAsia) return "Asia"; if (inEurope) return "Europe"; if (inUs) return "US"; return "Off-hours"; } function parsePriceToBeat(market) { const text = String(market?.question ?? market?.title ?? ""); if (!text) return null; const m = text.match(/price\s*to\s*beat[^\d$]*\$?\s*([0-9][0-9,]*(?:\.[0-9]+)?)/i); if (!m) return null; const raw = m[1].replace(/,/g, ""); const n = Number(raw); return Number.isFinite(n) ? n : null; } const dumpedMarkets = new Set(); function safeFileSlug(x) { return String(x ?? "") .toLowerCase() .replace(/[^a-z0-9_-]+/g, "-") .replace(/-+/g, "-") .replace(/(^-|-$)/g, "") .slice(0, 120); } function extractNumericFromMarket(market) { const directKeys = [ "priceToBeat", "price_to_beat", "strikePrice", "strike_price", "strike", "threshold", "thresholdPrice", "threshold_price", "targetPrice", "target_price", "referencePrice", "reference_price" ]; for (const k of directKeys) { const v = market?.[k]; const n = typeof v === "string" ? Number(v) : typeof v === "number" ? v : NaN; if (Number.isFinite(n)) return n; } const seen = new Set(); const stack = [{ obj: market, depth: 0 }]; while (stack.length) { const { obj, depth } = stack.pop(); if (!obj || typeof obj !== "object") continue; if (seen.has(obj) || depth > 6) continue; seen.add(obj); const entries = Array.isArray(obj) ? obj.entries() : Object.entries(obj); for (const [key, value] of entries) { const k = String(key).toLowerCase(); if (value && typeof value === "object") { stack.push({ obj: value, depth: depth + 1 }); continue; } if (!/(price|strike|threshold|target|beat)/i.test(k)) continue; const n = typeof value === "string" ? Number(value) : typeof value === "number" ? value : NaN; if (!Number.isFinite(n)) continue; if (n > 1000 && n < 2_000_000) return n; } } return null; } function priceToBeatFromPolymarketMarket(market) { const n = extractNumericFromMarket(market); if (n !== null) return n; return parsePriceToBeat(market); } const marketCache = { market: null, fetchedAtMs: 0 }; async function resolveCurrentBtc15mMarket() { if (CONFIG.polymarket.marketSlug) { return await fetchMarketBySlug(CONFIG.polymarket.marketSlug); } if (!CONFIG.polymarket.autoSelectLatest) return null; const now = Date.now(); if (marketCache.market && now - marketCache.fetchedAtMs < CONFIG.pollIntervalMs) { return marketCache.market; } const events = await fetchLiveEventsBySeriesId({ seriesId: CONFIG.polymarket.seriesId, limit: 25 }); const markets = flattenEventMarkets(events); const picked = pickLatestLiveMarket(markets); marketCache.market = picked; marketCache.fetchedAtMs = now; return picked; } async function fetchPolymarketSnapshot() { const market = await resolveCurrentBtc15mMarket(); if (!market) return { ok: false, reason: "market_not_found" }; const outcomes = Array.isArray(market.outcomes) ? market.outcomes : (typeof market.outcomes === "string" ? JSON.parse(market.outcomes) : []); const outcomePrices = Array.isArray(market.outcomePrices) ? market.outcomePrices : (typeof market.outcomePrices === "string" ? JSON.parse(market.outcomePrices) : []); const clobTokenIds = Array.isArray(market.clobTokenIds) ? market.clobTokenIds : (typeof market.clobTokenIds === "string" ? JSON.parse(market.clobTokenIds) : []); let upTokenId = null; let downTokenId = null; for (let i = 0; i < outcomes.length; i += 1) { const label = String(outcomes[i]); const tokenId = clobTokenIds[i] ? String(clobTokenIds[i]) : null; if (!tokenId) continue; if (label.toLowerCase() === CONFIG.polymarket.upOutcomeLabel.toLowerCase()) upTokenId = tokenId; if (label.toLowerCase() === CONFIG.polymarket.downOutcomeLabel.toLowerCase()) downTokenId = tokenId; } const upIndex = outcomes.findIndex((x) => String(x).toLowerCase() === CONFIG.polymarket.upOutcomeLabel.toLowerCase()); const downIndex = outcomes.findIndex((x) => String(x).toLowerCase() === CONFIG.polymarket.downOutcomeLabel.toLowerCase()); const gammaYes = upIndex >= 0 ? Number(outcomePrices[upIndex]) : null; const gammaNo = downIndex >= 0 ? Number(outcomePrices[downIndex]) : null; if (!upTokenId || !downTokenId) { return { ok: false, reason: "missing_token_ids", market, outcomes, clobTokenIds, outcomePrices }; } let upBuy = null; let downBuy = null; let upBookSummary = { bestBid: null, bestAsk: null, spread: null, bidLiquidity: null, askLiquidity: null }; let downBookSummary = { bestBid: null, bestAsk: null, spread: null, bidLiquidity: null, askLiquidity: null }; try { const [yesBuy, noBuy, upBook, downBook] = await Promise.all([ fetchClobPrice({ tokenId: upTokenId, side: "buy" }), fetchClobPrice({ tokenId: downTokenId, side: "buy" }), fetchOrderBook({ tokenId: upTokenId }), fetchOrderBook({ tokenId: downTokenId }) ]); upBuy = yesBuy; downBuy = noBuy; upBookSummary = summarizeOrderBook(upBook); downBookSummary = summarizeOrderBook(downBook); } catch { upBuy = null; downBuy = null; upBookSummary = { bestBid: Number(market.bestBid) || null, bestAsk: Number(market.bestAsk) || null, spread: Number(market.spread) || null, bidLiquidity: null, askLiquidity: null }; downBookSummary = { bestBid: null, bestAsk: null, spread: Number(market.spread) || null, bidLiquidity: null, askLiquidity: null }; } return { ok: true, market, tokens: { upTokenId, downTokenId }, prices: { up: upBuy ?? gammaYes, down: downBuy ?? gammaNo }, orderbook: { up: upBookSummary, down: downBookSummary } }; } async function main() { const binanceStream = startBinanceTradeStream({ symbol: CONFIG.symbol }); const polymarketLiveStream = startPolymarketChainlinkPriceStream({}); const chainlinkStream = startChainlinkPriceStream({}); let prevSpotPrice = null; let prevCurrentPrice = null; let priceToBeatState = { slug: null, value: null, setAtMs: null }; const header = [ "timestamp", "entry_minute", "time_left_min", "regime", "signal", "model_up", "model_down", "mkt_up", "mkt_down", "edge_up", "edge_down", "recommendation" ]; while (true) { const timing = getCandleWindowTiming(CONFIG.candleWindowMinutes); const wsTick = binanceStream.getLast(); const wsPrice = wsTick?.price ?? null; const polymarketWsTick = polymarketLiveStream.getLast(); const polymarketWsPrice = polymarketWsTick?.price ?? null; const chainlinkWsTick = chainlinkStream.getLast(); const chainlinkWsPrice = chainlinkWsTick?.price ?? null; try { const chainlinkPromise = polymarketWsPrice !== null ? Promise.resolve({ price: polymarketWsPrice, updatedAt: polymarketWsTick?.updatedAt ?? null, source: "polymarket_ws" }) : chainlinkWsPrice !== null ? Promise.resolve({ price: chainlinkWsPrice, updatedAt: chainlinkWsTick?.updatedAt ?? null, source: "chainlink_ws" }) : fetchChainlinkBtcUsd(); const [klines1m, klines5m, lastPrice, chainlink, poly] = await Promise.all([ fetchKlines({ interval: "1m", limit: 240 }), fetchKlines({ interval: "5m", limit: 200 }), fetchLastPrice(), chainlinkPromise, fetchPolymarketSnapshot() ]); const settlementMs = poly.ok && poly.market?.endDate ? new Date(poly.market.endDate).getTime() : null; const settlementLeftMin = settlementMs ? (settlementMs - Date.now()) / 60_000 : null; const timeLeftMin = settlementLeftMin ?? timing.remainingMinutes; const candles = klines1m; const closes = candles.map((c) => c.close); const vwap = computeSessionVwap(candles); const vwapSeries = computeVwapSeries(candles); const vwapNow = vwapSeries[vwapSeries.length - 1]; const lookback = CONFIG.vwapSlopeLookbackMinutes; const vwapSlope = vwapSeries.length >= lookback ? (vwapNow - vwapSeries[vwapSeries.length - lookback]) / lookback : null; const vwapDist = vwapNow ? (lastPrice - vwapNow) / vwapNow : null; const rsiNow = computeRsi(closes, CONFIG.rsiPeriod); const rsiSeries = []; for (let i = 0; i < closes.length; i += 1) { const sub = closes.slice(0, i + 1); const r = computeRsi(sub, CONFIG.rsiPeriod); if (r !== null) rsiSeries.push(r); } const rsiMa = sma(rsiSeries, CONFIG.rsiMaPeriod); const rsiSlope = slopeLast(rsiSeries, 3); const macd = computeMacd(closes, CONFIG.macdFast, CONFIG.macdSlow, CONFIG.macdSignal); const ha = computeHeikenAshi(candles); const consec = countConsecutive(ha); const vwapCrossCount = countVwapCrosses(closes, vwapSeries, 20); const volumeRecent = candles.slice(-20).reduce((a, c) => a + c.volume, 0); const volumeAvg = candles.slice(-120).reduce((a, c) => a + c.volume, 0) / 6; const failedVwapReclaim = vwapNow !== null && vwapSeries.length >= 3 ? closes[closes.length - 1] < vwapNow && closes[closes.length - 2] > vwapSeries[vwapSeries.length - 2] : false; const regimeInfo = detectRegime({ price: lastPrice, vwap: vwapNow, vwapSlope, vwapCrossCount, volumeRecent, volumeAvg }); const scored = scoreDirection({ price: lastPrice, vwap: vwapNow, vwapSlope, rsi: rsiNow, rsiSlope, macd, heikenColor: consec.color, heikenCount: consec.count, failedVwapReclaim }); const timeAware = applyTimeAwareness(scored.rawUp, timeLeftMin, CONFIG.candleWindowMinutes); const marketUp = poly.ok ? poly.prices.up : null; const marketDown = poly.ok ? poly.prices.down : null; const edge = computeEdge({ modelUp: timeAware.adjustedUp, modelDown: timeAware.adjustedDown, marketYes: marketUp, marketNo: marketDown }); const rec = decide({ remainingMinutes: timeLeftMin, edgeUp: edge.edgeUp, edgeDown: edge.edgeDown, modelUp: timeAware.adjustedUp, modelDown: timeAware.adjustedDown }); const vwapSlopeLabel = vwapSlope === null ? "-" : vwapSlope > 0 ? "UP" : vwapSlope < 0 ? "DOWN" : "FLAT"; const macdLabel = macd === null ? "-" : macd.hist < 0 ? (macd.histDelta !== null && macd.histDelta < 0 ? "bearish (expanding)" : "bearish") : (macd.histDelta !== null && macd.histDelta > 0 ? "bullish (expanding)" : "bullish"); const lastCandle = klines1m.length ? klines1m[klines1m.length - 1] : null; const lastClose = lastCandle?.close ?? null; const close1mAgo = klines1m.length >= 2 ? klines1m[klines1m.length - 2]?.close ?? null : null; const close3mAgo = klines1m.length >= 4 ? klines1m[klines1m.length - 4]?.close ?? null : null; const delta1m = lastClose !== null && close1mAgo !== null ? lastClose - close1mAgo : null; const delta3m = lastClose !== null && close3mAgo !== null ? lastClose - close3mAgo : null; const haNarrative = (consec.color ?? "").toLowerCase() === "green" ? "LONG" : (consec.color ?? "").toLowerCase() === "red" ? "SHORT" : "NEUTRAL"; const rsiNarrative = narrativeFromSlope(rsiSlope); const macdNarrative = narrativeFromSign(macd?.hist ?? null); const vwapNarrative = narrativeFromSign(vwapDist); const pLong = timeAware?.adjustedUp ?? null; const pShort = timeAware?.adjustedDown ?? null; const predictNarrative = (pLong !== null && pShort !== null && Number.isFinite(pLong) && Number.isFinite(pShort)) ? (pLong > pShort ? "LONG" : pShort > pLong ? "SHORT" : "NEUTRAL") : "NEUTRAL"; const predictValue = `${ANSI.green}LONG${ANSI.reset} ${ANSI.green}${formatProbPct(pLong, 0)}${ANSI.reset} / ${ANSI.red}SHORT${ANSI.reset} ${ANSI.red}${formatProbPct(pShort, 0)}${ANSI.reset}`; const predictLine = `Predict: ${predictValue}`; const marketUpStr = `${marketUp ?? "-"}${marketUp === null || marketUp === undefined ? "" : "¢"}`; const marketDownStr = `${marketDown ?? "-"}${marketDown === null || marketDown === undefined ? "" : "¢"}`; const polyHeaderValue = `${ANSI.green}↑ UP${ANSI.reset} ${marketUpStr} | ${ANSI.red}↓ DOWN${ANSI.reset} ${marketDownStr}`; const heikenValue = `${consec.color ?? "-"} x${consec.count}`; const heikenLine = formatNarrativeValue("Heiken Ashi", heikenValue, haNarrative); const rsiArrow = rsiSlope !== null && rsiSlope < 0 ? "↓" : rsiSlope !== null && rsiSlope > 0 ? "↑" : "-"; const rsiValue = `${formatNumber(rsiNow, 1)} ${rsiArrow}`; const rsiLine = formatNarrativeValue("RSI", rsiValue, rsiNarrative); const macdLine = formatNarrativeValue("MACD", macdLabel, macdNarrative); const delta1Narrative = narrativeFromSign(delta1m); const delta3Narrative = narrativeFromSign(delta3m); const deltaValue = `${colorByNarrative(formatSignedDelta(delta1m, lastClose), delta1Narrative)} | ${colorByNarrative(formatSignedDelta(delta3m, lastClose), delta3Narrative)}`; const deltaLine = `Delta 1/3Min: ${deltaValue}`; const vwapValue = `${formatNumber(vwapNow, 0)} (${formatPct(vwapDist, 2)}) | slope: ${vwapSlopeLabel}`; const vwapLine = formatNarrativeValue("VWAP", vwapValue, vwapNarrative); const signal = rec.action === "ENTER" ? (rec.side === "UP" ? "BUY UP" : "BUY DOWN") : "NO TRADE"; const actionLine = rec.action === "ENTER" ? `${rec.action} NOW (${rec.phase} ENTRY)` : `NO TRADE (${rec.phase})`; const spreadUp = poly.ok ? poly.orderbook.up.spread : null; const spreadDown = poly.ok ? poly.orderbook.down.spread : null; const spread = spreadUp !== null && spreadDown !== null ? Math.max(spreadUp, spreadDown) : (spreadUp ?? spreadDown); const liquidity = poly.ok ? (Number(poly.market?.liquidityNum) || Number(poly.market?.liquidity) || null) : null; const spotPrice = wsPrice ?? lastPrice; const currentPrice = chainlink?.price ?? null; const marketSlug = poly.ok ? String(poly.market?.slug ?? "") : ""; const marketStartMs = poly.ok && poly.market?.eventStartTime ? new Date(poly.market.eventStartTime).getTime() : null; if (marketSlug && priceToBeatState.slug !== marketSlug) { priceToBeatState = { slug: marketSlug, value: null, setAtMs: null }; } if (priceToBeatState.slug && priceToBeatState.value === null && currentPrice !== null) { const nowMs = Date.now(); const okToLatch = marketStartMs === null ? true : nowMs >= marketStartMs; if (okToLatch) { priceToBeatState = { slug: priceToBeatState.slug, value: Number(currentPrice), setAtMs: nowMs }; } } const priceToBeat = priceToBeatState.slug === marketSlug ? priceToBeatState.value : null; const currentPriceBaseLine = colorPriceLine({ label: "CURRENT PRICE", price: currentPrice, prevPrice: prevCurrentPrice, decimals: 2, prefix: "$" }); const ptbDelta = (currentPrice !== null && priceToBeat !== null && Number.isFinite(currentPrice) && Number.isFinite(priceToBeat)) ? currentPrice - priceToBeat : null; const ptbDeltaColor = ptbDelta === null ? ANSI.gray : ptbDelta > 0 ? ANSI.green : ptbDelta < 0 ? ANSI.red : ANSI.gray; const ptbDeltaText = ptbDelta === null ? `${ANSI.gray}-${ANSI.reset}` : `${ptbDeltaColor}${ptbDelta > 0 ? "+" : ptbDelta < 0 ? "-" : ""}$${Math.abs(ptbDelta).toFixed(2)}${ANSI.reset}`; const currentPriceValue = currentPriceBaseLine.split(": ")[1] ?? currentPriceBaseLine; const currentPriceLine = kv("CURRENT PRICE:", `${currentPriceValue} (${ptbDeltaText})`); if (poly.ok && poly.market && priceToBeatState.value === null) { const slug = safeFileSlug(poly.market.slug || poly.market.id || "market"); if (slug && !dumpedMarkets.has(slug)) { dumpedMarkets.add(slug); try { fs.mkdirSync("./logs", { recursive: true }); fs.writeFileSync(path.join("./logs", `polymarket_market_${slug}.json`), JSON.stringify(poly.market, null, 2), "utf8"); } catch { // ignore } } } const binanceSpotBaseLine = colorPriceLine({ label: "BTC (Binance)", price: spotPrice, prevPrice: prevSpotPrice, decimals: 0, prefix: "$" }); const diffLine = (spotPrice !== null && currentPrice !== null && Number.isFinite(spotPrice) && Number.isFinite(currentPrice) && currentPrice !== 0) ? (() => { const diffUsd = spotPrice - currentPrice; const diffPct = (diffUsd / currentPrice) * 100; const sign = diffUsd > 0 ? "+" : diffUsd < 0 ? "-" : ""; return ` (${sign}$${Math.abs(diffUsd).toFixed(2)}, ${sign}${Math.abs(diffPct).toFixed(2)}%)`; })() : ""; const binanceSpotLine = `${binanceSpotBaseLine}${diffLine}`; const binanceSpotValue = binanceSpotLine.split(": ")[1] ?? binanceSpotLine; const binanceSpotKvLine = kv("BTC (Binance):", binanceSpotValue); const titleLine = poly.ok ? `${poly.market?.question ?? "-"}` : "-"; const marketLine = kv("Market:", poly.ok ? (poly.market?.slug ?? "-") : "-"); const timeColor = timeLeftMin >= 10 && timeLeftMin <= 15 ? ANSI.green : timeLeftMin >= 5 && timeLeftMin < 10 ? ANSI.yellow : timeLeftMin >= 0 && timeLeftMin < 5 ? ANSI.red : ANSI.reset; const timeLeftLine = `⏱ Time left: ${timeColor}${fmtTimeLeft(timeLeftMin)}${ANSI.reset}`; const polyTimeLeftColor = settlementLeftMin !== null ? (settlementLeftMin >= 10 && settlementLeftMin <= 15 ? ANSI.green : settlementLeftMin >= 5 && settlementLeftMin < 10 ? ANSI.yellow : settlementLeftMin >= 0 && settlementLeftMin < 5 ? ANSI.red : ANSI.reset) : ANSI.reset; const lines = [ titleLine, marketLine, kv("Time left:", `${timeColor}${fmtTimeLeft(timeLeftMin)}${ANSI.reset}`), "", sepLine(), "", kv("TA Predict:", predictValue), kv("Heiken Ashi:", heikenLine.split(": ")[1] ?? heikenLine), kv("RSI:", rsiLine.split(": ")[1] ?? rsiLine), kv("MACD:", macdLine.split(": ")[1] ?? macdLine), kv("Delta 1/3:", deltaLine.split(": ")[1] ?? deltaLine), kv("VWAP:", vwapLine.split(": ")[1] ?? vwapLine), "", sepLine(), "", kv("POLYMARKET:", polyHeaderValue), liquidity !== null ? kv("Liquidity:", formatNumber(liquidity, 0)) : null, settlementLeftMin !== null ? kv("Time left:", `${polyTimeLeftColor}${fmtTimeLeft(settlementLeftMin)}${ANSI.reset}`) : null, priceToBeat !== null ? kv("PRICE TO BEAT: ", `$${formatNumber(priceToBeat, 0)}`) : kv("PRICE TO BEAT: ", `${ANSI.gray}-${ANSI.reset}`), currentPriceLine, "", sepLine(), "", binanceSpotKvLine, "", sepLine(), "", kv("ET | Session:", `${ANSI.white}${fmtEtTime(new Date())}${ANSI.reset} | ${ANSI.white}${getBtcSession(new Date())}${ANSI.reset}`), "", sepLine(), centerText(`${ANSI.dim}${ANSI.gray}created by @krajekis${ANSI.reset}`, screenWidth()) ].filter((x) => x !== null); renderScreen(lines.join("\n") + "\n"); prevSpotPrice = spotPrice ?? prevSpotPrice; prevCurrentPrice = currentPrice ?? prevCurrentPrice; appendCsvRow("./logs/signals.csv", header, [ new Date().toISOString(), timing.elapsedMinutes.toFixed(3), timeLeftMin.toFixed(3), regimeInfo.regime, signal, timeAware.adjustedUp, timeAware.adjustedDown, marketUp, marketDown, edge.edgeUp, edge.edgeDown, rec.action === "ENTER" ? `${rec.side}:${rec.phase}:${rec.strength}` : "NO_TRADE" ]); } catch (err) { console.log("────────────────────────────"); console.log(`Error: ${err?.message ?? String(err)}`); console.log("────────────────────────────"); } await sleep(CONFIG.pollIntervalMs); } } main();