Skip to main content

Overview

The Black-Scholes model computes the probability that a binary option finishes in-the-money, assuming the underlying price follows a geometric Brownian motion (GBM). For a binary call option with strike price K: P(in-the-money)=P(ST>K)=N(d2)P(\text{in-the-money}) = P(S_T > K) = N(d_2) where N is the standard normal cumulative distribution function and d₂ is defined below.

Mathematical Formulation

The d₂ Parameter

Under the risk-neutral measure, the probability that the underlying price S exceeds the strike K at expiry T is: d2=ln(S/K)+(rσ22)TσTd_2 = \frac{\ln(S/K) + \left(r - \frac{\sigma^2}{2}\right) T}{\sigma \sqrt{T}} Where:
  • S = current underlying price
  • K = strike price (threshold)
  • σ = volatility (standard deviation of log returns, per-second)
  • T = time remaining until expiry (seconds)
  • r = continuous risk-free rate (per-second)
Time Units: All parameters must use consistent time units. This implementation uses seconds throughout. The volatility σ is expressed as per-second volatility, matching the output of the EWMA estimator.

Normal CDF Approximation

The implementation uses the Abramowitz and Stegun polynomial approximation for N(x): N(x)12[1+sign(x)y]N(x) \approx \frac{1}{2}\left[1 + \text{sign}(x) \cdot y\right] where: y=1[i=15aiti]ex2/2y = 1 - \left[\sum_{i=1}^5 a_i t^i\right] \cdot e^{-x^2/2} t=11+px/2t = \frac{1}{1 + p|x|/\sqrt{2}} Coefficients:
CoefficientValue
a₁0.254829592
a₂-0.284496736
a₃1.421413741
a₄-1.453152027
a₅1.061405429
p0.3275911
Maximum Error: 1.5 × 10⁻⁷ across the entire real line. This is more than sufficient for financial applications.

Implementation

Normal CDF Function

probability.js
export function normalCDF(x) {
  const a1 = 0.254829592
  const a2 = -0.284496736
  const a3 = 1.421413741
  const a4 = -1.453152027
  const a5 = 1.061405429
  const p = 0.3275911

  const sign = x < 0 ? -1 : 1
  const absX = Math.abs(x) / Math.sqrt(2)

  const t = 1.0 / (1.0 + p * absX)
  const y =
    1.0 -
    ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-absX * absX)

  return 0.5 * (1.0 + sign * y)
}

Binary Call Probability

probability.js
export function binaryCallProbability({
  currentPrice,
  strikePrice,
  volatility,
  timeRemainingSeconds,
  riskFreeRate = 0,
}) {
  // At or past expiry: deterministic outcome
  if (timeRemainingSeconds <= 0) {
    return currentPrice > strikePrice ? 1.0 : 0.0
  }

  // Guard against degenerate inputs
  if (volatility <= 0 || currentPrice <= 0 || strikePrice <= 0) {
    return 0.5
  }

  const T = timeRemainingSeconds
  const sqrtT = Math.sqrt(T)

  // d2 = [ln(S/K) + (r - sigma^2/2) * T] / (sigma * sqrt(T))
  const d2 =
    (Math.log(currentPrice / strikePrice) +
      (riskFreeRate - (volatility * volatility) / 2) * T) /
    (volatility * sqrtT)

  return normalCDF(d2)
}

Edge Cases

1. At or Past Expiry (T ≤ 0)

When time remaining is zero or negative, the outcome is deterministic:
if (timeRemainingSeconds <= 0) {
  return currentPrice > strikePrice ? 1.0 : 0.0
}

2. Degenerate Inputs

If volatility, current price, or strike price are zero or negative, the model returns 0.5 (neutral probability):
if (volatility <= 0 || currentPrice <= 0 || strikePrice <= 0) {
  return 0.5
}
Zero volatility (σ = 0) implies the price never moves, making the binary option probability undefined. Returning 0.5 is a safe fallback.

Intuition

The d₂ Metric

d₂ measures how many standard deviations the current price is from the “break-even” log-return: d2=distance to strikevolatility×timed_2 = \frac{\text{distance to strike}}{\text{volatility} \times \sqrt{\text{time}}}
  • d₂ > 0: Current price is above the drift-adjusted strike → P > 0.5 (bullish)
  • d₂ < 0: Current price is below the drift-adjusted strike → P < 0.5 (bearish)
  • d₂ = 0: Exactly at the median outcome → P = 0.5

