Skip to main content
Pro Feature. Requires a Pro or Ultra subscription. Get started at api.mathematicalcompany.com
What is this? Options prices on stocks, crypto, and commodities contain information about the market’s probability distribution for future prices. Breeden-Litzenberger (1978) showed you can extract this “risk-neutral density” by taking the second derivative of call prices with respect to strike. Horizon uses this to compare options-implied probabilities against prediction market prices - when they disagree, that’s an edge signal.

Breeden-Litzenberger

Horizon implements the Breeden-Litzenberger theorem to extract risk-neutral probability densities from options chains, then maps these to prediction market edge signals. All computation runs in Rust.

Density Extraction

risk_neutral_density() - extract the full probability distribution from options prices via finite differences.

Probability Queries

implied_probability_above(), below(), between() - integrate the density over arbitrary ranges.

Edge Detection

cross_asset_edge() - compare options-implied probability against prediction market price.

Risk Premium

risk_premium_adjustment() - blend options and market probabilities to account for risk premium differences.

How It Works

Given a set of call option prices C(K) across strikes K:
  1. Compute density: f(K) = e^{rT} * d²C/dK² (Breeden-Litzenberger theorem)
  2. Normalize: Scale so the density integrates to 1.0 via trapezoidal rule
  3. Integrate: CDF = cumulative integral; probability queries integrate over ranges
  4. Compare: If options say P(BTC > 100K) = 0.70 but the prediction market prices it at 0.55, that’s a +0.15 edge

Core Functions

hz.risk_neutral_density

Extract the full risk-neutral probability density from an options chain.
import horizon as hz

# Example: BTC options chain (calls)
strikes = [80000, 85000, 90000, 95000, 100000, 105000, 110000, 115000, 120000]
call_prices = [22000, 17500, 13500, 10000, 7200, 4800, 3000, 1700, 900]

rnd = hz.risk_neutral_density(
    strikes=[float(k) for k in strikes],
    call_prices=[float(c) for c in call_prices],
    r=0.05,    # Risk-free rate
    t=0.25,    # Time to expiry (in years)
)

print(f"Mean: ${rnd.mean:,.0f}")
print(f"Variance: {rnd.variance:,.0f}")
print(f"Density points: {len(rnd.densities)}")
print(f"CDF at last strike: {rnd.cdf[-1]:.4f}")  # Should be ~1.0

RiskNeutralDensity Type

FieldTypeDescription
strikeslist[float]Strike prices
densitieslist[float]Probability density at each strike
cdflist[float]Cumulative distribution function
meanfloatMean of the distribution
variancefloatVariance of the distribution

Probability Queries

hz.implied_probability_above

Probability that the underlying exceeds a threshold.
prob = hz.implied_probability_above(
    strikes=strikes_f,
    call_prices=calls_f,
    threshold=100000.0,    # BTC > $100K?
    r=0.05,
    t=0.25,
)
print(f"P(BTC > $100K) = {prob:.4f}")

hz.implied_probability_below

Probability that the underlying is below a threshold.
prob = hz.implied_probability_below(strikes_f, calls_f, 90000.0, r=0.05, t=0.25)
print(f"P(BTC < $90K) = {prob:.4f}")

hz.implied_probability_between

Probability that the underlying falls within a range.
prob = hz.implied_probability_between(
    strikes_f, calls_f,
    lower=95000.0,
    upper=110000.0,
    r=0.05,
    t=0.25,
)
print(f"P($95K < BTC < $110K) = {prob:.4f}")

Edge Detection

hz.cross_asset_edge

Compare an options-implied probability against a prediction market price.
# Options say P(BTC > $100K) = 0.70
# Prediction market prices it at 0.55
edge = hz.cross_asset_edge(
    option_prob=0.70,
    market_price=0.55,
    transaction_cost=0.005,    # 50 bps round-trip
)
print(f"Raw edge: {edge.raw_edge:+.4f}")          # +0.1500
print(f"Adjusted edge: {edge.adjusted_edge:+.4f}") # +0.1450
print(f"Confidence: {edge.confidence:.4f}")         # High

CrossAssetEdge Type

FieldTypeDescription
option_implied_probfloatProbability from options chain
market_pricefloatPrediction market price
raw_edgefloatoption_prob - market_price
adjusted_edgefloatEdge minus transaction costs
confidencefloatEdge magnitude relative to transaction cost (0-1)

Risk Premium Adjustment

hz.risk_premium_adjustment

Options prices contain a risk premium that prediction markets don’t. This function blends the two probability estimates using a historical calibration ratio.
adjusted = hz.risk_premium_adjustment(
    option_prob=0.70,
    market_price=0.55,
    historical_ratio=0.6,  # 60% weight to options, 40% to market
)
print(f"Blended probability: {adjusted:.4f}")
# 0.70 * 0.6 + 0.55 * 0.4 = 0.64
ParameterTypeDescription
option_probfloatOptions-implied probability
market_pricefloatPrediction market price
historical_ratiofloatWeight on options probability (0-1, default 0.5)

Pipeline Integration

hz.cross_asset_signal

Pipeline factory that reads options chain data from ctx.params and computes edge signals.
hz.run(
    pipeline=[
        options_data_fetcher,  # Populates ctx.params["options_chain"]
        hz.cross_asset_signal(
            options_data_key="options_chain",
            threshold_key="threshold",
            fee_rate=0.005,
        ),
        my_strategy,  # Reads ctx.params["cross_asset_edge"]
    ],
    ...
)
Expects ctx.params["options_chain"] to be:
{
    "strikes": [80000.0, 85000.0, ...],
    "call_prices": [22000.0, 17500.0, ...],
}
Injects into ctx.params:
{
    "cross_asset_edge": {
        "option_implied_prob": 0.70,
        "market_price": 0.55,
        "raw_edge": 0.15,
        "adjusted_edge": 0.145,
        "confidence": 0.97,
    }
}

Example: Cross-Asset Arb Strategy

import horizon as hz

def options_fetcher(ctx):
    """Fetch BTC options chain (placeholder)."""
    ctx.params["options_chain"] = {
        "strikes": [80000, 85000, 90000, 95000, 100000, 105000, 110000],
        "call_prices": [22000, 17500, 13500, 10000, 7200, 4800, 3000],
    }
    ctx.params["threshold"] = 100000.0  # BTC > $100K
    ctx.params["r"] = 0.05
    ctx.params["t"] = 0.25

def edge_trader(ctx):
    """Trade when cross-asset edge exceeds threshold."""
    edge_data = ctx.params.get("cross_asset_edge")
    if edge_data and edge_data["adjusted_edge"] > 0.05:
        # Options say price should be higher -> buy YES
        return [hz.Quote(bid=edge_data["market_price"] + 0.01, ask=0.99, size=10.0)]
    return []

hz.run(
    pipeline=[
        options_fetcher,
        hz.cross_asset_signal("options_chain", "threshold", fee_rate=0.005),
        edge_trader,
    ],
    ...
)