Skip to main content

Overview

This reference documents all major data structures used by the bot, including prediction results, interval records, configuration types, and API response shapes.

Core Types

Tick

Price tick from ChainlinkFeed.
interface Tick {
  timestamp: number  // Unix milliseconds (Date.now())
  price: number      // BTC/USD price
}
Source: src/feeds/chainlink.js:26 Example:
{
  timestamp: 1733227533421,
  price: 96842.50
}

PredictionResult

Output from PredictionEngine.predict().
interface PredictionResult {
  // ── Full prediction (when abstained = false or undefined) ──
  probability: number         // Model probability (0.01-0.99)
  direction: 'UP' | 'DOWN'    // Based on probability >= 0.5
  volatility: number          // EWMA sigma (per-second)
  momentum: number            // Combined ROC factor
  reversion: number           // Mean-reversion signal
  calibrated: boolean         // Was Platt scaling applied?
  
  // ── Abstention (when abstained = true) ──
  abstained?: boolean         // True when prediction withheld
  reason?: string             // Abstention reason code:
                              // 'insufficient_data', 'dead_zone', 
                              // 'anomalous_regime', 'cold_streak',
                              // 'insufficient_ev', 'insufficient_margin',
                              // 'drawdown_suspended', 'insufficient_ev_yellow'
}
Source: src/engine/predictor.js:68-77 Example - Full prediction:
{
  probability: 0.687,
  direction: 'UP',
  volatility: 0.00042,
  momentum: 0.0023,
  reversion: -0.0008,
  calibrated: true
}
Example - Abstention:
{
  abstained: true,
  reason: 'dead_zone',
  volatility: 0.00039,
  probability: 0.512,  // Base probability (before abstention)
  direction: 'UP'
}

EVResult

Expected value calculation from calculateEV().
interface EVResult {
  evYes: number      // EV of buying YES: p/q - 1
  evNo: number       // EV of buying NO: (1-p)/(1-q) - 1
  margin: number     // Safety margin: |p-q| / max(p, 1-p)
  bestSide: 'YES' | 'NO'  // Which side has higher EV
  bestEV: number     // max(evYes, evNo)
  ev: number         // Alias for bestEV
  edge: number       // Model edge: p - q
}
Source: src/feeds/polymarket.js:141-150 Example:
{
  evYes: 0.0818,      // 8.18% expected value on YES
  evNo: -0.0435,      // Negative EV on NO
  margin: 0.1538,     // 15.38% margin
  bestSide: 'YES',
  bestEV: 0.0818,
  ev: 0.0818,
  edge: 0.045         // Model is 4.5% more bullish than market
}

BetResult

Bet sizing output from PositionSizer.
interface BetResult {
  bet: number         // Recommended bet size in USD
  fullKelly: number   // Full Kelly fraction (before alpha adjustment)
  alpha: number       // Fractional Kelly multiplier (0-1)
  side: 'YES' | 'NO'  // Which side to bet
  capped: boolean     // Was bet capped at maxBetPct?
}
Source: src/risk/position-sizer.js Example:
{
  bet: 2.50,          // Bet $2.50
  fullKelly: 0.025,   // 2.5% of bankroll
  alpha: 0.25,        // Using 1/4 Kelly
  side: 'YES',
  capped: false
}

DrawdownState

Risk state from DrawdownTracker.
interface DrawdownState {
  bankroll: number       // Current bankroll in USD
  highWaterMark: number  // Historical peak bankroll
  drawdownPct: number    // Drawdown percentage (0-1)
  coldStreak: number     // Consecutive high-confidence misses
  level: 'green' | 'yellow' | 'red' | 'critical'  // Risk level
}
Source: src/risk/drawdown-tracker.js Example:
{
  bankroll: 94.50,
  highWaterMark: 105.00,
  drawdownPct: 0.10,    // 10% drawdown
  coldStreak: 0,
  level: 'yellow'
}

IntervalRecord

