You trade a single YES share on a FIFO limit order book. The contract settles to $1 if a latent score process Zₜ finishes above zero at time T, and $0 otherwise. Prices are integer percentage ticks from 1 to 99 - a bid at tick 48 means you're willing to buy YES at 48¢.
You are a market maker posting passive limit orders on a shared order book. Your goal is to maximize edge - the profit you earn from providing liquidity. You can't send market orders or IOC orders; you can only place resting limit orders and cancel them.
You share the book with a static competitor that maintains a hidden ladder of resting orders around the starting price. The competitor never re-anchors to fair value and never reacts to jumps - it just refills consumed levels at a fixed offset. Your advantage comes from adapting to market conditions it ignores.
For each passive fill, edge measures how much better your execution price was than the true probability at the time of the fill:
Buy q shares at price x: edge = q × (p_t − x) Sell q shares at price x: edge = q × (x − p_t) where p_t = Pr(Z_T > 0 | Z_t) is the true fair value
Retail fills generate positive edge (you captured the spread). Arb fills generate negative edge (the arbitrageur took stale quotes). Your leaderboard score is the mean edge across 200 simulations with randomized parameters. Higher is better.
Each simulation runs 2,000 steps. Before each simulation, hyperparameters (jump intensity, jump variance, retail arrival rate, competitor spread) are sampled randomly - your strategy doesn't know these values and must adapt from observed fills and order flow.
The underlying score follows a Gaussian random walk with compound Poisson jumps:
Z_{t+1} = Z_t + σ·ε_t + Σ J_{t,k}
ε_t ~ N(0, 1) Gaussian diffusion (fixed σ = 0.02)
N_t ~ Poisson(λ_jump) jump count per step
J_{t,k} ~ N(μ_jump, σ_jump²) jump sizesThe diffusion term is fixed across all simulations. The regime-level variation comes entirely from the jump parameters - intensity, mean, and variance - which are randomized per simulation. With ~2 expected jumps per simulation at default intensity, each jump is a significant information shock.
The informed fair value at step t with H steps remaining is:
p_t = Pr(Z_T > 0 | Z_t)
= Σ Poi(n; λ·H) × Φ((Z_t + n·μ) / √(H·σ² + n·σ_jump²))Your strategy does not receive pₜ or Zₜ. You must infer market conditions from fills, your inventory, and the competitor's best bid and ask.
Each step follows this sequence:
on_step callback - you see fills from last step and place new ordersYou quote before the next price move, so you are always exposed to adverse selection. The arb executes before retail, matching the AMM challenge structure.
Three other actors trade on the book:
Strategies are written in Python and must extend BaseStrategy. You implement a single method, on_step, called once per timestep with your current state. Return a list of actions: place limit orders, cancel specific orders, or cancel all.
def on_step(self, state: StepState) -> Sequence[Action]:
# state contains:
# step: int current tick number
# steps_remaining: int steps until settlement
# yes_inventory: float your YES holdings
# no_inventory: float your NO holdings
# cash: float total cash
# reserved_cash: float cash locked in resting orders
# free_cash: float available cash
# competitor_best_bid_ticks: int | None
# competitor_best_ask_ticks: int | None
# fills: tuple[Fill, ...] fills from last step
# own_orders: tuple[OwnOrderView, ...]
return [CancelAll(), PlaceOrder(side=Side.BUY, price_ticks=p, quantity=q), ...]You start with $1,000 cash, 0 YES, and 0 NO. Minting one YES and one NO costs $1. Resting bids reserve price × quantity cash; uncovered asks reserve (1 − price) × quantity.
from orderbook_pm_challenge.strategy import BaseStrategy
from orderbook_pm_challenge.types import CancelAll, PlaceOrder, Side, StepState
class Strategy(BaseStrategy):
"""Minimal static ladder strategy."""
quote_size = 5
def on_step(self, state: StepState):
competitor_bid = state.competitor_best_bid_ticks or 49
competitor_ask = state.competitor_best_ask_ticks or 51
midpoint = (competitor_bid + competitor_ask) // 2
actions = [CancelAll()]
if state.yes_inventory < 100:
actions.append(
PlaceOrder(
side=Side.BUY,
price_ticks=max(1, midpoint - 2),
quantity=self.quote_size,
)
)
if state.free_cash > 0 and state.no_inventory < 100:
actions.append(
PlaceOrder(
side=Side.SELL,
price_ticks=min(99, midpoint + 2),
quantity=self.quote_size,
)
)
return actions