// Data, hooks, icons — shared across all pages. const { useState, useEffect, useRef, useMemo, useCallback } = React; // ============================================================ // ICONS — Lucide-style, hand-drawn paths // ============================================================ const Icon = ({ d, size = 18, stroke = 1.6, fill = "none", style }) => ( {d} ); const IconHome = (p) => } />; const IconSignal = (p) => } />; const IconChart = (p) => } />; const IconShield = (p) => } />; const IconClock = (p) => } />; const IconBell = (p) => } />; const IconList = (p) => } />; const IconTerm = (p) => } />; const IconGear = (p) => } />; const IconSearch = (p) => } />; const IconPlus = (p) => } />; const IconPause = (p) => } />; const IconPlay = (p) => } />; const IconEdit = (p) => } />; const IconStop = (p) => } />; const IconChevron = (p) => } />; const IconUpgrade = (p) => } />; const IconCheck = (p) => } />; const IconX = (p) => } />; const IconCopy = (p) => } />; const IconEye = (p) => } />; const IconEyeOff = (p) => } />; const IconExternal= (p) => } />; const IconFilter = (p) => } />; const IconRefresh = (p) => } />; const IconDown = (p) => } />; const IconAlert = (p) => } />; const IconKey = (p) => } />; const IconBolt = (p) => } />; const IconRocket = (p) => } />; const IconSwap = (p) => } />; const IconLayers = (p) => } />; const IconLogo = ({ size = 22 }) => ( ); // Asset glyphs const IconBinance = ({ size = 18 }) => ( ); const IconTV = ({ size = 18 }) => ( ); const IconTelegram = ({ size = 18 }) => ( ); // Kraken — stylised tentacle/spiral mark in brand purple const IconKraken = ({ size = 18 }) => ( ); // ============================================================ // EXCHANGES — config per supported venue (spec v3) // ============================================================ const EXCHANGES = { binance: { id: "binance", name: "Binance", full: "Binance.com (international)", Icon: IconBinance, color: "#F0B90B", ccxtId: "binance", nativeSymbol: "BTCUSDT", fees: { maker: 0.10, taker: 0.10 }, rateLimit: "1200 weight/min · 10 orders/sec", oco: true, stopLoss: "OCO order", stopLossNote: "exchange-side · survives bot crashes", testnet: true, testnetUrl: "testnet.binance.vision", docs: "binance-docs.github.io/apidocs/spot", rttMs: 47, quirks: "Native BTCUSDT symbol · CCXT normalises to BTC/USDT", }, kraken: { id: "kraken", name: "Kraken", full: "Kraken Spot", Icon: IconKraken, color: "#5741D9", ccxtId: "kraken", nativeSymbol: "XXBTZUSD", fees: { maker: 0.16, taker: 0.26 }, rateLimit: "15 calls / 3s · counter decays 1/3s", oco: false, stopLoss: "Conditional close", stopLossNote: "attached to opening order via close param", testnet: false, testnetUrl: "no public testnet — paper-trade mode", docs: "docs.kraken.com/api", rttMs: 88, quirks: "Uses XBT internally · CCXT maps to BTC · stricter rate limit", }, }; // ============================================================ // LIVE-TICKER + PRNG + HOOKS // ============================================================ function useTicker(initial, { volatility = 0.0008, intervalMs = 1400, bias = 0 } = {}) { const [value, setValue] = useState(initial); const [dir, setDir] = useState(0); useEffect(() => { let stop = false; const tick = () => { if (stop) return; setValue((v) => { const drift = (Math.random() - 0.5 + bias) * volatility * v; const next = Math.max(0.01, v + drift); setDir(drift > 0 ? 1 : drift < 0 ? -1 : 0); return next; }); }; const id = setInterval(tick, intervalMs + Math.random() * 600); return () => { stop = true; clearInterval(id); }; }, [volatility, intervalMs, bias]); return [value, dir]; } function seedRand(seed) { let s = seed; return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; } function generateSeries(n, { seed = 1, start = 100, drift = 0.0008, vol = 0.012 } = {}) { const rand = seedRand(seed); const out = [start]; for (let i = 1; i < n; i++) { const d = (rand() - 0.5) * vol + drift; out.push(Math.max(0.01, out[i - 1] * (1 + d))); } return out; } function useCountUp(target, duration = 900) { const [val, setVal] = useState(target); const fromRef = useRef(target); useEffect(() => { const from = fromRef.current; const start = performance.now(); let raf; const step = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(from + (target - from) * eased); if (p < 1) raf = requestAnimationFrame(step); else fromRef.current = target; }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [target, duration]); return val; } // ============================================================ // SCENARIOS (Tweaks-driven) // ============================================================ const SCENARIOS = { winning: { tone: "profit", equity: 11842.30, change24h: 132.18, changePct: 1.13, todayTrades: 7, todayWin: 6, dailyLoss: 0.42, drawdown: 2.3 }, losing: { tone: "loss", equity: 9680.12, change24h: -204.50, changePct: -2.07, todayTrades: 9, todayWin: 3, dailyLoss: 2.71, drawdown: 8.4 }, volatile: { tone: "warn", equity: 10520.74, change24h: 18.24, changePct: 0.17, todayTrades: 14, todayWin: 7, dailyLoss: 1.18, drawdown: 5.6 }, }; // ============================================================ // BOT — the MVP is a single bot. Source of truth. // ============================================================ const BOT = { name: "BTC/USDT EMA Cross", pair: "BTC/USDT", activeExchange: "binance", // spec v3: single-active via ACTIVE_EXCHANGE market: "Spot", timeframe: "5m", strategy: "EMA(9/21) Cross + RSI(14) > 50", source: "TradingView Pine v5", mode: "testnet", // testnet | live | paper status: "Running", uptimeSec: 184302, // ~2d 3h region: "Dubai · Hetzner", startBalance: 10000, }; // ============================================================ // SIGNALS — TradingView webhook log // ============================================================ const SIGNALS = [ { t: -12, id: "sig-0a7c", side: "BUY", price: 64210.50, status: "executed", reason: null, latencyMs: 412 }, { t: -184, id: "sig-0a7b", side: "SELL", price: 64055.40, status: "executed", reason: null, latencyMs: 538 }, { t: -640, id: "sig-0a7a", side: "BUY", price: 63980.10, status: "executed", reason: null, latencyMs: 689 }, { t: -1020, id: "sig-0a79", side: "BUY", price: 63920.00, status: "deduplicated", reason: "Duplicate signal_id (< 60s)", latencyMs: 24 }, { t: -1810, id: "sig-0a78", side: "SELL", price: 63810.20, status: "executed", reason: null, latencyMs: 461 }, { t: -2440, id: "sig-0a77", side: "BUY", price: 63702.00, status: "rejected", reason: "Position already open", latencyMs: 18 }, { t: -3110, id: "sig-0a76", side: "SELL", price: 63812.50, status: "stopped_out", reason: "Stop loss hit @ 1.98%", latencyMs: 488 }, { t: -4290, id: "sig-0a75", side: "BUY", price: 64104.00, status: "executed", reason: null, latencyMs: 612 }, { t: -5640, id: "sig-0a74", side: "BUY", price: 64012.10, status: "rejected", reason: "Min notional $10 not met", latencyMs: 22 }, { t: -7100, id: "sig-0a73", side: "SELL", price: 64198.40, status: "executed", reason: null, latencyMs: 384 }, { t: -9200, id: "sig-0a72", side: "BUY", price: 63972.00, status: "executed", reason: null, latencyMs: 502 }, { t: -12400, id: "sig-0a71", side: "BUY", price: 63810.00, status: "rejected", reason: "Daily loss limit reached", latencyMs: 14 }, ]; // ============================================================ // TRADES — full history // ============================================================ const TRADES = [ { t: -12, exchange: "binance", side: "BUY", qty: 0.0084, entry: 64210.50, exit: null, pnl: null, pnlPct: null, stop: 62926.30, closedBy: null, status: "open" }, { t: -184, exchange: "binance", side: "SELL", qty: 0.0082, entry: 63702.00, exit: 64055.40, pnl: 28.97, pnlPct: 0.55, stop: null, closedBy: "signal", status: "closed" }, { t: -640, exchange: "binance", side: "BUY", qty: 0.0085, entry: 63702.00, exit: 63980.10, pnl: 23.64, pnlPct: 0.44, stop: null, closedBy: "signal", status: "closed" }, { t: -1810, exchange: "binance", side: "SELL", qty: 0.0083, entry: 64104.00, exit: 63810.20, pnl: 24.39, pnlPct: 0.46, stop: null, closedBy: "signal", status: "closed" }, { t: -3110, exchange: "binance", side: "SELL", qty: 0.0080, entry: 65085.30, exit: 63812.50, pnl: -101.82,pnlPct: -1.96, stop: 63812.50, closedBy: "stop_loss",status: "stopped_out" }, { t: -4290, exchange: "binance", side: "BUY", qty: 0.0086, entry: 64104.00, exit: 65085.30, pnl: 84.39, pnlPct: 1.53, stop: null, closedBy: "signal", status: "closed" }, { t: -7100, exchange: "kraken", side: "SELL", qty: 0.0082, entry: 63972.00, exit: 64198.40, pnl: 18.56, pnlPct: 0.35, stop: null, closedBy: "signal", status: "closed" }, { t: -9200, exchange: "kraken", side: "BUY", qty: 0.0084, entry: 63972.00, exit: 64198.40, pnl: 19.02, pnlPct: 0.35, stop: null, closedBy: "signal", status: "closed" }, { t: -14800, exchange: "binance", side: "BUY", qty: 0.0080, entry: 63540.00, exit: 64104.00, pnl: 45.12, pnlPct: 0.89, stop: null, closedBy: "signal", status: "closed" }, { t: -22400, exchange: "binance", side: "SELL", qty: 0.0079, entry: 63820.00, exit: 63540.00, pnl: 22.12, pnlPct: 0.44, stop: null, closedBy: "signal", status: "closed" }, { t: -29600, exchange: "kraken", side: "BUY", qty: 0.0083, entry: 63820.00, exit: 64210.00, pnl: 32.37, pnlPct: 0.61, stop: null, closedBy: "signal", status: "closed" }, { t: -38800, exchange: "kraken", side: "SELL", qty: 0.0082, entry: 64198.00, exit: 64822.00, pnl: -51.17, pnlPct: -0.97, stop: 64822.00, closedBy: "stop_loss",status: "stopped_out" }, { t: -52400, exchange: "binance", side: "BUY", qty: 0.0080, entry: 64198.00, exit: 65010.00, pnl: 64.96, pnlPct: 1.26, stop: null, closedBy: "signal", status: "closed" }, { t: -68000, exchange: "binance", side: "SELL", qty: 0.0078, entry: 64850.00, exit: 64198.00, pnl: 50.86, pnlPct: 1.01, stop: null, closedBy: "signal", status: "closed" }, { t: -86400, exchange: "binance", side: "BUY", qty: 0.0081, entry: 64124.00, exit: 64850.00, pnl: 58.81, pnlPct: 1.13, stop: null, closedBy: "signal", status: "closed" }, ]; // ============================================================ // LOGS — structured JSON entries // ============================================================ const LOGS = [ { t: -12, level: "INFO", type: "trade_executed", msg: "Market BUY 0.0084 BTC/USDT @ 64210.50 · OCO stop @ 62926.30", ctx: { signal_id: "sig-0a7c", order_id: "B-4419821", latency_ms: 412 } }, { t: -45, level: "DEBUG", type: "risk_check_passed", msg: "All 6 pre-trade checks passed", ctx: { signal_id: "sig-0a7c" } }, { t: -60, level: "INFO", type: "signal_received", msg: "Webhook BUY BTC/USDT @ 64210.50", ctx: { signal_id: "sig-0a7c", source_ip: "52.89.214.238" } }, { t: -184, level: "INFO", type: "trade_executed", msg: "Market SELL 0.0082 BTC/USDT @ 64055.40 · PnL +$28.97", ctx: { signal_id: "sig-0a7b", order_id: "B-4419742" } }, { t: -1020, level: "WARN", type: "signal_deduplicated", msg: "Dropped duplicate signal_id within 60s window", ctx: { signal_id: "sig-0a79", age_s: 14 } }, { t: -2440, level: "WARN", type: "risk_check_failed", msg: "Rejected: position already open (max_concurrent_positions=1)", ctx: { signal_id: "sig-0a77" } }, { t: -3110, level: "INFO", type: "stop_loss_triggered", msg: "OCO stop filled @ 63812.50 · PnL -$101.82", ctx: { signal_id: "sig-0a76" } }, { t: -5640, level: "WARN", type: "risk_check_failed", msg: "Position size $4.20 below Binance min notional $10", ctx: { signal_id: "sig-0a74" } }, { t: -7220, level: "DEBUG", type: "exchange_api_call", msg: "GET /api/v3/account · 47ms", ctx: { endpoint: "/api/v3/account" } }, { t: -12400, level: "ERROR",type: "risk_check_failed", msg: "Daily loss limit reached: -3.02% (cap -3.00%)", ctx: { signal_id: "sig-0a71" } }, { t: -12500, level: "ERROR",type: "daily_limit_hit", msg: "Circuit breaker tripped · halting all trading", ctx: {} }, { t: -14200, level: "INFO", type: "bot_startup", msg: "Bot started · mode=testnet · exchange=binance · balance=$10,000.00", ctx: { mode: "testnet" } }, { t: -14210, level: "INFO", type: "state_recovery", msg: "Reconciled 1 open position from exchange · 0 orphans", ctx: { open: 1 } }, ]; // ============================================================ // Daily P&L for the last 14 days (for bar chart) // ============================================================ const DAILY_PNL = [ { d: -13, pnl: 82.40, trades: 6, wr: 0.83 }, { d: -12, pnl: -34.10, trades: 5, wr: 0.40 }, { d: -11, pnl: 124.50, trades: 8, wr: 0.75 }, { d: -10, pnl: 18.20, trades: 4, wr: 0.50 }, { d: -9, pnl: -88.40, trades: 9, wr: 0.33 }, { d: -8, pnl: 162.30, trades: 7, wr: 0.86 }, { d: -7, pnl: 204.18, trades:11, wr: 0.73 }, { d: -6, pnl: 56.40, trades: 5, wr: 0.60 }, { d: -5, pnl: -42.10, trades: 6, wr: 0.33 }, { d: -4, pnl: 128.30, trades: 9, wr: 0.78 }, { d: -3, pnl: 96.80, trades: 7, wr: 0.71 }, { d: -2, pnl: -18.40, trades: 4, wr: 0.50 }, { d: -1, pnl: 184.20, trades: 8, wr: 0.75 }, { d: 0, pnl: 132.18, trades: 7, wr: 0.86 }, ]; // Backtest monthly returns (rows = year, cols = month) const MONTHLY = [ { year: 2024, vals: [1.2, -0.8, 3.4, 2.1, 0.9, -1.4, 4.2, 1.8, 2.9, -2.1, 3.6, 4.4] }, { year: 2025, vals: [2.4, 1.1, -1.8, 3.2, 4.1, 2.7, 1.4, -0.6, 3.8, 2.1, 1.9, 2.8] }, { year: 2026, vals: [3.1, 2.6, 1.9, 4.2, 2.1, null, null, null, null, null, null, null] }, ]; // ============================================================ // BUILD PROGRESS — 37 tasks across 8 categories (spec v3 task_status) // ============================================================ const TASK_CATEGORIES = [ { id: "foundation", label: "Foundation", color: "var(--info)" }, { id: "database", label: "Database", color: "#A78BFA" }, { id: "risk", label: "Risk", color: "var(--accent)" }, { id: "execution", label: "Execution", color: "var(--profit)" }, { id: "api", label: "API", color: "#60A5FA" }, { id: "alerts", label: "Alerts", color: "#F472B6" }, { id: "recovery", label: "Recovery", color: "var(--warn)" }, { id: "deployment", label: "Deployment", color: "var(--text-2)" }, ]; const TASKS = [ { id: "T01", cat: "foundation", task: "Initialize project structure and folder layout", status: "done" }, { id: "T02", cat: "foundation", task: "Create .env.example with all environment variables", status: "done" }, { id: "T03", cat: "foundation", task: "Build config.py with Pydantic settings loader", status: "done" }, { id: "T04", cat: "foundation", task: "Set up requirements.txt with pinned dependencies", status: "done" }, { id: "T05", cat: "foundation", task: "Create .gitignore", status: "done" }, { id: "T06", cat: "database", task: "Set up SQLAlchemy async engine with WAL mode", status: "done" }, { id: "T07", cat: "database", task: "Create Trade model (with exchange column)", status: "done" }, { id: "T08", cat: "database", task: "Create Signal model", status: "done" }, { id: "T09", cat: "database", task: "Create DailyStats model", status: "done" }, { id: "T10", cat: "risk", task: "Build RiskManager with pre-trade validation", status: "done" }, { id: "T11", cat: "risk", task: "Implement position sizing (fixed fraction)", status: "done" }, { id: "T12", cat: "risk", task: "Build circuit breaker", status: "done" }, { id: "T13", cat: "risk", task: "Implement max drawdown kill switch", status: "done" }, { id: "T14", cat: "risk", task: "Write tests for risk manager", status: "in-progress" }, { id: "T15", cat: "execution", task: "Build AbstractExchangeClient base class (ABC interface)", status: "done" }, { id: "T16", cat: "execution", task: "Build ExchangeFactory (reads ACTIVE_EXCHANGE → client)", status: "done" }, { id: "T17", cat: "execution", task: "Build BinanceClient (OCO orders, testnet support)", status: "done" }, { id: "T18", cat: "execution", task: "Build KrakenClient (conditional close, paper-trade mode)", status: "in-progress" }, { id: "T19", cat: "execution", task: "Implement testnet / live / paper mode toggle", status: "in-progress" }, { id: "T20", cat: "execution", task: "Build order manager (exchange-agnostic dispatch)", status: "done" }, { id: "T21", cat: "execution", task: "Add retry logic with exponential backoff (3 retries)", status: "in-progress" }, { id: "T22", cat: "execution", task: "Write tests for exchange factory + both clients (mocked)", status: "pending" }, { id: "T23", cat: "api", task: "Create FastAPI app with Uvicorn entry point", status: "done" }, { id: "T24", cat: "api", task: "Build POST /webhook with secret validation", status: "done" }, { id: "T25", cat: "api", task: "Implement signal deduplication (60s window)", status: "done" }, { id: "T26", cat: "api", task: "Build GET /health endpoint (includes active_exchange)", status: "in-progress" }, { id: "T27", cat: "api", task: "Add rate limiting via SlowAPI", status: "done" }, { id: "T28", cat: "api", task: "Create Pydantic models for webhook payload", status: "done" }, { id: "T29", cat: "alerts", task: "Build Telegram bot with async sender", status: "done" }, { id: "T30", cat: "alerts", task: "Create message templates for all events", status: "done" }, { id: "T31", cat: "alerts", task: "Implement /stop /resume /status /balance /today commands", status: "in-progress" }, { id: "T32", cat: "alerts", task: "Build daily summary scheduler (23:59 UTC)", status: "pending" }, { id: "T33", cat: "recovery", task: "Build state reconciler (exchange-aware)", status: "pending" }, { id: "T34", cat: "deployment", task: "Set up structured JSON logging with rotation", status: "in-progress" }, { id: "T35", cat: "deployment", task: "Create Dockerfile", status: "pending" }, { id: "T36", cat: "deployment", task: "Create docker-compose.yml with health checks", status: "pending" }, { id: "T37", cat: "deployment", task: "Write README.md with setup guide (both exchanges)", status: "pending" }, ]; // ============================================================ // ROADMAP — v1 / v2 / v3 (spec v3) // ============================================================ const ROADMAP = [ { v: "v1", name: "Reliable Execution", timeline: "Weeks 1–3", current: true, status: "in-progress", features: [ "Single pair BTC/USDT", "TradingView webhook → FastAPI", "Signal dedup + validation", "Risk manager with pre-trade checks", "Binance + Kraken via CCXT (single-active)", "Exchange factory pattern", "OCO stop / conditional close", "Position sizing (fixed fraction)", "Daily loss circuit breaker", "Telegram alerts + /commands", "State recovery on restart", "Health check endpoint", "Docker deployment", "Testnet / paper mode default", ], }, { v: "v2", name: "Professional Trading System", timeline: "Months 2–3", status: "planned", features: [ "Multi-pair support", "Futures / perpetual trading", "Trailing stop loss", "Take profit (fixed + trailing)", "Web dashboard (FastAPI + React)", "PostgreSQL migration", "Trade history + analytics", "Multi-exchange (Bybit, OKX)", "Docker Compose + NGINX + HTTPS", "Grafana + Prometheus monitoring", "Auto-restart with systemd", ], }, { v: "v3", name: "AI Quant Platform", timeline: "Months 4–6+", status: "future", features: [ "Market regime detection (ML)", "Sentiment analysis (news + social)", "AI strategy optimization", "Reinforcement learning signals", "Adaptive position sizing", "Portfolio balancing", "Order book analytics", "Volatility engine", "Backtesting cluster", ], }, ]; // Changelog distilled from spec const CHANGELOG = [ { date: "2026-05-15 01:30 UTC", agent: "claude_chat", version: "spec v3.0 · MVP V1.1", summary: "Added Kraken as second supported exchange", highlights: [ "Exchange factory pattern (AbstractExchangeClient → BinanceClient, KrakenClient)", "Single-active per deploy via ACTIVE_EXCHANGE env var", "Kraken specifics: conditional close, paper-trade mode (no public testnet), stricter rate limit", "Added 3 new tasks (T15–T17 factory split) · 34 → 37 total", "Added ACTIVE_EXCHANGE, KRAKEN_API_KEY, KRAKEN_API_SECRET, TRADING_PAIR env vars", "trades table gains `exchange` column", "Health endpoint returns active_exchange + trading_mode (testnet | live | paper)", ], }, { date: "2026-05-15 00:00 UTC", agent: "claude_chat", version: "spec v2.0", summary: "Project spec finalized · 34-task plan", highlights: [ "Framework: Flask → FastAPI (async-native)", "Max daily loss: 5% → 3%", "Stop loss expanded: boolean → full OCO config", "Removed Codex API · deferred Bybit/OKX/BingX to v2", "Added testnet default, signal dedup (60s), state recovery, /health, rate limiting", "Added 8 clarifying questions for build kickoff", ], }, ]; // Hourly heatmap — wins per hour-of-day (UTC) over 30d const HOURLY = (() => { const r = seedRand(7); return Array.from({ length: 24 }, (_, h) => ({ hour: h, pnl: (r() - 0.4) * 90 + (h >= 13 && h <= 17 ? 40 : 0), // London/NY overlap edge trades: Math.floor(r() * 8) + 1, })); })(); // Format helpers function fmtUsd(n, { dec = 2, sign = false } = {}) { if (n == null) return "—"; const s = Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec }); return (sign ? (n >= 0 ? "+" : "−") : (n < 0 ? "−" : "")) + "$" + s; } function fmtPct(n, { dec = 2, sign = true } = {}) { if (n == null) return "—"; return (sign ? (n >= 0 ? "+" : "") : "") + n.toFixed(dec) + "%"; } function fmtRel(secAgo) { const s = -secAgo; if (s < 60) return s + "s ago"; if (s < 3600) return Math.round(s / 60) + "m ago"; if (s < 86400) return Math.round(s / 3600) + "h ago"; return Math.round(s / 86400) + "d ago"; } function fmtDuration(sec) { const d = Math.floor(sec / 86400); const h = Math.floor((sec % 86400) / 3600); const m = Math.floor((sec % 3600) / 60); if (d) return d + "d " + h.toString().padStart(2, "0") + "h " + m.toString().padStart(2, "0") + "m"; if (h) return h + "h " + m.toString().padStart(2, "0") + "m"; return m + "m"; } function fmtTime(secAgo) { const dt = new Date(Date.now() + secAgo * 1000); return dt.toISOString().slice(11, 19); // HH:MM:SS UTC } Object.assign(window, { Icon, IconHome, IconSignal, IconChart, IconShield, IconClock, IconBell, IconList, IconTerm, IconGear, IconSearch, IconPlus, IconPause, IconPlay, IconEdit, IconStop, IconChevron, IconUpgrade, IconCheck, IconX, IconCopy, IconEye, IconEyeOff, IconExternal, IconFilter, IconRefresh, IconDown, IconAlert, IconKey, IconBolt, IconRocket, IconSwap, IconLayers, IconLogo, IconBinance, IconKraken, IconTV, IconTelegram, useTicker, useCountUp, generateSeries, seedRand, EXCHANGES, TASK_CATEGORIES, TASKS, ROADMAP, CHANGELOG, SCENARIOS, BOT, SIGNALS, TRADES, LOGS, DAILY_PNL, MONTHLY, HOURLY, fmtUsd, fmtPct, fmtRel, fmtDuration, fmtTime, });