DEV Community

manja316
manja316

Posted on

86.9% Win Rate Trading Polymarket Crashes: The Algorithm That Detects Panic Selling

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:

  1. A news event triggers fear
  2. Retail traders dump contracts at market price
  3. Price overshoots the "true" probability by 15-40%
  4. Smart money buys the dip
  5. 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),
    }
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. It's behavioral, not technical. I'm trading human psychology, not chart patterns. Humans panic the same way every time.
  2. It requires infrastructure. You need 24/7 monitoring, sub-second data, and automated execution. Most traders won't build this.
  3. 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)