Skip to main content
Pro Feature. Requires a Pro or Ultra subscription. Get started at api.mathematicalcompany.com
What is this? Standard Avellaneda-Stoikov market making operates in probability space (0 to 1), but this causes boundary compression problems - prices near 0 or 1 have distorted spread and risk. Logit-space pricing transforms prices via logit(p) = ln(p/(1-p)), moving calculations to the real line where Gaussian assumptions work properly. The result: correct Greeks, better spreads near boundaries, and a tradable “belief volatility” parameter analogous to implied vol in options.

Logit-Space Pricing

Horizon implements the logit-space pricing framework from Dalen 2025 (“Toward Black-Scholes for Prediction Markets”). All math is in Rust; Python pipeline wrappers handle feed ingestion and context injection.

Logit Transform

hz.logit() / hz.sigmoid() - the core transforms between probability and logit space.

Logit Greeks

hz.logit_greeks() - delta, gamma, theta, belief-vega in the correct mathematical space.

Logit Market Maker

hz.logit_market_maker() - full pipeline factory using logit reservation price and optimal spread.

PnL Decomposition

hz.logit_pnl_decompose() - Taylor decomposition of PnL into directional, curvature, vega, and residual.

Why Logit Space?

In probability space, market making near boundaries (p near 0 or 1) has three problems:
  1. Spread compression: An optimal spread of 0.04 makes no sense at p=0.02
  2. Greek distortion: Delta and gamma behave differently near boundaries
  3. Non-Gaussian dynamics: Price changes aren’t normally distributed in probability space
The logit transform x = ln(p/(1-p)) maps (0,1) to (-inf, +inf), where:
  • Gaussian diffusion assumptions hold
  • Spread scales naturally with p(1-p) (sigmoid derivative)
  • Greeks have clean closed-form expressions

Core Functions

hz.logit / hz.sigmoid

The fundamental transforms.
import horizon as hz

# logit: (0,1) -> (-inf, +inf)
hz.logit(0.5)   # 0.0
hz.logit(0.1)   # -2.197
hz.logit(0.9)   # 2.197

# sigmoid: (-inf, +inf) -> (0,1)
hz.sigmoid(0.0)  # 0.5
hz.sigmoid(-2.0) # 0.119
hz.sigmoid(2.0)  # 0.881

# Inverse property
assert abs(hz.sigmoid(hz.logit(0.73)) - 0.73) < 1e-10

hz.sigmoid_prime / hz.sigmoid_double_prime

Derivatives of the sigmoid function. Used internally for Greeks computation.
hz.sigmoid_prime(0.0)         # 0.25 = p(1-p) at p=0.5
hz.sigmoid_double_prime(0.0)  # 0.0  = p(1-p)(1-2p) at p=0.5

Logit Market Making

hz.logit_reservation_price

Compute the logit-space inventory-skewed fair value.
r = hz.logit_reservation_price(
    mid=0.50,          # Current mid price
    inventory=10.0,    # Net inventory
    gamma=0.5,         # Risk aversion
    belief_vol=0.2,    # Belief volatility
    t=1.0,             # Time horizon
)
# Formula: sigmoid(logit(mid) - q * gamma * sigma_b^2 * T)
ParameterTypeDescription
midfloatCurrent mid price (0-1)
inventoryfloatNet inventory (positive = long)
gammafloatRisk aversion parameter
belief_volfloatBelief volatility (analogous to implied vol)
tfloatTime horizon

hz.logit_optimal_spread

Compute the logit-space optimal bid-ask spread.
spread = hz.logit_optimal_spread(
    belief_vol=0.2,   # Belief volatility
    inventory=0.0,    # Current inventory
    gamma=0.5,        # Risk aversion
    kappa=1.5,        # Order arrival intensity
    t=1.0,            # Time horizon
)
# Logit spread = gamma*sigma_b^2*T + (2/gamma)*ln(1 + gamma/kappa)
# Then scaled to probability space via p(1-p)

hz.toxicity_adjusted_spread

Widen spread based on VPIN toxicity signal. Bridges the VPIN detector to spread adjustment.
adjusted = hz.toxicity_adjusted_spread(
    base_spread=0.04,   # Base spread from model
    vpin=0.7,           # VPIN value (0-1)
    sensitivity=1.0,    # How aggressively to widen (0-10)
)
# Formula: base_spread * (1 + sensitivity * vpin)

