Skip to main content
A market maker on Polymarket using Black-Scholes binary pricing, spread-based toxicity estimation, and GLFT adverse selection spread with inventory skew.

Full Code

"""GLFT Market Maker on Polymarket (paper mode)."""

import math
import horizon as hz
from horizon.context import FeedData


def black_scholes_binary(price: float, strike: float, vol: float, tte: float) -> float:
    """Binary option fair value: Phi(d2)."""
    if tte <= 0:
        return 1.0 if price >= strike else 0.0
    d2 = (math.log(price / strike) - 0.5 * vol**2 * tte) / (vol * math.sqrt(tte))
    return 0.5 * (1 + math.erf(d2 / math.sqrt(2)))


def spread_toxicity(bid: float, ask: float) -> float:
    """Spread-based toxicity proxy: wider spread = more toxic flow."""
    if bid <= 0 or ask <= 0:
        return 0.5
    mid = (bid + ask) / 2.0
    if mid <= 0:
        return 0.5
    relative_spread = (ask - bid) / mid
    return min(1.0, max(0.0, relative_spread * 100.0))


def glft_spread(fair: float, tox: float, inventory: float, gamma: float = 0.1) -> float:
    """GLFT adverse selection spread with inventory penalty."""
    base = 0.02
    inv_penalty = gamma * abs(inventory) * 0.001
    tox_adj = tox * 0.04
    return base + inv_penalty + tox_adj


# --- Pipeline functions ---

def fair_value(ctx: hz.Context) -> float:
    btc_price = ctx.feeds.get("btc", FeedData()).price or 100_000
    strike = 100_000
    vol = 0.6
    tte = 30 / 365  # 30 days to expiry
    return black_scholes_binary(btc_price, strike, vol, tte)


def toxicity(ctx: hz.Context) -> float:
    feed = ctx.feeds.get("btc", FeedData())
    return spread_toxicity(feed.bid, feed.ask)


def quoter(ctx: hz.Context, fair: float, tox: float) -> list[hz.Quote]:
    spread = glft_spread(fair, tox, ctx.inventory.net, gamma=0.1)
    return hz.quotes(fair, spread, size=5)


if __name__ == "__main__":
    hz.run(
        name="btc_mm",
        exchange=hz.Polymarket(private_key="0x_demo_key"),
        markets=["will-btc-hit-100k-by-end-of-2025"],
        feeds={"btc": hz.BinanceWS("btcusdt")},
        pipeline=[fair_value, toxicity, quoter],
        risk=hz.Risk(max_position=100, max_drawdown_pct=5),
        interval=0.5,
        mode="paper",
        dashboard=False,
    )

Pipeline Breakdown

Stage 1: Fair Value

Uses the Black-Scholes binary option formula to price a prediction market as a digital option:
P(binary) = Φ(d₂)

where d₂ = (ln(S/K) - 0.5σ²T) / (σ√T)
  • S = current BTC price (from Binance feed)
  • K = strike price ($100k)
  • σ = implied volatility (0.6)
  • T = time to expiry (30/365 years)

Stage 2: Toxicity

Uses spread-based toxicity as a proxy for informed flow. A wider bid-ask spread on the underlying suggests more adverse selection:
  • Tight spread → low toxicity → tighter quotes
  • Wide spread → high toxicity → wider quotes

Stage 3: Quoter

The GLFT spread combines three components:
spread = base_spread + γ × |inventory| × 0.001 + toxicity × 0.04
  • Base spread: minimum spread (2 cents)
  • Inventory penalty: widens spread as position grows
  • Toxicity adjustment: widens spread when flow is more informed

Run It

# Paper mode (default)
python examples/polymarket_mm.py

# Live mode
python -m horizon run examples/polymarket_mm.py --mode=live --dashboard