Complete record of a closed 5-minute interval. Source: src/tracker/interval.js:6-56
interface IntervalRecord {
  // ── Core identity ──
  index: number                    // Sequential 1-based index
  epochTimestamp: number           // 5-min epoch boundary (Unix seconds)
  
  // ── Price data ──
  strikePrice: number | null       // Target price from Vatic
  finalPrice: number | null        // Actual price at interval close
  result: 'UP' | 'DOWN' | 'ACTIVE' // Outcome (ACTIVE = not closed yet)
  
  // ── Predictions ──
  prediction: Prediction | null          // Final prediction snapshot (30s mark)
  predictionCorrect: boolean | null      // Was final prediction correct?
  earlyPrediction: Prediction | null     // Early prediction snapshot (60s mark)
  earlyPredictionCorrect: boolean | null // Was early prediction correct?
  livePrediction?: Prediction | null     // Current prediction (updates every tick)
  
  // ── Model factors (captured at early snapshot) ──
  volatility: number | null        // EWMA sigma (per-second)
  momentum: number | null          // Combined ROC factor
  reversion: number | null         // Mean-reversion signal
  calibrated: boolean | null       // Was Platt calibration active?
  
  // ── Market & EV (captured at early snapshot) ──
  qMarket: number | null           // Polymarket UP token price (0-1)
  evAtCapture: number | null       // Best expected value
  edge: number | null              // Model edge: p - q
  margin: number | null            // Safety margin
  evSide: string | null            // 'YES' | 'NO' - best EV side
  
  // ── Bet details (captured at early snapshot) ──
  betSize: number | null           // USD bet from PositionSizer
  fullKelly: number | null         // Full Kelly fraction
  alpha: number | null             // Fractional Kelly alpha used
  betSide: string | null           // 'YES' | 'NO'
  betCapped: boolean | null        // Was bet capped at maxBetPct?
  
  // ── Risk state (captured at early snapshot) ──
  drawdownLevel: string | null     // 'green'|'yellow'|'red'|'critical'
  bankroll: number | null          // Current bankroll USD
  drawdownPct: number | null       // Drawdown from high-water mark (0-1)
  coldStreak: number | null        // Consecutive high-conf misses
  
  // ── Abstention tracking ──
  abstentionReason: string | null  // Why we abstained (null if traded)
  
  // ── Timing ──
  timeRemainingAtCapture: number | null  // Seconds at early capture
  
  // ── Price dynamics (computed at close) ──
  priceDelta: number | null        // finalPrice - strikePrice
  priceMovePct: number | null      // (finalPrice - strikePrice) / strikePrice * 100
  
  closedAt: string | null          // ISO timestamp of interval close
}

interface Prediction {
  probability: number     // Model probability (0.01-0.99)
  direction: 'UP' | 'DOWN'
}
Example:
{
  index: 42,
  epochTimestamp: 1733227500,
  strikePrice: 96842.50,
  finalPrice: 96891.25,
  result: 'UP',
  
  prediction: { probability: 0.683, direction: 'UP' },
  predictionCorrect: true,
  earlyPrediction: { probability: 0.687, direction: 'UP' },
  earlyPredictionCorrect: true,
  
  volatility: 0.00042,
  momentum: 0.0023,
  reversion: -0.0008,
  calibrated: true,
  
  qMarket: 0.55,
  evAtCapture: 0.0818,
  edge: 0.045,
  margin: 0.1538,
  evSide: 'YES',
  
  betSize: 2.50,
  fullKelly: 0.025,
  alpha: 0.25,
  betSide: 'YES',
  betCapped: false,
  
  drawdownLevel: 'green',
  bankroll: 102.50,
  drawdownPct: 0,
  coldStreak: 0,
  
  abstentionReason: null,
  timeRemainingAtCapture: 58,
  
  priceDelta: 48.75,
  priceMovePct: 0.0503,
  
  closedAt: '2024-12-03T12:10:00.123Z'
}
Most fields are null until the appropriate capture window:
  • livePrediction: Updates every tick
  • earlyPrediction + model/market/bet/risk fields: Captured once at ≤60s remaining
  • prediction: Captured once at ≤30s remaining
  • finalPrice, result, price dynamics, closedAt: Set when interval closes

