Skip to main content

Overview

The abstention system is a safety filter that prevents trading when the model lacks a statistical edge. Even with a confident prediction (e.g., 85% UP), the system may abstain if volatility is anomalous, recent accuracy is poor, or the edge vs market is too narrow.
Critical Principle: Abstention is not failure. It’s the system recognizing “I don’t know” and protecting capital.

The 7 Abstention Conditions

1

Condition 1: Insufficient Data

Trigger: Fewer than 50 price ticks receivedRationale: EWMA volatility requires ~50 ticks to stabilize. Trading before this produces unreliable σ estimates.Implementation (src/engine/predictor.js:82-85):
if (sigma === 0 || this._volatility.getTickCount() < abstentionCfg.minTicks) {
  return { abstained: true, reason: 'insufficient_data', volatility: sigma }
}
Config: engine.abstention.minTicks = 50Duration: First ~50 seconds of each interval (1 tick/sec)
2

Condition 2: Dead Zone

Trigger: Black-Scholes probability within 10% of 50%Rationale: When the base probability is near 50% (40%-60% range), the model has no directional edge. Even after momentum/reversion adjustments, the final probability rarely moves outside 35%-65%, insufficient for profitable trading.Implementation (src/engine/predictor.js:96-99):
const baseProb = binaryCallProbability({...})
if (Math.abs(baseProb - 0.5) < abstentionCfg.deadZone) {
  return { abstained: true, reason: 'dead_zone', volatility: sigma, probability: baseProb, direction: baseProb >= 0.5 ? 'UP' : 'DOWN' }
}
Config: engine.abstention.deadZone = 0.10Example:
  • BTC = 64,232,Strike=64,232, Strike = 64,245 (13 bps apart)
  • σ = 0.00012/sec, T = 180s
  • Black-Scholes → 48% UP (dead zone: 40% < 48% < 60%)
  • System abstains even though momentum might push to 55%
3

Condition 3: Anomalous Volatility Regime

Trigger: Current volatility > 2× rolling mean volatilityRationale: Volatility spikes indicate regime changes (news events, flash crashes, oracle issues). Model calibrated for normal markets may fail during anomalous periods.Implementation (src/engine/predictor.js:101-105):
const meanSigma = this._volatility.getMeanSigma()  // Mean of last 100 σ values
if (meanSigma > 0 && sigma > abstentionCfg.sigmaMultiplier * meanSigma) {
  return { abstained: true, reason: 'anomalous_regime', volatility: sigma, probability: baseProb, direction: baseProb >= 0.5 ? 'UP' : 'DOWN' }
}
Config: engine.abstention.sigmaMultiplier = 2.0Example:
  • Normal σ = 0.00012/sec (mean over 100 ticks)
  • Spike: σ = 0.00028/sec (2.33× mean)
  • System abstains until volatility normalizes
Typical Triggers:
  • FOMC announcements
  • Exchange hacks
  • Whale market orders
  • Oracle data feed interruptions
4

Condition 4: Cold Streak

Trigger: Accuracy < 40% over last 20 predictionsRationale: Consecutive misses indicate model miscalibration or strategy breakdown. Continue trading would compound losses.Implementation (src/engine/predictor.js:108-111):
const window = abstentionCfg.minAccuracyWindow  // 20
if (this._recentOutcomes.length >= window && this.getRecentAccuracy() < abstentionCfg.minAccuracy) {
  return { abstained: true, reason: 'cold_streak', volatility: sigma, probability: baseProb, direction: baseProb >= 0.5 ? 'UP' : 'DOWN' }
}
Config:
  • engine.abstention.minAccuracy = 0.40
  • engine.abstention.minAccuracyWindow = 20
Example:
  • Last 20 predictions: 7 correct, 13 wrong (35% accuracy)
  • System abstains until accuracy recovers to 40%+
Recovery: Each new correct prediction improves rolling accuracy, potentially exiting cold streak
5

Condition 5: Insufficient Expected Value

Trigger: Best EV < 5%Rationale: After Polymarket’s 3% fee, trades with < 5% EV are marginally profitable and don’t justify execution risk (slippage, oracle delays, etc.).Implementation (src/index.js:157-161):
evResult = calculateEV(prediction.probability, qMarket)
if (evResult) {
  const absCfg = config.engine.abstention
  if (evResult.bestEV < absCfg.minEV) {
    abstention = { abstained: true, reason: 'insufficient_ev', volatility: prediction.volatility }
    prediction = null
  }
}
Config: engine.abstention.minEV = 0.05Example:
  • Model: p = 0.52 (52% UP)
  • Market: q = 0.50 (50% UP)
  • EV(YES) = 0.52/0.50 - 1 = 4% (below 5% threshold)
  • System abstains
Note: This check requires market price q, so it happens after prediction generation (post-prediction filter).
6

Condition 6: Insufficient Margin

