Skip to main content

Architecture

The Polymarket Bot prediction engine combines multiple mathematical models to generate probabilistic forecasts for binary market outcomes. The system operates in real-time on streaming price data and produces calibrated probability estimates.

Core Components

The prediction pipeline consists of five interconnected modules:

Black-Scholes

Binary option probability from geometric Brownian motion assumptions

Volatility

EWMA estimator producing per-second volatility from tick stream

Momentum

ROC and mean-reversion signals across multiple time windows

Calibration

Platt sigmoid scaling for post-hoc probability refinement

Prediction Flow

1. Data Ingestion

Price ticks are fed into both the volatility estimator and momentum analyzer:
feedTick({ timestamp, price }) {
  this._volatility.update(price, timestamp)
  this._momentum.addTick({ timestamp, price })
}

2. Base Probability Calculation

The Black-Scholes binary option model produces a base probability using current volatility:
const sigma = this._volatility.getVolatility()
const baseProb = binaryCallProbability({
  currentPrice,
  strikePrice,
  volatility: sigma,
  timeRemainingSeconds,
  riskFreeRate: 0,
})
Volatility Units: The EWMA estimator produces per-second volatility (σ), matching the time units expected by the Black-Scholes formula.

3. Signal Extraction

Momentum and mean-reversion signals are computed from the tick buffer:
const { combined: momentumFactor } = this._momentum.getMomentum()
const { signal: reversionFactor } = this._momentum.getMeanReversion()

4. Logit-Space Fusion

Signals are combined using log-odds arithmetic to preserve probability bounds:
const logitBase = logit(baseProb)
const logitAdj = logitBase
  + config.engine.prediction.logitMomentumWeight * momentumFactor
  + config.engine.prediction.logitReversionWeight * reversionFactor
finalProb = sigmoid(logitAdj)
Logit-space combination is critical — naive probability addition/multiplication can produce values outside [0,1]. The logit transform maps (0,1) → ℝ, allowing safe additive adjustments.

5. Platt Calibration

When ≥200 prediction/outcome pairs have been collected, the scaler automatically activates:
if (this._scaler.canFit()) {
  if (!this._scaler.getStats().fitted) {
    this._scaler.fit()
  }
  finalProb = this._scaler.calibrate(finalProb)
}

Abstention Conditions

The engine abstains (refuses to predict) under six conditions:
ConditionTriggerReason
Insufficient Dataσ = 0 or ticks < minTicksEWMA not warmed up
Dead Zone|p - 0.5| < deadZoneNo edge detected
Anomalous Regimeσ > sigmaMultiplier × mean(σ)Volatility spike
Cold StreakRecent accuracy < minAccuracyModel underperforming
Low EVEV < minEVExpected value too small (post-prediction filter)
Thin Marginmargin < minMarginInsufficient Kelly bet size (post-prediction filter)
Conditions 5-6 are implemented as post-prediction filters in src/index.js because they require market price data external to the model.

Near-Expiry Guard

When ≤5 seconds remain until market close, momentum/reversion adjustments are skipped:
if (timeRemainingSeconds <= config.engine.prediction.nearExpiryGuardSec) {
  finalProb = baseProb  // Use only Black-Scholes probability
} else {
  // Apply logit-space adjustments
}
This prevents late-stage signal noise from corrupting the base probability estimate.

Mathematical Foundations

Logit Transform

The logit (log-odds) function maps probability space to the real line: logit(p)=log(p1p):(0,1)(,+)\text{logit}(p) = \log\left(\frac{p}{1-p}\right) \quad : \quad (0,1) \to (-\infty, +\infty) Its inverse, the sigmoid (logistic) function: σ(z)=11+ez:(,+)(0,1)\sigma(z) = \frac{1}{1 + e^{-z}} \quad : \quad (-\infty, +\infty) \to (0,1)

Why Logit-Space Combination?

Probabilities are constrained to [0,1], making naive arithmetic problematic:
  • Addition: 0.6 + 0.5 = 1.1 (invalid)
  • Multiplication: 0.9 × 0.9 = 0.81 (compression bias)
Log-odds are additive on the real line: padj=σ(logit(pbase)+w1f1+w2f2+)p_{\text{adj}} = \sigma\left(\text{logit}(p_{\text{base}}) + w_1 f_1 + w_2 f_2 + \cdots\right) This ensures:
  1. Adjusted probabilities always ∈ (0,1)
  2. Symmetric treatment of probabilities near 0 and 1
  3. Natural interpretation as additive evidence

Implementation Details

Safety Clamping

The logit function is undefined at 0 and 1. Inputs are clamped to [10⁻⁷, 1-10⁻⁷]:
function logit(p) {
  const safe = clamp(p, 1e-7, 1 - 1e-7)
  return Math.log(safe / (1 - safe))
}
Final predictions are clamped to [0.01, 0.99] before returning:
finalProb = clamp(finalProb, 0.01, 0.99)

Outcome Tracking

The engine maintains a rolling window of recent outcomes for cold-streak detection:
recordOutcome(correct, predictedProb) {
  const window = config.engine.abstention.minAccuracyWindow
  this._recentOutcomes.push(correct)
  while (this._recentOutcomes.length > window) {
    this._recentOutcomes.shift()
  }
  // Also feed Platt scaler
  this._scaler.collect(predictedProb, correct ? 1 : 0)
}

State Management

The engine offers two reset modes:
// Full reset: clear volatility + momentum + calibration
reset() {
  this._volatility.reset()
  this._momentum.reset()
  this._recentOutcomes = []
  this._scaler = new PlattScaler()
}

// Partial reset: preserve volatility estimate across epochs
resetMomentum() {
  this._momentum.reset()
}
When to use which reset:
  • reset(): Market close, context switch, or emergency flush
  • resetMomentum(): New interval start (volatility should persist)

Configuration Parameters

Key engine settings from config.js:
engine: {
  ewma: {
    lambda: 0.94  // EWMA decay factor
  },
  momentum: {
    bufferSize: 300  // Max ticks retained
  },
  prediction: {
    logitMomentumWeight: 2.0,     // Momentum signal weight in logit space
    logitReversionWeight: 1.5,    // Reversion signal weight in logit space
    nearExpiryGuardSec: 5         // Disable adjustments when t ≤ 5s
  },
  abstention: {
    minTicks: 5,                  // Minimum ticks before predicting
    deadZone: 0.05,               // ±5% around 0.5
    sigmaMultiplier: 3.0,         // Anomaly threshold (3× mean volatility)
    minAccuracyWindow: 20,        // Rolling accuracy window size
    minAccuracy: 0.50             // Cold-streak threshold
  }
}

Next Steps

Black-Scholes Model

Deep dive into binary option probability calculation

Volatility Estimation

EWMA algorithm and per-second variance normalization

Momentum Signals

ROC windows and mean-reversion detection

Predictor Integration

Complete prediction engine API and usage examples

Build docs developers (and LLMs) love