Effect of Parameters

ParameterIncreases →Probability Effect
Current Price (S)HigherIncreases P(S_T > K)
Strike Price (K)HigherDecreases P(S_T > K)
Volatility (σ)HigherMoves P toward 0.5 (uncertainty)
Time (T)HigherMoves P toward 0.5 (more diffusion)
Risk-Free Rate (r)HigherIncreases P(S_T > K) (drift)
Volatility Dampening: High volatility broadens the probability distribution, pulling extreme probabilities (near 0 or 1) back toward 0.5. This reflects increased outcome uncertainty.

Usage Example

import { binaryCallProbability } from './probability.js'

// Market state
const currentPrice = 0.52  // Current Polymarket price
const strikePrice = 0.55   // "Will price exceed 0.55?"
const volatility = 0.0002  // Per-second volatility from EWMA
const timeRemaining = 300  // 5 minutes until close

// Compute probability
const prob = binaryCallProbability({
  currentPrice,
  strikePrice,
  volatility,
  timeRemainingSeconds: timeRemaining,
  riskFreeRate: 0  // Crypto markets: no risk-free rate
})

console.log(`P(price > ${strikePrice}) = ${(prob * 100).toFixed(2)}%`)
// Output: P(price > 0.55) = 23.47%

Integration with Engine

The prediction engine calls this function during the base probability calculation step:
predictor.js
const sigma = this._volatility.getVolatility()

const baseProb = binaryCallProbability({
  currentPrice,
  strikePrice,
  volatility: sigma,  // Per-second volatility from EWMA
  timeRemainingSeconds,
  riskFreeRate: 0,
})
This base probability is then adjusted using momentum and reversion signals via logit-space fusion.

Assumptions and Limitations

Assumptions

  1. Geometric Brownian Motion: Price follows dS/S = r dt + σ dW
  2. Constant Volatility: σ does not change over [t, T]
  3. Log-Normal Returns: Log returns are normally distributed
  4. Frictionless Market: No transaction costs or constraints
  5. Continuous Trading: No gaps or jumps

Limitations

Real-World Violations:
  • Volatility Clustering: σ is not constant; markets exhibit volatility regimes
  • Fat Tails: Real returns have heavier tails than the normal distribution
  • Jump Risk: Black swan events violate GBM continuity
  • Microstructure: Tick data contains discrete jumps and bid-ask spreads
The engine mitigates these issues through:
  • EWMA Volatility: Adapts to changing volatility regimes
  • Momentum Signals: Capture short-term trends not explained by GBM
  • Platt Calibration: Corrects systematic probability biases
  • Abstention Logic: Refuses to predict during anomalous regimes

Alternative Formulations

Why d₂ and not d₁?

The Black-Scholes model defines two parameters: d1=d2+σT=ln(S/K)+(r+σ22)TσTd_1 = d_2 + \sigma\sqrt{T} = \frac{\ln(S/K) + \left(r + \frac{\sigma^2}{2}\right) T}{\sigma \sqrt{T}} d2=d1σT=ln(S/K)+(rσ22)TσTd_2 = d_1 - \sigma\sqrt{T} = \frac{\ln(S/K) + \left(r - \frac{\sigma^2}{2}\right) T}{\sigma \sqrt{T}}
  • d₁: Used for delta (option sensitivity to price)
  • d₂: Used for risk-neutral probability
For a binary option, we care about probability under the risk-neutral measure → use d₂.

Digital vs. Cash-or-Nothing

This implementation prices a cash-or-nothing call (pays 1ifST>K,else1 if S_T > K, else 0): Value=erTN(d2)\text{Value} = e^{-rT} \cdot N(d_2) Since r = 0 (crypto markets) and we want probability (not price), the formula simplifies to N(d₂).

References

  • Black, F., & Scholes, M. (1973). “The Pricing of Options and Corporate Liabilities.” Journal of Political Economy, 81(3), 637-654.
  • Abramowitz, M., & Stegun, I. A. (1964). Handbook of Mathematical Functions. National Bureau of Standards.
  • Hull, J. C. (2017). Options, Futures, and Other Derivatives (10th ed.). Pearson.

Next Steps

Volatility Estimation

How the EWMA estimator produces per-second volatility

Prediction Engine

How Black-Scholes integrates with the full prediction pipeline

Build docs developers (and LLMs) love