Trigger: |p - q| / max(p, 1-p) < 15%Rationale: Even with high EV, if the model probability is too close to market probability, small calibration errors can flip the trade into negative EV. The margin check ensures a safety buffer.Implementation (src/index.js:162-165):
if (evResult.margin < absCfg.minMargin) {
  abstention = { abstained: true, reason: 'insufficient_margin', volatility: prediction.volatility }
  prediction = null
}
Config: engine.abstention.minMargin = 0.15Formula: margin = |p - q| / max(p, 1-p)Example:
  • Model: p = 0.60 (60% UP)
  • Market: q = 0.52 (52% UP)
  • Edge: |0.60 - 0.52| = 8pp
  • Margin: 0.08 / max(0.60, 0.40) = 0.08 / 0.60 = 13.3% (below 15%)
  • System abstains despite 15% EV
Why margin, not edge? Margin is normalized by prediction confidence. An 8pp edge on a 60% prediction (margin=13%) is riskier than an 8pp edge on an 80% prediction (margin=10%).
7

Condition 7: Drawdown Suspension

Trigger: Drawdown level = RED or CRITICALRationale: When bankroll has declined 20%+ from high-water mark, risk management forces a trading pause to prevent catastrophic losses.Implementation (src/index.js:169-180):
const ddAdjustments = drawdownTracker.getAdjustments()
if (ddAdjustments.suspend) {
  abstention = { abstained: true, reason: 'drawdown_suspended', volatility: prediction.volatility }
  prediction = null
} else if (ddAdjustments.minEVOverride != null && evResult) {
  if (evResult.bestEV < ddAdjustments.minEVOverride) {
    abstention = { abstained: true, reason: 'insufficient_ev_yellow', volatility: prediction.volatility }
    prediction = null
  }
}
Levels:
  • Green (0-10% DD): No suspension
  • Yellow (10-20% DD): Raise EV threshold to 10% (vs 5%)
  • Red (20-30% DD): Suspend all trading
  • Critical (30%+ DD): Suspend all trading
Config: risk.drawdown.{yellowPct, redPct, criticalPct}Example:
  • Bankroll started at 100,now100, now 75 (25% drawdown = RED)
  • System suspends trading until bankroll recovers to $80+ (20% drawdown = YELLOW)

Abstention Cascade

Conditions are checked in order. First failure triggers immediate abstention:

Statistics & Tracking

The system records abstention reasons per interval in data/history.json:
{
  "intervalNumber": 42,
  "epochTimestamp": 1709571300,
  "strikePrice": 64355.45,
  "finalPrice": 64412.88,
  "outcome": "UP",
  "earlyPrediction": null,
  "abstentionReason": "insufficient_margin",
  "volatility": 0.000115,
  "qMarket": 0.52,
  "evResult": {
    "bestEV": 0.15,
    "edge": 0.08,
    "margin": 0.133
  }
}

Analyzing Abstention Patterns

High abstention rate by reason:
ReasonHigh Rate (>30%)InterpretationAction
insufficient_dataNormalSystem warming upNone (expected)
dead_zoneNormalTight markets near 50/50Increase deadZone to reduce noise
anomalous_regimeConcerningFrequent volatility spikesCheck oracle health, increase sigmaMultiplier
cold_streakConcerningModel miscalibratedReview prediction logic, adjust weights
insufficient_evNormalMarket efficientIncrease minEV for higher bar
insufficient_marginNormalClose calls rejectedDecrease minMargin if too conservative
drawdown_suspendedConcerningSustained lossesReview overall strategy, reduce risk

Configuration Trade-offs

{
  "engine": {
    "abstention": {
      "minTicks": 30,
      "deadZone": 0.05,
      "sigmaMultiplier": 3.0,
      "minAccuracy": 0.30,
      "minEV": 0.03,
      "minMargin": 0.10
    }
  }
}
Effects:
  • More frequent trading (higher volume)
  • Lower edge per trade (thinner margins)
  • Higher variance (more losses mixed with wins)
Use When:
  • High confidence in model calibration
  • Seeking volume for statistical convergence
  • Liquidity is abundant (can fill small-edge trades)
{
  "engine": {
    "abstention": {
      "minTicks": 100,
      "deadZone": 0.15,
      "sigmaMultiplier": 1.5,
      "minAccuracy": 0.50,
      "minEV": 0.10,
      "minMargin": 0.25
    }
  }
}
Effects:
  • Rare trading (low volume)
  • High edge per trade (strong opportunities only)
  • Lower variance (fewer bad trades)
Use When:
  • Model calibration uncertain
  • Capital preservation priority
  • Liquidity scarce (avoid low-edge slippage)

Special Case: Abstention During Prediction

The system still generates predictions even when abstaining. This allows:
  1. Performance tracking - Evaluate model accuracy without trading
  2. Calibration learning - Feed outcomes to Platt scaler
  3. Display transparency - User sees “model says 85% UP, but abstaining because insufficient margin”
