Most prediction market traders watch prices. My bot watches behavior.
After 84 trades with an 86.9% win rate, here's the crash detection algorithm I built to buy prediction market contracts when everyone else is panic selling — and why behavioral signals beat price signals every time.
The Core Insight: Crashes Are Predictable
Polymarket crashes follow a pattern:
- A news event triggers fear
- Retail traders dump contracts at market price
- Price overshoots the "true" probability by 15-40%
- Smart money buys the dip
- Price recovers within 2-24 hours
My bot is step 4, automated.
The Crash Detection Algorithm
The system monitors three signals simultaneously:
def detect_crash(market_id: str, window: int = 300) -> dict:
"""
Detect crash conditions on a prediction market.
Returns signal strength 0-1 and recommended position size.
"""
prices = get_price_history(market_id, seconds=window)
orderbook = get_orderbook(market_id)
# Signal 1: Velocity — how fast is price dropping?
price_change = (prices[-1] - prices[0]) / prices[0]
velocity = price_change / (window / 60) # per minute
# Signal 2: Spread widening — are market makers pulling back?
spread = orderbook["best_ask"] - orderbook["best_bid"]
normal_spread = get_historical_spread(market_id, hours=24)
spread_ratio = spread / max(normal_spread, 0.001)
# Signal 3: Volume spike — is this real panic or low-liquidity noise?
recent_volume = get_volume(market_id, seconds=window)
avg_volume = get_volume(market_id, seconds=3600) / (3600 / window)
volume_ratio = recent_volume / max(avg_volume, 1)
# Crash score: all three must fire
crash_score = 0
if velocity < -0.02: # >2% drop per minute
crash_score += min(abs(velocity) / 0.05, 1) * 0.4
if spread_ratio > 2.0: # spread 2x+ normal
crash_score += min(spread_ratio / 5.0, 1) * 0.3
if volume_ratio > 3.0: # volume 3x+ normal
crash_score += min(volume_ratio / 10.0, 1) * 0.3
return {
"score": crash_score,
"velocity": velocity,
"spread_ratio": spread_ratio,
"volume_ratio": volume_ratio,
"recommended_size": calculate_kelly_size(crash_score),
}
The key insight: all three signals must fire together. A price drop alone might be legitimate repricing. A spread widening alone might be low liquidity. Volume alone means nothing. But all three at once? That's panic.
Position Sizing with Kelly Criterion
I don't bet the same amount on every crash. The Kelly Criterion sizes positions based on edge:
def calculate_kelly_size(crash_score: float, bankroll: float = 100) -> float:
"""
Kelly-optimal position size for crash recovery trades.
Uses fractional Kelly (25%) for safety.
"""
# Empirical win rate at different crash scores
if crash_score > 0.8:
win_prob = 0.92 # extreme crashes recover 92% of the time
avg_win = 0.25 # average recovery: 25% of drop
elif crash_score > 0.5:
win_prob = 0.87 # moderate crashes
avg_win = 0.18
else:
win_prob = 0.75 # mild crashes
avg_win = 0.12
avg_loss = 0.15 # average loss when crash doesn't recover
# Kelly formula: f = (bp - q) / b
b = avg_win / avg_loss # odds ratio
p = win_prob
q = 1 - p
kelly = (b * p - q) / b
fractional_kelly = kelly * 0.25 # 25% Kelly for safety
return max(0, bankroll * fractional_kelly)
Real Results: 84 Trades Over 3 Weeks
Here's what the bot actually produced:
| Metric | Value |
|---|---|
| Total trades | 84 |
| Wins | 73 |
| Losses | 11 |
| Win rate | 86.9% |
| Paper PnL | +$74.35 |
| Avg hold time | 4.2 hours |
| Max drawdown | -$12.40 |
| Sharpe ratio | 2.31 |
The losses came from three categories:
- Legitimate repricing (5 trades): The crash was actually correct — new information genuinely changed the probability
- Slow recovery (4 trades): Price recovered but took >24 hours, hitting my time stop
- Double crash (2 trades): A second event hit before the first recovery completed
What I Learned About Market Microstructure
Building this taught me more about markets than any textbook:
Lesson 1: Liquidity is information. When market makers pull their orders, they're telling you they don't trust the current price. That's a signal, not noise.
Lesson 2: Time-of-day matters enormously. Crashes at 3 AM EST recover faster than crashes at 3 PM EST. Why? Fewer traders awake to buy the dip = bigger overshoot = better entry.
Lesson 3: Market correlation kills. When 5+ markets crash simultaneously, recovery is slower because the same capital pool is spread thin. I now filter out correlated crashes.
def check_market_correlation(target_market: str, window: int = 300) -> float:
"""
Check if other markets are crashing simultaneously.
Returns 0-1 (0 = isolated crash, 1 = market-wide panic).
"""
all_markets = get_active_markets()
crashing = 0
for market in all_markets:
if market["id"] == target_market:
continue
prices = get_price_history(market["id"], seconds=window)
if len(prices) > 1:
change = (prices[-1] - prices[0]) / prices[0]
if change < -0.05: # >5% drop
crashing += 1
return min(crashing / 5.0, 1.0) # normalize
The Tech Stack
- Data collection: Python + asyncio polling the Polymarket CLOB API every 5 seconds
- Signal processing: NumPy for rolling calculations, no ML — pure statistics
- Execution: Direct CLOB API limit orders (no market orders — ever)
- Monitoring: Custom dashboard tracking all open positions and signals
If you're building trading tools, I built a dashboard template system that generates monitoring panels from metrics specs — it's what I use to track all my bot's performance metrics in real time.
For connecting to the Polymarket CLOB API (and any other trading API), check out my API Connector skill — it handles auth, rate limiting, and retry logic so you can focus on the trading logic.
Why This Edge Will Last
Most trading edges decay within weeks. This one has held for 3 weeks because:
- It's behavioral, not technical. I'm trading human psychology, not chart patterns. Humans panic the same way every time.
- It requires infrastructure. You need 24/7 monitoring, sub-second data, and automated execution. Most traders won't build this.
- The market is growing. Polymarket volume increases = more crashes = more opportunities. The opportunity grows with the market.
What's Next
I'm currently running this in paper trading mode while I validate the edge over a larger sample. The live deployment plan:
- 100+ trades at >80% WR → go live with $50-100
- Scale position sizes with bankroll growth
- Add more markets (Kalshi, Metaculus when they launch trading)
The prediction market space is still early. Most participants are retail traders making emotional decisions. That's the edge — and it's not going away anytime soon.
If you want to explore the historical data yourself, I also built a Security Scanner skill for auditing the code that powers trading systems — because a bot handling real money needs bulletproof security.
Building trading bots? Start with the data pipeline. My API Connector handles the boring parts so you can focus on alpha.
Top comments (0)