A Python-based algorithmic trading competition by IMC Trading (one of the world's top HFT firms). You build trading bots that compete in a simulated virtual marke…
Connected to the competition feed. Verify dates, eligibility and prizes on the official page.
No open IMC Prosperity competitions in the feed right now.
The feed refreshes daily. Browse every tracked platform or check again soon.
Subscribe once — every open competition's deadline shows up in your native calendar and auto-updates daily as new ones open and old ones close. No account needed.
Outlook or another app: copy the feed URL, then add a calendar from URL.
A Python-based algorithmic trading competition by IMC Trading (one of the world's top HFT firms). You build trading bots that compete in a simulated virtual market across 5 rounds. Combines algorithmic trading + market microstructure + strategy.
prosperity.imc.com
A Python Trader class with a run() method. Each round introduces new tradeable products with different market dynamics. Your bot must:
| Concept | Why It Matters |
|---|---|
| Bid/Ask spread | How markets work. You trade inside it to profit. |
| Order book | Shows all pending orders at each price level |
| Market making | Buy at bid, sell at ask. Profit = spread. |
| Position limits | You can't hold unlimited inventory — manage risk |
| Mean reversion | Prices revert to fair value — exploit this |
| Pair trading | Two correlated assets diverge → trade the spread |
Place bids just below fair value, asks just above. Simple. Reliable. Low-risk.
# If fair_value is known (stable asset):
orders.append(Order(product, fair_value - 1, buy_volume)) # bid
orders.append(Order(product, fair_value + 1, -sell_volume)) # ask
The "edge" is the distance between your trade price and true fair value. Maximize this consistently.
When no fixed fair value exists, estimate it dynamically.
from collections import deque
class EMATrader:
def __init__(self, window=8):
self.prices = deque(maxlen=window)
self.ema = None
self.alpha = 2 / (window + 1)
def update(self, mid_price):
if self.ema is None:
self.ema = mid_price
else:
self.ema = self.alpha * mid_price + (1 - self.alpha) * self.ema
def signal(self, ask, bid):
if ask < self.ema: return "BUY"
if bid > self.ema: return "SELL"
return "HOLD"
EMA window (~8 for Kelp) is a key tunable parameter — backtest it.
import numpy as np
from collections import deque
class ZScoreReversion:
def __init__(self, short_window=5, long_window=20):
self.short = deque(maxlen=short_window)
self.long = deque(maxlen=long_window)
def update(self, price):
self.short.append(price)
self.long.append(price)
def z_score(self):
if len(self.long) < self.long.maxlen:
return 0
ema_short = np.mean(self.short)
ema_long = np.mean(self.long)
std_long = np.std(self.long) + 1e-9
return (ema_short - ema_long) / std_long
def signal(self, threshold=1.5):
z = self.z_score()
if z > threshold: return "SELL" # Overbought — reversion expected
if z < -threshold: return "BUY" # Oversold — reversion expected
return "HOLD"
When a basket product trades at a price diverging from its components:
from sklearn.linear_model import LinearRegression
import numpy as np
# Fit weights of components to basket price
def fit_basket_weights(component_prices, basket_prices):
model = LinearRegression(fit_intercept=True)
model.fit(component_prices, basket_prices)
return model
# At runtime: compute synthetic fair value
def basket_signal(model, component_prices, basket_mid, threshold=1.5):
synthetic = model.predict(component_prices.reshape(1, -1))[0]
spread = basket_mid - synthetic
std = np.std(recent_spreads) # rolling std of spread
z = spread / (std + 1e-9)
if z > threshold: return "SELL basket"
if z < -threshold: return "BUY basket"
return "HOLD"
Alpha Animals (2nd place USA, 9th globally, Prosperity 3) used this to achieve rank 2 in Round 2.
Treat competition vouchers as call options using Black-Scholes. Compute implied volatility from market prices and exploit deviations.
from scipy.stats import norm
import numpy as np
def black_scholes_call(S, K, T, r, sigma):
"""S=spot, K=strike, T=time to expiry, r=rate (0), sigma=IV"""
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
return S * norm.cdf(d1) - K * np.exp(-r*T) * norm.cdf(d2)
def implied_vol(market_price, S, K, T, r=0.0, tol=1e-5):
"""Invert Black-Scholes for IV using bisection."""
lo, hi = 0.001, 5.0
for _ in range(100):
mid = (lo + hi) / 2
if black_scholes_call(S, K, T, r, mid) > market_price:
hi = mid
else:
lo = mid
if hi - lo < tol:
break
return mid
When counterparty data is revealed, track individual trader win rates.
| Strategy | Asset Type | Risk | Complexity |
|---|---|---|---|
| Market making | Stable (known fair value) | Low | Low |
| EMA fair value | Moderately volatile | Medium | Low |
| Z-score mean reversion | Highly volatile | Medium | Medium |
| Basket arbitrage | Index/basket products | Medium | High |
| Options pricing | Vouchers/derivatives | High | High |
| Counterparty copying | Any | Medium | Medium |
| Product | Strategy | PnL (2nd place) |
|---|---|---|
| Rainforest Resin | Fixed fair value (~10,000) market making ±2.5 spread | ~39,000 SeaShells/round |
| Kelp | 8-period SMA as fair value; market make around deviation | ~5,000 SeaShells/round |
| Squid Ink | EMA z-score mean reversion OR volatility spike detection | ~8,000 SeaShells/round |
2nd place (Frankfurt Hedgehogs) used "Wall Mid": (best_bid + best_ask)/2 as fair value proxy with inventory flattening when position skews.
Frankfurt Hedgehogs key insight: Baskets mean-revert vs synthetic value, but components do NOT move to match basket → trade baskets only, don't hedge with components.
def basket_arb(basket_mid, croissant_mid, jam_mid, djembe_mid, threshold=50):
synthetic = 6*croissant_mid + 3*jam_mid + 1*djembe_mid
spread = basket_mid - synthetic
if spread > threshold: return "SELL basket"
if spread < -threshold: return "BUY basket"
return "HOLD"
Fixed threshold (±50) outperformed z-score for this product — stability over peak performance.
This is the options round. Every top team used Black-Scholes.
from scipy.stats import norm
import numpy as np
def black_scholes_call(S, K, T, sigma, r=0.0):
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T) + 1e-9)
d2 = d1 - sigma*np.sqrt(T)
return S*norm.cdf(d1) - K*np.exp(-r*T)*norm.cdf(d2)
def implied_vol(market_price, S, K, T, r=0.0, tol=1e-5):
lo, hi = 0.001, 5.0
for _ in range(100):
mid = (lo + hi) / 2
if black_scholes_call(S, K, T, mid, r) > market_price: hi = mid
else: lo = mid
if hi - lo < tol: break
return (lo + hi) / 2
# Greeks for risk management
def delta(S, K, T, sigma, r=0.0):
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T) + 1e-9)
return norm.cdf(d1)
def gamma(S, K, T, sigma, r=0.0):
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T) + 1e-9)
return norm.pdf(d1) / (S * sigma * np.sqrt(T) + 1e-9)
def vega(S, K, T, sigma, r=0.0):
d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T) + 1e-9)
return S * norm.pdf(d1) * np.sqrt(T)
Volatility smile exploitation (2nd place globally):
ln(S/K)/√T → parabolic smile appearsbest_bid_volume > 9 on local exchange, sell order at best ask guaranteed to fill# Track counterparty win rate
class InsiderDetector:
def __init__(self, window=50):
self.trades = {} # {trader_name: deque of (buy/sell, subsequent_price_move)}
self.window = window
def update(self, trader, direction, price_before, price_after):
if trader not in self.trades:
from collections import deque
self.trades[trader] = deque(maxlen=self.window)
profitable = (direction == 'BUY' and price_after > price_before) or \
(direction == 'SELL' and price_after < price_before)
self.trades[trader].append(profitable)
def win_rate(self, trader):
if trader not in self.trades or len(self.trades[trader]) == 0:
return 0.5
return sum(self.trades[trader]) / len(self.trades[trader])
def is_insider(self, trader, threshold=0.70):
return self.win_rate(trader) > threshold
# Olivia had >70% win rate — copy her trades as regime signals
Install the community-standard backtester:
pip install -U prosperity3bt
prosperity3bt algorithm.py 1 # all data from round 1
prosperity3bt algorithm.py 1 --vis # auto-open visualizer
prosperity3bt algorithm.py 1 2 3 # multiple rounds
P3 Visualizer: jmerle.github.io/imc-prosperity-3-visualizer/
[ ] pip install prosperity3bt and run all Prosperity 3 round data
[ ] Clone and read: github.com/TimoDiehm/imc-prosperity-3 (2nd globally)
[ ] Clone and read: github.com/CarterT27/imc-prosperity-3 (9th global, 2nd USA)
[ ] Implement Black-Scholes + Greeks from scratch (you will need this)
[ ] Implement implied vol solver (Newton-Raphson or bisection)
[ ] Build EMA + z-score mean reversion detector
[ ] Build market maker with inventory management
[ ] Build insider/counterparty win-rate tracker
[ ] Study Round 1 manual challenge patterns from Prosperity 2 and 3
[ ] Understand: basket fair value from components (linear regression)
| Resource | URL |
|---|---|
| Frankfurt Hedgehogs — 2nd globally (code + blog) | github.com/TimoDiehm/imc-prosperity-3 |
| Alpha Animals — 9th global, 2nd USA (code) | github.com/CarterT27/imc-prosperity-3 |
| Community backtester | github.com/jmerle/imc-prosperity-3-backtester |
| IMC Prosperity 3 writeup (Matius Chong) | medium.com/@matius_chong/imc-prosperity-3-challenge-2025 |
| IMC Prosperity 3 writeup (Martin Oravec, 73rd) | medium.com/@oravec.martin01/imc-prosperity-3-be859180f133 |
| Top 100 strategies explained | medium.com/@shriyan.gosavi/how-i-placed-top-100-in-the-imc-trading-challenge |
| Official platform | prosperity.imc.com |