Example from src/index.js:141-149:
const result = engine.predict({...})
if (result.abstained) {
  abstention = result
  // Preserve model probability for display/recording even when abstaining
  if (result.probability != null) {
    rawPrediction = { probability: result.probability, direction: result.direction, volatility: result.volatility }
  }
} else {
  prediction = result
  rawPrediction = result
}
This ensures data/history.json contains:
{
  "rawPrediction": { "probability": 0.85, "direction": "UP", "volatility": 0.00012 },
  "earlyPrediction": null,
  "abstentionReason": "insufficient_margin"
}
Later analysis can answer: “Would we have won if we traded despite abstaining?”

Abstention vs Risk Management

Abstention System (pre-trade filter):
  • Blocks trades when model has no edge
  • Based on statistical properties (volatility, accuracy, EV)
  • Binary decision: trade or don’t trade
Risk Management (post-trade filter):
  • Sizes trades when edge exists
  • Based on calibration quality (Brier Score) and bankroll state (drawdown)
  • Continuous scaling: bet size from 0% to 5% of bankroll
Interaction:
Abstention System → Prediction → Risk Management → Bet Size

If abstention triggers → No bet (size = $0)
If abstention passes → Calculate bet size (may still be $0 if Brier tier 0)

Example Scenarios

Scenario 1: Green Light (Trade Executes)

// ── State ──
// Ticks: 120
// BTC: $64,232, Strike: $64,450 (34 bps apart)
// σ: 0.00012/sec, meanσ: 0.00011/sec
// Recent accuracy: 18/20 (90%)
// Market: q = 0.10
// Drawdown: 5% (green)

// ── Checks ──
// 1. Ticks >= 50? ✓ (120)
// 2. Dead zone? ✓ (baseProb = 8%, outside 40-60%)
// 3. Anomalous σ? ✓ (0.00012 < 2×0.00011)
// 4. Cold streak? ✓ (90% > 40%)
// 5. EV >= 5%? ✓ (EV = 750%)
// 6. Margin >= 15%? ✓ (margin = 88%)
// 7. Drawdown OK? ✓ (green, no suspension)

// ── Result ──
// Bet: $5.00 (capped at max 5%)

Scenario 2: Dead Zone (Abstain)

// ── State ──
// BTC: $64,232, Strike: $64,235 (0.5 bps apart)
// σ: 0.00012/sec, T: 180s

// ── Black-Scholes ──
// d₂ = [ln(64232/64235) - (0.00012²/2)×180] / (0.00012×√180)
//    = -0.00005 / 0.00159 = -0.029
// N(d₂) = 49% (dead zone: 40% < 49% < 60%)

// ── Result ──
// Abstain: dead_zone
// Reason: No directional edge when price is too close to strike

Scenario 3: Anomalous Regime (Abstain)

// ── State ──
// σ: 0.00035/sec (spike!)
// meanσ: 0.00012/sec (last 100 ticks)

// ── Check ──
// σ > 2 × meanσ?
// 0.00035 > 2 × 0.00012 = 0.00024? ✓ YES

// ── Result ──
// Abstain: anomalous_regime
// Reason: Volatility spike suggests regime change or data issue

Scenario 4: Insufficient Margin (Abstain)

// ── State ──
// Model: p = 0.58 (58% UP)
// Market: q = 0.52 (52% UP)

// ── EV Check ──
// EV(YES) = 0.58 / 0.52 - 1 = 11.5% ✓ (> 5% threshold)

// ── Margin Check ──
// margin = |0.58 - 0.52| / max(0.58, 0.42)
//        = 0.06 / 0.58 = 10.3% ✗ (< 15% threshold)

// ── Result ──
// Abstain: insufficient_margin
// Reason: Edge too narrow, calibration error could flip sign

Monitoring Abstention Health

From src/ui/display.js, the console shows abstention reason in real-time:
  POLYMARKET BOT
+----------------------------------------------------------------------+
|   BTC Price: $64,232.02   Strike: $64,235.45   Time: 2:56   [LIVE]   |
|   Prediction: ~ 49%   ABSTENTION   (dead_zone)                        |
|   Market: --  EV: --  Edge: --  [POLY]                                |
|   Bankroll: $100.00  Drawdown: 0.0% [GREEN]  Bet: --  Kelly α: --     |
+----------------------------------------------------------------------+
Daily reports (src/reporter/daily.js) aggregate abstention stats:
## Abstention Breakdown

| Reason | Count | % |
|--------|-------|---|
| dead_zone | 18 | 30% |
| insufficient_margin | 12 | 20% |
| insufficient_data | 8 | 13% |
| anomalous_regime | 3 | 5% |
| cold_streak | 1 | 2% |
| **Total** | **42** | **70%** |

Trades executed: 18 (30%)

Next Steps

System Architecture

See how abstention fits into the overall workflow

Risk Management

Understand bet sizing and drawdown tracking

Build docs developers (and LLMs) love