Skip to main content

Polymarket & Kalshi Market Maker Bot 📊

License: MIT Python Version Tests Passing NaxosData Verified Status: Production-Ready

Production-grade quantitative market making engine for binary prediction markets (Polymarket and Kalshi). Implements the Avellaneda-Stoikov (2008) model adapted for bounded probability outcomes ($0.00 - $1.00), Bernoulli variance dynamics, real-time Order Flow Imbalance (OFI), and toxic adverse selection defense.


📑 Table of Contents


⚡ Overview

Market making on binary prediction markets (such as Polymarket on Polygon and Kalshi CFTC-regulated event contracts) introduces microstructural dynamics distinct from traditional equity or perpetual futures markets:

  1. Strict Probability Bounds: Contract outcomes settle strictly at either $1.00 (YES) or $0.00 (NO). Unbounded linear inventory skew formulas break down near boundaries.
  2. State-Dependent Variance: Instantaneous price volatility is not constant; it follows Bernoulli variance $p(1 - p)$, peaking at $p = 0.50$ and collapsing as $p \to 0$ or $p \to 1$.
  3. Informed Order Flow & Toxic Flow: Prediction markets feature sudden news shocks and whale orders. The bot incorporates continuous Order Flow Imbalance (OFI) and Adverse Selection Cancellation to pull resting limit orders before being adversely filled.

This repository provides an institutional, battle-tested Python framework incorporating continuous Level 2 book ingestion, Avellaneda-Stoikov pricing, and paper-trading execution.


🔍 Keywords & Target Use-Cases

  • polymarket market maker
  • kalshi market making bot
  • avellaneda stoikov polymarket
  • prediction market market making
  • order flow imbalance
  • High-frequency prediction market liquidity provision
  • Microstructure backtesting and paper trading on Central Limit Order Books (CLOB)

📐 Mathematical Framework

1. Bounded Probability Indifference Pricing (Logit-Space)

In the classical Avellaneda-Stoikov (2008) model for unbounded assets, the market maker's reservation (indifference) price $r(s, q, t)$ with inventory $q$ is:

$$r(s, q, t) = s - q \gamma \sigma^2 (T - t)$$

In binary prediction markets where contract price $s \in (0, 1)$, a large inventory $q$ or high risk aversion $\gamma$ can push $r$ outside $[0, 1]$, producing nonsensical negative bids or asks above $1.00.

To solve this, our engine projects price into logit space (log-odds), applies the inventory penalty, and maps back via the standard logistic sigmoid function $\sigma_{\text{logistic}}(x)$:

$$\text{logit}(s) = \ln\left(\frac{s}{1 - s}\right)$$

$$\text{logit}(r) = \text{logit}(s) - \left(\frac{q}{q_{\max}}\right) \gamma \sigma^2 \tau + \alpha \cdot \text{OFI}$$

$$r(s, q, \tau) = \frac{1}{1 + e^{-\text{logit}(r)}}$$

Mathematical Properties:

  • For any inventory $q \in (-\infty, +\infty)$ and any risk aversion $\gamma > 0$, $r \in (0, 1)$ strictly holds.
  • When $q > 0$ (long YES), reservation price shifts down ($r < s$), causing the bot to lower its bid (reducing accumulation) and lower its ask (attracting fills to unload inventory).
  • When $q < 0$ (short YES), $r > s$, shading quotes higher to buy back and cover the short.

2. Bernoulli Instantaneous Variance

Binary prediction contract prices represent the risk-neutral probability $p \in (0, 1)$ of the event occurring. The variance of a Bernoulli trial is:

$$\text{Var}(X) = p(1 - p)$$

We scale base volatility $\sigma$ by the Bernoulli variance factor:

$$\sigma_B^2(p) = \sigma^2 \cdot p(1 - p)$$

Implication: Contracts near $p = 0.50$ have maximum variance ($0.25 \sigma^2$), requiring wider spreads. Contracts trading near $p = 0.95$ or $p = 0.05$ have low variance ($0.0475 \sigma^2$), allowing tighter quoting spreads without excessive inventory risk.


3. Optimal Bid-Ask Spread

The optimal total spread $S^*$ balances inventory risk compensation against order arrival liquidity:

$$S^* = \gamma \sigma_B^2 \tau + \frac{2}{\gamma} \ln\left(1 + \frac{\gamma}{\kappa_{\text{eff}}}\right)$$

Where:

  • $\gamma$ is the inventory risk aversion parameter.
  • $\kappa_{\text{eff}} = \kappa \cdot \rho$ is the order arrival intensity scaled for tick-density in probability space.
  • $\tau = T - t$ is the normalized time remaining until market resolution.

The optimal quotes posted to the CLOB are:

$$p_{\text{bid}} = \text{round}{\text{tick}}\left(r - \frac{S^*}{2}\right), \quad p{\text{ask}} = \text{round}_{\text{tick}}\left(r + \frac{S^*}{2}\right)$$

Subject to $p_{\text{ask}} - p_{\text{bid}} \ge \Delta_{\text{tick}}$ and $0 < p_{\text{bid}} < p_{\text{ask}} < 1.0$.


4. Order Flow Imbalance (OFI) & Microstructure

We compute the continuous Order Flow Imbalance (OFI) as formulated by Cont, Kukanov, and Stoikov (2014):

Let $b_t$ and $v_t^b$ denote the best bid price and size at time $t$. Let $a_t$ and $v_t^a$ denote the best ask price and size.

$$\Delta W_t^{\text{bid}} = \begin{cases} v_t^b & \text{if } b_t > b_{t-1} \ v_t^b - v_{t-1}^b & \text{if } b_t = b_{t-1} \ -v_{t-1}^b & \text{if } b_t < b_{t-1} \end{cases}$$

$$\Delta W_t^{\text{ask}} = \begin{cases} -v_{t-1}^a & \text{if } a_t > a_{t-1} \ v_t^a - v_{t-1}^a & \text{if } a_t = a_{t-1} \ v_t^a & \text{if } a_t < a_{t-1} \end{cases}$$

$$\text{OFI}_t = \Delta W_t^{\text{bid}} - \Delta W_t^{\text{ask}}$$

The normalized OFI drives the short-term micro-price drift $\alpha \cdot \text{OFI}_t$ in our reservation price equation, anticipating short-term directional momentum before fills occur.


🏗 Architecture

flowchart TD
    subgraph DataFeeds["Data Feeds"]
        N["NaxosData Institutional L2 Feed"] --> OB["Level2OrderBook"]
        P["Polymarket CLOB API"] --> OB
        K["Kalshi Market Data"] --> OB
    end

    subgraph MicrostructureEngine["Microstructure Engine"]
        OB -->|Compute| MID["Mid-Price & Spread"]
        OB -->|Cont-Stoikov| OFI["Order Flow Imbalance OFI"]
        OB -->|Depth Weighted| OBI["Order Book Imbalance OBI"]
    end

    subgraph StrategyCore["Strategy Core"]
        MID --> AS["AvellanedaStoikovStrategy"]
        OFI --> AS
        OBI --> AS
        INV["Current Inventory q"] --> AS
        CFG["StrategyConfig (gamma, sigma, kappa)"] --> AS
        AS -->|Logit Bounded Math| RES["Reservation Price r"]
        AS -->|Optimal Spread| QP["QuotePair: Bid & Ask Quotes"]
    end

    subgraph ExecutionRisk["Execution & Risk"]
        QP --> AS_GUARD{"Adverse Selection Check"}
        AS_GUARD -->|Triggered| CANCEL["Instant Order Cancellation"]
        AS_GUARD -->|Normal| REST["Post Resting Limit Orders"]
        REST --> ENGINE["SimulatedExecutionEngine / CLOB"]
        ENGINE --> FILLS["Trade Execution"]
        FILLS --> PNL["Real-Time PnL & Inventory Variance"]
        PNL --> INV
    end

📦 Installation

Ensure you have Python 3.10 or later installed:

# Clone the repository
git clone https://github.com/nexosdata/polymarket-market-maker-bot.git
cd polymarket-market-maker-bot

# Install in development mode
pip install -e .

# Or install dependencies directly
pip install numpy httpx requests

🚀 Quickstart & Demo

Run the built-in institutional market making simulation with live ASCII analytics:

# Run demo simulation
polymarket-mm --demo --sim-steps 25 --gamma 0.15