API Response Types

VaticResponse

Response from Vatic strike price API.
interface VaticResponse {
  target_price?: number  // Strike price (primary field)
  target?: number        // Fallback field
  price?: number         // Second fallback
  [key: string]: any     // Other metadata fields
}
Source: src/feeds/vatic.js:36 Example:
{
  target_price: 96842.50,
  timestamp: 1733227500,
  asset: 'btc',
  type: '5min'
}

PolymarketMarket

Response from discoverActiveMarket().
interface PolymarketMarket {
  found: boolean
  conditionId?: string      // Polymarket condition ID
  upTokenId?: string        // CLOB token ID for UP
  downTokenId?: string      // CLOB token ID for DOWN
  outcomePrices?: number[]  // [upPrice, downPrice]
  slug?: string             // Market slug (e.g., 'btc-updown-5m-1733227500')
}
Source: src/feeds/polymarket.js:38-46 Example:
{
  found: true,
  conditionId: '0x1234...abcd',
  upTokenId: '0x5678...ef01',
  downTokenId: '0x9abc...def2',
  outcomePrices: [0.55, 0.45],
  slug: 'btc-updown-5m-1733227500'
}

Configuration Types

Config

Complete configuration schema from config/default.json.
interface Config {
  engine: EngineConfig
  feeds: FeedsConfig
  storage: StorageConfig
  bot: BotConfig
  risk: RiskConfig
}

interface EngineConfig {
  ewma: {
    lambda: number  // EWMA decay factor (0-1), default: 0.94
  }
  momentum: {
    bufferSize: number  // Max ticks to retain, default: 300
  }
  abstention: {
    minTicks: number           // Min ticks before predictions, default: 50
    deadZone: number           // Abstain if |p-0.5| < this, default: 0.10
    sigmaMultiplier: number    // Abstain if sigma > this * mean, default: 2.0
    minAccuracy: number        // Min recent accuracy, default: 0.40
    minAccuracyWindow: number  // Window size, default: 20
    minEV: number              // Min expected value, default: 0.05
    minMargin: number          // Min safety margin, default: 0.15
  }
  prediction: {
    logitMomentumWeight: number     // Momentum adjustment, default: 150
    logitReversionWeight: number    // Reversion adjustment, default: 80
    nearExpiryGuardSec: number      // Skip adjustments when < this, default: 5
  }
}

interface FeedsConfig {
  chainlink: {
    spikeThreshold: number  // Spike detection threshold, default: 0.10
  }
  polymarket: {
    gammaBaseUrl: string        // Gamma API base, default: 'https://gamma-api.polymarket.com'
    clobBaseUrl: string         // CLOB API base, default: 'https://clob.polymarket.com'
    slugPrefix: string          // Market slug prefix, default: 'btc-updown-5m-'
    pollIntervalSec: number     // Price poll interval, default: 5
    offsetsFallback: number[]   // Epoch offsets to try, default: [-300, 300]
    enabled: boolean            // Enable Polymarket feed, default: true
  }
}

interface StorageConfig {
  historyPath: string  // History file path, default: 'data/history.json'
}

interface BotConfig {
  loopIntervalMs: number  // Main loop interval, default: 1000
}

interface RiskConfig {
  bankroll: number       // Initial bankroll USD, default: 100
  maxBetPct: number      // Max bet as fraction of bankroll, default: 0.05
  minBetUsd: number      // Min bet size USD, default: 1
  kellyFraction: number  // Base fractional Kelly, default: 0.25
  drawdown: {
    yellowPct: number    // Yellow threshold, default: 0.10
    redPct: number       // Red threshold, default: 0.20
    criticalPct: number  // Critical threshold, default: 0.30
  }
  coldStreak: {
    consecutiveMissThreshold: number  // Threshold for cold streak, default: 5
    minConfidenceForStreak: number    // Min probability to count, default: 0.70
  }
  brierTiers: BrierTier[]  // Brier score based alpha adjustments
}