Logit Greeks

hz.logit_greeks

Compute prediction market Greeks in logit space.
g = hz.logit_greeks(
    price=0.60,       # Current price
    size=10.0,        # Position size
    is_yes=True,      # YES or NO side
    t_hours=24.0,     # Hours to resolution
    belief_vol=0.2,   # Belief volatility
)
print(f"Delta: {g.delta:.4f}")        # Rate of change w.r.t. price
print(f"Gamma: {g.gamma:.4f}")        # Curvature (convexity)
print(f"Theta: {g.theta:.4f}")        # Time decay
print(f"Belief Vega: {g.belief_vega:.4f}")  # Sensitivity to belief vol
FieldFormulaInterpretation
deltasign * size * p(1-p)Exposure to price moves, naturally scaled by sigmoid derivative
gammasign * size * p(1-p)(1-2p)Convexity; zero at p=0.5, maximal near boundaries
theta-sign * size * p(1-p) * sigma_b^2 / (2T)Time decay from belief volatility
belief_vegasign * size * p(1-p) * 2*sigma_b*TSensitivity to changes in belief vol

LogitGreeks Type

FieldTypeDescription
deltafloatFirst-order price sensitivity
gammafloatSecond-order price sensitivity
thetafloatTime decay
belief_vegafloatBelief volatility sensitivity

PnL Decomposition

hz.logit_pnl_decompose

Taylor decomposition of realized PnL into component contributions.
attr = hz.logit_pnl_decompose(
    p_before=0.40,     # Price at start
    p_after=0.60,      # Price at end
    size=10.0,         # Position size
    is_yes=True,       # YES or NO side
    vol_before=0.20,   # Belief vol at start
    vol_after=0.25,    # Belief vol at end
    corr_before=0.0,   # Correlation at start (optional)
    corr_after=0.0,    # Correlation at end (optional)
)
print(f"Directional: {attr.directional:.4f}")
print(f"Curvature:   {attr.curvature:.4f}")
print(f"Vega PnL:    {attr.belief_vega_pnl:.4f}")
print(f"Corr PnL:    {attr.correlation_vega_pnl:.4f}")
print(f"Residual:    {attr.residual:.4f}")
# Components sum to actual PnL

LogitPnlAttribution Type

FieldTypeDescription
directionalfloatDelta * dx (first-order move)
curvaturefloat0.5 * Gamma * dx^2 (second-order)
belief_vega_pnlfloatVega * (vol_after - vol_before)
correlation_vega_pnlfloatCorrVega * (corr_after - corr_before)
residualfloatActual PnL minus sum of components

Pipeline Integration

hz.logit_market_maker

Full logit-space market maker for hz.run(). Drop-in replacement for hz.market_maker().
hz.run(
    pipeline=[
        hz.logit_market_maker(
            base_spread=0.04,
            gamma=0.3,
            kappa=1.5,
            belief_vol=0.2,
            max_position=100.0,
            use_vpin=True,           # Read VPIN from ctx.params
            vpin_sensitivity=1.5,    # Spread widening factor
        ),
    ],
    markets=[market],
    exchange=exchange,
)

hz.logit_greeks_pipeline

Injects logit Greeks into ctx.params each cycle.
hz.run(
    pipeline=[
        hz.logit_greeks_pipeline(feed_name="binance", belief_vol=0.2, t_hours=24.0),
        my_strategy,  # Can read ctx.params["logit_delta"], etc.
    ],
    ...
)

hz.logit_pnl_attribution_pipeline

Tracks per-market PnL decomposition over time.
hz.run(
    pipeline=[
        hz.logit_pnl_attribution_pipeline(belief_vol=0.2),
        my_strategy,  # Can read ctx.params["logit_pnl_attribution"]
    ],
    ...
)

Greeks-Based PnL Attribution

hz.greeks_attribution

Batch PnL decomposition for multiple positions using logit Greeks.
from horizon import greeks_attribution

positions = engine.positions()
price_before = {"market-a": 0.40, "market-b": 0.55}
price_after = {"market-a": 0.60, "market-b": 0.50}

results = greeks_attribution(positions, price_before, price_after, belief_vol=0.2)
for r in results:
    print(f"{r.market_id}: dir={r.directional:.4f} curv={r.curvature:.4f}")