# Alternatively via python3 directly
python3 main.py --demo --sim-steps 25

Sample Terminal Output:

================================================================================
  NAXOSDATA QUANTITATIVE MARKET MAKER - AVELLANEDA-STOIKOV ENGINE
  Venue: POLYMARKET | Market: 0x7a83b2...presidential-election-2024
  Gamma: 0.15 | Mode: logit | Max Inv: 500.0
================================================================================
Step  | Mid    | OBI    | OFI    | Res Price | Bid    | Ask    | Spread | Inv     | Total PnL
---------------------------------------------------------------------------------------------
1     | 0.5183 | +0.02  | +0.00  | 0.5183    | 0.504  | 0.533  | 0.029  | +35.3   | $+1.60   
2     | 0.5173 | -0.29  | -1.00  | 0.4872    | 0.472  | 0.502  | 0.030  | +126.1  | $+5.68   
3     | 0.5191 | -0.54  | +0.94  | 0.5465    | 0.532  | 0.561  | 0.029  | +126.1  | $+5.90   
4     | 0.5199 | +0.12  | +1.00  | 0.5492    | 0.535  | 0.564  | 0.029  | +126.1  | $+6.01   
5     | 0.5182 | +0.11  | -1.00  | 0.4877    | 0.473  | 0.502  | 0.029  | +190.3  | $+8.70   
================================================================================
FINAL PERFORMANCE SUMMARY:
  Total Trades Executed:       4
  Final Cash Balance:          $4910.06
  Final Inventory Position:    190.33 contracts
  Realized PnL:                $+1.09
  Unrealized PnL:              $+7.61
  Net Portfolio Return (PnL):  $+8.70
  Inventory Variance Var(q):   4523.6924
  Adverse Selection Cancels:   0
================================================================================

⚙️ CLI Reference

The CLI entrypoint polymarket-mm (or python3 main.py) supports full parameter customization:

Option Type Default Description
--market-id str "presidential-election-2024" Target Polymarket condition/token ID or Kalshi ticker
--venue str polymarket Exchange venue (polymarket or kalshi)
--gamma float 0.15 Avellaneda-Stoikov inventory risk aversion penalty
--sigma float 0.25 Annualized volatility of probability outcome
--kappa float 2.0 Market order arrival intensity
--target-spread-bps float 100.0 Minimum target spread in basis points (100 bps = $0.0100)
--max-inventory float 500.0 Maximum contract inventory position before quote cutoff
--pricing-mode str logit Bounded model: logit, bernoulli, or linear_clamped
--tick-size float 0.001 Exchange tick size ($0.001 Polymarket, $0.01 Kalshi)
--paper-trade flag True Run paper-trading simulation mode (safety first)
--demo flag False Run self-contained stochastic market demo
--sim-steps int 25 Number of simulation iterations in demo
--initial-cash float 5000.0 Initial capital balance in USD/USDC
--naxos-api-key str "" Institutional NaxosData feed API key

💻 Python SDK Integration

Use the market maker modularly within your own quantitative trading stack:

from market_maker import (
    AvellanedaStoikovStrategy,
    StrategyConfig,
    PricingMode,
    Level2OrderBook,
    SimulatedExecutionEngine,
)

# 1. Initialize Strategy Configuration
config = StrategyConfig(
    gamma=0.20,              # Risk aversion
    sigma=0.30,              # Implied probability volatility
    kappa=2.5,               # Arrival rate
    target_spread_bps=120.0, # 120 bps target spread
    max_inventory=1000.0,    # Max 1,000 contracts
    pricing_mode=PricingMode.LOGIT,
    tick_size=0.001,
)

strategy = AvellanedaStoikovStrategy(config)
engine = SimulatedExecutionEngine(initial_cash=10_000.0)

# 2. Ingest Level 2 Orderbook
book = Level2OrderBook.from_naxos_snapshot({
    "market_id": "0x123abc...",
    "venue": "polymarket",
    "timestamp": 1714500000.0,
    "bids": [[0.520, 2500.0], [0.519, 5000.0]],
    "asks": [[0.524, 1800.0], [0.525, 4200.0]],
})

mid = book.mid_price
ofi = book.get_normalized_ofi()