interface BrierTier {
  maxBrier: number | null      // Max Brier for this tier (null = no max)
  minPredictions: number       // Min predictions to qualify
  maxPredictions?: number      // Max predictions for this tier
  alpha: number                // Fractional Kelly multiplier (0-1)
}
Source: config/default.json Example:
import config from './config.js'

console.log(config.engine.ewma.lambda)  // 0.94
console.log(config.feeds.polymarket.pollIntervalSec)  // 5
console.log(config.risk.bankroll)  // 100

Enums

ConnectionStatus

type ConnectionStatus = 'connected' | 'disconnected' | 'reconnecting'
Used by ChainlinkFeed’s onStatus callback.

Direction

type Direction = 'UP' | 'DOWN'
Prediction direction (price will go up or down relative to strike).

IntervalResult

type IntervalResult = 'UP' | 'DOWN' | 'ACTIVE'
Actual outcome of an interval:
  • 'UP': Final price > strike price
  • 'DOWN': Final price ≤ strike price
  • 'ACTIVE': Interval not closed yet

DrawdownLevel

type DrawdownLevel = 'green' | 'yellow' | 'red' | 'critical'
Risk levels:
  • 'green': < 10% drawdown (default)
  • 'yellow': 10-20% drawdown (raise minEV threshold)
  • 'red': 20-30% drawdown (suspend trading)
  • 'critical': > 30% drawdown (emergency suspension)

AbstentionReason

type AbstentionReason =
  | 'insufficient_data'        // < minTicks received
  | 'dead_zone'                // Probability too close to 0.5
  | 'anomalous_regime'         // Volatility spike detected
  | 'cold_streak'              // Recent accuracy below threshold
  | 'insufficient_ev'          // Expected value < minEV
  | 'insufficient_margin'      // Safety margin < minMargin
  | 'drawdown_suspended'       // Red/critical drawdown level
  | 'insufficient_ev_yellow'   // EV below raised threshold (yellow level)
Reasons why the bot abstains from making a prediction or trade.

Type Guards & Validators

isPrediction()

Check if a PredictionResult is a full prediction (not abstention).
function isPrediction(result) {
  return !result.abstained && result.probability != null
}

if (isPrediction(result)) {
  console.log(`Prediction: ${result.direction} (${result.probability})`)
}

isValidTick()

Validate a price tick.
function isValidTick(tick) {
  return (
    tick &&
    typeof tick.timestamp === 'number' &&
    typeof tick.price === 'number' &&
    Number.isFinite(tick.price) &&
    tick.price > 0
  )
}
Used internally by ChainlinkFeed (see src/feeds/chainlink.js:98-111).

isValidEV()

Check if EV result is tradeable.
function isValidEV(ev, minEV = 0.05, minMargin = 0.15) {
  return (
    ev != null &&
    ev.bestEV >= minEV &&
    ev.margin >= minMargin
  )
}

if (prediction && isValidEV(evResult)) {
  console.log(`Trade signal: ${evResult.bestSide}`)
}

JSON Serialization

All data structures are JSON-serializable by design:
import { HistoryStore } from './tracker/history.js'

const store = new HistoryStore()
const records = await store.load()

// Records are plain objects - safe to serialize
const json = JSON.stringify(records, null, 2)
console.log(json)

// Export to file
import { writeFile } from 'fs/promises'
await writeFile('export.json', json)
Special handling:
  • Dates: Stored as ISO strings (e.g., closedAt)
  • Timestamps: Unix seconds (epoch) or milliseconds (ticks)
  • Null values: Explicitly stored (not undefined) for clarity

See Also

Build docs developers (and LLMs) love