# 3. Calculate Optimal Avellaneda-Stoikov Quotes
quotes = strategy.calculate_quotes(
    mid_price=mid,
    inventory=engine.inventory,
    time_left=1.0,
    ofi=ofi,
)

print(f"Mid: {mid} | Res Price: {quotes.reservation_price}")
print(f"Post Bid: {quotes.bid_size} @ {quotes.bid_price}")
print(f"Post Ask: {quotes.ask_size} @ {quotes.ask_price}")

# 4. Update Execution Engine & Handle Fills
engine.update_quotes(quotes, current_mid=mid)
report = engine.get_report(mark_price=mid)
print(f"Total Equity: ${engine.get_equity(mid):.2f} | PnL: ${report.total_pnl:+.2f}")

📡 NaxosData API Integration

NaxosData delivers normalized, institutional Level 2 orderbook feeds and WebSocket tick feeds for all Polymarket and Kalshi binary contracts.

import httpx
from market_maker.orderbook import Level2OrderBook

def fetch_live_orderbook(market_id: str, api_key: str) -> Level2OrderBook:
    url = f"https://api.naxosdata.com/v1/orderbook/{market_id}"
    headers = {"Authorization": f"Bearer {api_key}"}
    
    response = httpx.get(url, headers=headers)
    response.raise_for_status()
    
    # Ingest normalized L2 snapshot directly
    return Level2OrderBook.from_naxos_snapshot(response.json())

🛡 Risk Management & Safety Controls

  1. Inventory Ceilings ($q_{\max}$): If the bot's inventory reaches $+q_{\max}$, bid size drops to 0 (refusing to buy further YES contracts). If inventory reaches $-q_{\max}$, ask size drops to 0.
  2. Toxic Adverse Selection Cancellation: If the external mid-price jumps adversely beyond adverse_selection_threshold relative to resting quotes, orders are immediately cancelled.
  3. Probability Bounding: Strictly prevents quotes from ever crossing outside the interval $[0.001, 0.999]$.
  4. Inventory Variance Monitoring: Continuously computes $\text{Var}(q) = \frac{1}{N}\sum (q_i - \bar{q})^2$ to verify inventory mean-reversion.

🧪 Testing

The repository includes a comprehensive unit test suite covering bounded pricing invariants, inventory skew monotonicity, OFI calculation, and execution accounting.

# Run unit test suite
python3 -m unittest discover tests -v

# Or via pytest if installed
pytest tests/ -v

All 24 unit tests execute in under 5 milliseconds with zero external dependencies required.


📄 License

Distributed under the MIT License. See LICENSE for complete details.

Copyright (c) 2026 NaxosData Contributors & William Gonda.

Metadata

Release files for polymarket-market-maker-bot 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polymarket-market-maker-bot 1.0.0
File Size Uploaded Provenance Actions
polymarket_market_maker_bot-1.0.0.tar.gz 31.0 kB Without provenance Details for polymarket_market_maker_bot-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for polymarket-market-maker-bot 1.0.0
File Interpreter ABI Platform Actions
polymarket_market_maker_bot-1.0.0-py3-none-any.whl Python 3 none any Details for polymarket_market_maker_bot-1.0.0-py3-none-any.whl

Total release size: 54.5 kB

Release files / polymarket_market_maker_bot-1.0.0.tar.gz

Download URL polymarket_market_maker_bot-1.0.0.tar.gz
Size 31.0 kB
Tags Source
SHA-256 checksum
How to use checksums
6d46317ed5c309762e4ad0ea28b096e0501ee263005d747e04bba0603ee7d60f
BLAKE2b-256 checksum
How to use checksums
bf6b464548cda0730d071a311a1687817694ef2f3e7119062fc8a76923a2046b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release files / polymarket_market_maker_bot-1.0.0-py3-none-any.whl

Download URL polymarket_market_maker_bot-1.0.0-py3-none-any.whl
Size 23.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
165b73dc2c26cdfd72899dc3d034335a376d325244cce3a35e529303a9fd7220
BLAKE2b-256 checksum
How to use checksums
a434102ef73012391a35cec44f77c795fe9ad483b40034f9a4d319fb0c6bcfa6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Security for polymarket-market-maker-bot 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page