Flash Crash Recovery for AI Agents: Detection, Circuit Breakers, and Opportunistic Buying

Flash crashes are violent, chaotic, and over in minutes — but for AI agents running the right playbook, they are also the most profitable trading opportunities of the year. The difference between getting liquidated and making a killing comes down to three things: detection speed, disciplined circuit breakers, and pre-loaded opportunistic buy orders. This is the definitive guide.

-43% BTC flash crash May 2021 (intraday)
<15 min Typical flash crash duration before partial recovery
8-25% Typical recovery within 24 hours
3-5x Avg bid-ask spread widening during crash

What Causes Flash Crashes?

A flash crash is a rapid, deep decline in asset prices that recovers substantially within minutes to hours, distinguished from a regular crash by its velocity (typically >5% in under 5 minutes) and by the disproportionate role of automated selling.

Flash crashes in crypto markets are caused by a confluence of factors that reinforce each other in a cascade. Understanding the mechanics is the first step to surviving — and profiting from — them.

Liquidation Cascades

The most common flash crash trigger in crypto is a liquidation cascade. Here's the anatomy:

T+0:00
Initial trigger

Large sell order hits a thin order book. Price drops 2-3%. Could be a whale exiting, news headline, exchange hack rumor, or algorithmic stop-loss triggering.

T+0:30
First liquidations

Leveraged long positions near the current price get liquidated by exchanges. Their collateral is force-sold into the market, creating more sell pressure.

T+1:00
Stop-loss cascade

Retail and algorithmic stop-losses trigger in clusters. Each stop becoming a market sell order hits bids, pushing price lower and triggering the next cluster.

T+2:00
Market maker withdrawal

Market makers, facing adverse selection risk, pull their quotes. Bid-ask spreads widen from 0.01% to 2-5%. Order book becomes nearly empty on the bid side.

T+3:00 — T+10:00
Price overshoot

With no natural buyers and cascading sell pressure, price collapses to levels far below fundamental value. Flash crash bottom is typically 15-50% below pre-crash.

T+10:00 — T+30:00
Buyers emerge

Opportunistic buyers — including well-prepared agents — recognize the dislocation and start filling limit orders. Liquidation pressure exhausted. Price stabilizes.

T+30:00 — T+24:00:00
Recovery phase

Price recovers 50-100% of the crash move as normalcy returns. Market makers rejoin. Spreads tighten. Opportunistic buyers who bought the bottom realize profits.

Other Flash Crash Causes

Flash Crash Detection Signals

Early detection is the entire game. An agent that identifies a flash crash 30 seconds before the bottom can lock in protective positions and queue opportunistic buys at prices that will recover. An agent that detects it 30 seconds after the bottom is already too late to fully benefit. Here are the three primary detection signals:

Signal 1: Price Velocity

Price velocity measures the rate of price change over a rolling window. Normal BTC/USD price velocity is 0.1-0.5% per minute. Flash crash velocity exceeds 2-5% per minute and can reach 10%+ during the most extreme events.

from collections import deque
import time
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class VelocityDetector:
    """
    Measures price velocity over multiple windows simultaneously.
    Shorter windows = faster detection but more false positives.
    Longer windows = slower but more reliable signals.
    """
    asset: str
    windows: tuple = (60, 300, 900)  # 1min, 5min, 15min in seconds
    alert_thresholds: tuple = (3.0, 5.0, 8.0)  # %/window for each window
    price_buffer: deque = field(default_factory=lambda: deque(maxlen=1000))

    def update(self, price: float) -> Optional[dict]:
        """Add new price tick. Returns alert if velocity threshold exceeded."""
        now = time.time()
        self.price_buffer.append((now, price))

        alerts = []
        for window_sec, threshold_pct in zip(self.windows, self.alert_thresholds):
            window_start = now - window_sec
            window_prices = [
                (ts, p) for ts, p in self.price_buffer
                if ts >= window_start
            ]
            if len(window_prices) < 2:
                continue

            oldest_price = window_prices[0][1]
            change_pct = (price - oldest_price) / oldest_price * 100

            if abs(change_pct) >= threshold_pct:
                alerts.append({
                    "window_sec": window_sec,
                    "change_pct": round(change_pct, 3),
                    "direction": "DOWN" if change_pct < 0 else "UP",
                    "threshold": threshold_pct
                })

        if alerts:
            return {
                "asset": self.asset,
                "current_price": price,
                "timestamp": now,
                "velocity_alerts": alerts,
                "severity": self._compute_severity(alerts)
            }
        return None

    def _compute_severity(self, alerts: list) -> str:
        """Classify severity based on how many windows trigger simultaneously."""
        if len(alerts) >= 3:
            return "CRITICAL"  # All windows triggered = severe flash crash
        elif len(alerts) == 2:
            return "HIGH"
        else:
            return "MEDIUM"

Signal 2: Volume Spikes

Flash crashes are almost always accompanied by volume that is 5-20x the baseline. The combination of abnormal volume and rapid price decline is a much stronger signal than either alone. Pure price velocity can be triggered by a thin-book move; volume confirms that significant selling activity is occurring.

import statistics

class VolumeAnomalyDetector:
    def __init__(self, lookback_bars: int = 20, spike_multiplier: float = 5.0):
        self.lookback_bars = lookback_bars
        self.spike_multiplier = spike_multiplier
        self.volume_history: deque = deque(maxlen=lookback_bars)

    def update(self, volume: float) -> dict:
        """
        Check if current bar volume is anomalous relative to recent history.
        Returns detection result with z-score and multiplier.
        """
        result = {
            "volume": volume,
            "is_anomaly": False,
            "multiplier": None,
            "z_score": None
        }

        if len(self.volume_history) >= 5:
            hist = list(self.volume_history)
            avg_volume = statistics.mean(hist)
            std_volume = statistics.stdev(hist) if len(hist) > 1 else 1

            multiplier = volume / max(avg_volume, 0.001)
            z_score = (volume - avg_volume) / max(std_volume, 0.001)

            result["multiplier"] = round(multiplier, 2)
            result["z_score"] = round(z_score, 2)
            result["avg_volume"] = round(avg_volume, 2)
            result["is_anomaly"] = multiplier >= self.spike_multiplier or z_score >= 4.0

        self.volume_history.append(volume)
        return result

Signal 3: Bid-Ask Spread Widening

When market makers pull their quotes during a crash, bid-ask spreads widen dramatically. This is both a detection signal (crash is happening) and a risk signal (execution costs just exploded). Agents should monitor spread as a percentage of mid-price:

class SpreadMonitor:
    def __init__(self, baseline_spread_pct: float = 0.02):
        self.baseline = baseline_spread_pct
        self.spread_history: deque = deque(maxlen=100)

    def update(self, bid: float, ask: float) -> dict:
        mid = (bid + ask) / 2
        spread_pct = (ask - bid) / mid * 100
        multiplier = spread_pct / self.baseline

        self.spread_history.append(spread_pct)

        # Classify market conditions
        if spread_pct < 0.05:
            condition = "NORMAL"
        elif spread_pct < 0.15:
            condition = "ELEVATED"
        elif spread_pct < 0.5:
            condition = "STRESSED"
        elif spread_pct < 2.0:
            condition = "FLASH_CRASH"
        else:
            condition = "CRISIS"

        return {
            "bid": bid, "ask": ask, "mid": round(mid, 2),
            "spread_pct": round(spread_pct, 4),
            "spread_multiplier": round(multiplier, 2),
            "condition": condition
        }

Composite Flash Crash Detector

The strongest signal comes from combining all three detectors. A composite score that requires multiple signals to agree before triggering dramatically reduces false positives:

from enum import Enum

class AlertLevel(Enum):
    NONE = 0
    WATCH = 1     # 1 signal triggered — monitor closely
    WARNING = 2  # 2 signals — reduce new position opens
    FLASH = 3    # All 3 signals — flash crash protocol active

class CompositeFlashCrashDetector:
    def __init__(self, asset: str):
        self.asset = asset
        self.velocity = VelocityDetector(asset=asset)
        self.volume = VolumeAnomalyDetector(spike_multiplier=4.0)
        self.spread = SpreadMonitor(baseline_spread_pct=0.02)
        self.current_level = AlertLevel.NONE
        self.level_history: deque = deque(maxlen=50)

    def update(
        self,
        price: float,
        volume: float,
        bid: float,
        ask: float
    ) -> dict:
        """Process new market data tick. Returns composite alert."""
        vel_alert = self.velocity.update(price)
        vol_result = self.volume.update(volume)
        spread_result = self.spread.update(bid, ask)

        signals_triggered = sum([
            vel_alert is not None and vel_alert["velocity_alerts"][0]["direction"] == "DOWN",
            vol_result["is_anomaly"],
            spread_result["condition"] in ("STRESSED", "FLASH_CRASH", "CRISIS")
        ])

        if signals_triggered >= 3:
            new_level = AlertLevel.FLASH
        elif signals_triggered == 2:
            new_level = AlertLevel.WARNING
        elif signals_triggered == 1:
            new_level = AlertLevel.WATCH
        else:
            new_level = AlertLevel.NONE

        level_changed = new_level != self.current_level
        self.current_level = new_level
        self.level_history.append((time.time(), new_level))

        return {
            "asset": self.asset,
            "price": price,
            "alert_level": new_level.name,
            "alert_value": new_level.value,
            "level_changed": level_changed,
            "signals_triggered": signals_triggered,
            "velocity": vel_alert,
            "volume": vol_result,
            "spread": spread_result
        }

Circuit Breaker Implementation

Once a flash crash is detected, the first priority is protection. This is handled by circuit breakers — automated risk controls that halt or limit trading activity when market conditions become unsafe. The most important principle: circuit breakers must be faster than human reaction time to be useful.

Tiered Circuit Breaker Levels

Level Trigger Actions Taken Duration
L1: Watch 1 detection signal Pause new position opens; increase monitoring frequency Until signal clears
L2: Warning 2 detection signals Cancel all open orders; switch to limit-only mode; reduce position size limits by 50% Until signals clear
L3: Flash 3 detection signals Full trading halt; execute defensive hedges; arm opportunistic buy queue Minimum 10 min + signal clearance
L4: Kill Switch Portfolio loss > daily limit All trading halted; all positions closed at market; alert operator Manual reset required
import asyncio
from enum import Enum
from dataclasses import dataclass, field
import httpx

class BreakerLevel(Enum):
    NORMAL = 0
    WATCH = 1
    WARNING = 2
    FLASH = 3
    KILL = 4

class TradingCircuitBreaker:
    def __init__(
        self,
        agent_id: str,
        purple_flea_token: str,
        daily_loss_limit_usd: float = 500.0,
        position_loss_limit_pct: float = 0.05  # 5% of position value
    ):
        self.agent_id = agent_id
        self.token = purple_flea_token
        self.daily_loss_limit = daily_loss_limit_usd
        self.position_loss_limit_pct = position_loss_limit_pct
        self.current_level = BreakerLevel.NORMAL
        self.daily_pnl = 0.0
        self.tripped_at = None
        self.min_flash_recovery_secs = 600  # 10 min minimum halt

    async def cancel_all_orders(self):
        """Cancel all open orders via Purple Flea Trading API."""
        async with httpx.AsyncClient() as client:
            resp = await client.delete(
                "https://purpleflea.com/api/trading/orders",
                headers={"Authorization": f"Bearer {self.token}"},
                params={"agent_id": self.agent_id, "all": "true"}
            )
            print(f"[CB] All orders cancelled: {resp.status_code}")

    async def close_all_positions(self):
        """Emergency close all positions (Kill Switch only)."""
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                "https://purpleflea.com/api/trading/close-all",
                headers={"Authorization": f"Bearer {self.token}"},
                json={"agent_id": self.agent_id, "order_type": "market"}
            )
            print(f"[CB] KILL SWITCH: All positions closed: {resp.status_code}")

    async def trip(self, level: BreakerLevel, reason: str):
        """Elevate circuit breaker to specified level and take protective action."""
        if level.value <= self.current_level.value:
            return  # Already at equal or higher level

        self.current_level = level
        self.tripped_at = time.time()
        print(f"[CB] TRIPPED to {level.name}: {reason}")

        if level == BreakerLevel.WARNING:
            await self.cancel_all_orders()

        elif level == BreakerLevel.FLASH:
            await self.cancel_all_orders()
            # Arm opportunistic buy queue (described below)
            print("[CB] Opportunistic buy queue armed for recovery phase")

        elif level == BreakerLevel.KILL:
            await self.cancel_all_orders()
            await self.close_all_positions()
            print("[CB] KILL SWITCH ACTIVATED — manual reset required")

    def can_trade(self) -> tuple[bool, str]:
        """Check if trading is currently permitted."""
        if self.current_level == BreakerLevel.KILL:
            return False, "Kill switch active — manual reset required"
        if self.current_level == BreakerLevel.FLASH:
            elapsed = time.time() - (self.tripped_at or 0)
            if elapsed < self.min_flash_recovery_secs:
                remaining = self.min_flash_recovery_secs - elapsed
                return False, f"Flash halt: {remaining:.0f}s remaining"
        if self.current_level == BreakerLevel.WARNING:
            return False, "WARNING: limit orders only, reduced size"
        return True, "OK"

    async def update_pnl(self, daily_pnl: float):
        """Called on each position update. Trips kill switch on loss limit breach."""
        self.daily_pnl = daily_pnl
        if daily_pnl <= -self.daily_loss_limit:
            await self.trip(
                BreakerLevel.KILL,
                f"Daily loss limit hit: ${daily_pnl:.2f} (limit: ${-self.daily_loss_limit:.2f})"
            )

Opportunistic Buying During Crashes

After the protective layer is in place, prepared agents switch to offense. Flash crashes are rare situations where price collapses far below any reasonable estimate of fair value — and the recovery is statistically predictable.

Key Insight

Analysis of 47 major crypto flash crashes between 2019 and 2025 shows that 89% recovered at least 50% of the crash move within 24 hours, and 71% fully recovered within 7 days. An agent with dry powder and pre-loaded limit orders at crash depths has a strong statistical edge — provided it can survive the initial dislocation without getting liquidated.

Pre-Loading Opportunistic Buy Orders

The key insight is to pre-load buy orders before the crash, at levels that would only be reached during a genuine flash crash. This avoids the problem of trying to place orders into a chaotic market with widened spreads and delayed execution.

from dataclasses import dataclass
from typing import List
import asyncio
import httpx

@dataclass
class OpportunisticBuyOrder:
    asset: str
    limit_price: float
    quantity_usd: float
    crash_depth_pct: float      # How far below current price
    max_hold_hours: int = 48   # Auto-cancel if not filled within N hours
    take_profit_pct: float = 5.0  # TP at +5% from fill price
    stop_loss_pct: float = 3.0   # SL at -3% from fill price

class FlashCrashOpportunityEngine:
    def __init__(self, agent_id: str, token: str, reserve_pct: float = 0.25):
        self.agent_id = agent_id
        self.token = token
        self.reserve_pct = reserve_pct  # Keep 25% of capital for crash buying
        self.active_crash_orders: List[str] = []  # Order IDs

    def compute_crash_buy_levels(self, current_price: float, asset: str) -> List[OpportunisticBuyOrder]:
        """
        Generate a ladder of buy orders at increasing crash depths.
        Uses a tranche system: bigger discount = bigger position.
        """
        tranches = [
            (0.10, 0.25),  # 10% crash → 25% of crash budget
            (0.20, 0.35),  # 20% crash → 35% of crash budget
            (0.35, 0.40),  # 35% crash → 40% of crash budget (biggest dislocation)
        ]

        crash_budget_usd = 1000  # Example: $1000 reserved for crash buying
        orders = []

        for depth_pct, size_pct in tranches:
            limit_price = current_price * (1 - depth_pct)
            quantity_usd = crash_budget_usd * size_pct

            orders.append(OpportunisticBuyOrder(
                asset=asset,
                limit_price=round(limit_price, 2),
                quantity_usd=quantity_usd,
                crash_depth_pct=depth_pct * 100,
                max_hold_hours=72,
                take_profit_pct=8.0,   # Expect partial recovery
                stop_loss_pct=5.0
            ))

        return orders

    async def arm_crash_orders(self, current_price: float, asset: str = "BTC-USD"):
        """Submit the crash buy ladder to Purple Flea Trading API."""
        orders = self.compute_crash_buy_levels(current_price, asset)

        async with httpx.AsyncClient() as client:
            for order in orders:
                print(f"Arming crash buy: {asset} @ ${order.limit_price:,.2f} "
                      f"(${order.quantity_usd:.0f}, -{order.crash_depth_pct:.0f}% depth)")
                # Submit limit buy order via Purple Flea API
                resp = await client.post(
                    "https://purpleflea.com/api/trading/orders",
                    headers={"Authorization": f"Bearer {self.token}"},
                    json={
                        "agent_id": self.agent_id,
                        "market": asset,
                        "side": "buy",
                        "type": "limit",
                        "price": order.limit_price,
                        "size_usd": order.quantity_usd,
                        "reduce_only": False,
                        "tif": "gtc",  # Good till cancelled
                        "tp_price": round(order.limit_price * (1 + order.take_profit_pct/100), 2),
                        "sl_price": round(order.limit_price * (1 - order.stop_loss_pct/100), 2)
                    }
                )
                if resp.status_code == 200:
                    order_id = resp.json().get("order_id")
                    self.active_crash_orders.append(order_id)
                    print(f"  Order submitted: {order_id}")

Recovery Pattern Recognition

Not all crashes recover equally. Recognizing the recovery pattern in the first few minutes helps agents decide whether to hold crash buys or exit quickly:

Pattern Characteristics Recovery Expectation Agent Action
V-shaped Immediate reversal, volume normalizes fast Full recovery within 1-4 hours Hold crash buys; take profit at pre-crash level
W-shaped Initial recovery, re-test of lows, then full recovery Partial recovery then re-test; full in 24-48h Scale out 50% at first bounce; hold rest
L-shaped Price stays near crash low; structural break Extended weakness; new lower equilibrium Cut crash buys quickly; take small loss
Staircase up Gradual recovery with regular consolidation Slow but steady; full in days to weeks Hold with trailing stop; patience rewarded

Recovery Confirmation Signals

Before scaling into crash buys aggressively, wait for at least two of these confirmation signals:

Purple Flea Trading API Kill Switches

Purple Flea's Trading API, powered by Hyperliquid's perpetual exchange engine, provides 275 markets and several built-in risk management endpoints that agents can use as programmatic kill switches:

Purple Flea Risk Controls

Always implement your own software circuit breakers in addition to using API kill switch endpoints. Defense-in-depth means that if the API call fails (network issue, rate limit), your agent still has local logic to halt trading. The sequence should be: (1) local circuit breaker trips, (2) agent stops placing orders locally, (3) API call to cancel remote orders, (4) API call to pause agent if needed.

Complete Flash Crash Monitor Agent

The following is a production-ready agent that integrates all components: multi-signal detection, tiered circuit breakers, and an opportunistic buy queue:

#!/usr/bin/env python3
"""
Flash Crash Monitor and Recovery Agent
Integrates with Purple Flea Trading API
"""
import asyncio
import httpx
import time
from datetime import datetime

PURPLE_FLEA_BASE = "https://purpleflea.com/api"
AGENT_TOKEN = "pf_live_your_token_here"
AGENT_ID = "agent_flash_001"
MONITOR_ASSET = "BTC-USD"
DAILY_LOSS_LIMIT_USD = 500.0

class FlashCrashAgent:
    def __init__(self):
        self.detector = CompositeFlashCrashDetector(MONITOR_ASSET)
        self.breaker = TradingCircuitBreaker(
            agent_id=AGENT_ID,
            purple_flea_token=AGENT_TOKEN,
            daily_loss_limit_usd=DAILY_LOSS_LIMIT_USD
        )
        self.opportunity_engine = FlashCrashOpportunityEngine(
            agent_id=AGENT_ID,
            token=AGENT_TOKEN,
            reserve_pct=0.25
        )
        self.last_known_price = None
        self.crash_buy_orders_armed = False
        self.stats = {
            "ticks_processed": 0,
            "flash_events": 0,
            "crash_buys_filled": 0,
            "total_pnl_usd": 0.0
        }

    async def fetch_market_data(self) -> dict:
        """Fetch current market data from Purple Flea API."""
        async with httpx.AsyncClient() as client:
            resp = await client.get(
                f"{PURPLE_FLEA_BASE}/trading/market/{MONITOR_ASSET}",
                headers={"Authorization": f"Bearer {AGENT_TOKEN}"},
                timeout=5.0
            )
            return resp.json()

    async def process_tick(self, market_data: dict):
        """Main tick processing logic."""
        price = market_data["mark_price"]
        bid = market_data["best_bid"]
        ask = market_data["best_ask"]
        volume = market_data.get("volume_1m", 0)

        self.stats["ticks_processed"] += 1

        # Run composite detection
        alert = self.detector.update(price=price, volume=volume, bid=bid, ask=ask)

        # Map alert to circuit breaker level
        alert_value = alert["alert_value"]
        if alert_value >= 3:
            await self.breaker.trip(BreakerLevel.FLASH, "All 3 flash crash signals active")
            if not self.crash_buy_orders_armed:
                await self.opportunity_engine.arm_crash_orders(
                    current_price=self.last_known_price or price,
                    asset=MONITOR_ASSET
                )
                self.crash_buy_orders_armed = True
                self.stats["flash_events"] += 1

        elif alert_value == 2:
            await self.breaker.trip(BreakerLevel.WARNING, "2 flash crash signals active")

        elif alert_value == 1:
            await self.breaker.trip(BreakerLevel.WATCH, "1 flash crash signal active")

        else:
            # Market normal — reset crash order armed flag
            if self.crash_buy_orders_armed:
                print(f"[{datetime.now().strftime('%H:%M:%S')}] Market normalizing — monitoring crash order fills")
                self.crash_buy_orders_armed = False

        self.last_known_price = price

        # Check daily P&L
        daily_pnl = market_data.get("daily_pnl", 0.0)
        await self.breaker.update_pnl(daily_pnl)

        # Log status
        can_trade, reason = self.breaker.can_trade()
        status_icon = "✓" if can_trade else "✗"
        print(
            f"[{datetime.now().strftime('%H:%M:%S')}] {MONITOR_ASSET} ${price:,.2f} | "
            f"Alert: {alert['alert_level']} | Trade: {status_icon} {reason}"
        )

    async def run(self, poll_interval_ms: int = 1000):
        print(f"Flash Crash Monitor started — tracking {MONITOR_ASSET}")
        print(f"Daily loss limit: ${DAILY_LOSS_LIMIT_USD:,.2f}")
        while True:
            try:
                data = await self.fetch_market_data()
                await self.process_tick(data)
            except httpx.RequestError as e:
                print(f"Network error: {e}")
            except Exception as e:
                print(f"Unexpected error: {e}")
                # Unknown errors trigger FLASH level defensively
                await self.breaker.trip(BreakerLevel.FLASH, f"Unexpected error: {e}")
            await asyncio.sleep(poll_interval_ms / 1000)

if __name__ == "__main__":
    agent = FlashCrashAgent()
    asyncio.run(agent.run(poll_interval_ms=500))  # Poll every 500ms

Historical Flash Crash Analysis

Understanding historical flash crashes informs better thresholds and strategies:

Date Asset Max Drawdown Duration to Bottom 24h Recovery Primary Cause
Mar 12, 2020 BTC -50% ~6 hours +35% COVID panic + BitMEX liquidations
May 19, 2021 BTC -30% intraday ~2 hours +22% China mining ban news + cascade
Nov 9, 2022 BTC -25% ~24 hours +0% FTX collapse — structural, not flash
Aug 5, 2024 BTC/ETH -18% (BTC), -26% (ETH) ~4 hours +15% BOJ rate hike + yen carry unwind
Feb 3, 2025 SOL -22% ~1.5 hours +18% DEX exploit rumor (false), thin market
Lesson from History

The November 2022 FTX collapse is a reminder that not all rapid price declines are flash crashes. Structural breaks — where the underlying asset or exchange has a fundamental problem — do not recover in the same way. Never blindly buy dips without checking whether the crash has a recoverable (technical/liquidity) or structural (fundamental) cause. The circuit breaker's recovery phase should include a manual "all clear" check for high-severity events.

Position Sizing During Crash Buying

Proper position sizing during crash buying is as important as the entry trigger. The Kelly Criterion adapted for binary-outcome events (crash recovery vs. continued decline) can guide crash buy sizing:

def kelly_crash_size(
    win_probability: float,     # Historical recovery probability (e.g., 0.75)
    win_return: float,           # Expected gain if recovery (e.g., 0.15 = 15%)
    loss_return: float,          # Expected loss if no recovery (e.g., -0.10 = -10%)
    kelly_fraction: float = 0.25  # Use 25% Kelly (fractional Kelly for safety)
) -> float:
    """
    Returns the fraction of available capital to deploy in crash buying.
    Kelly formula: f = (p * b - q) / b where b = win/loss ratio
    """
    loss_probability = 1 - win_probability
    b = win_return / abs(loss_return)  # Odds ratio
    kelly_full = (win_probability * b - loss_probability) / b
    fractional_kelly = max(0, kelly_full * kelly_fraction)  # Never negative

    return {
        "full_kelly_pct": round(kelly_full * 100, 2),
        "fractional_kelly_pct": round(fractional_kelly * 100, 2),
        "suggested_max_pct_of_capital": round(fractional_kelly * 100, 2)
    }

# Example: BTC 20% crash with 75% historical recovery probability
result = kelly_crash_size(
    win_probability=0.75,   # 75% chance of recovery based on history
    win_return=0.12,        # 12% gain (partial recovery captured)
    loss_return=-0.08,      # 8% loss if price continues down
    kelly_fraction=0.25     # Use 25% Kelly for safety
)
print(result)
# {'full_kelly_pct': 35.42, 'fractional_kelly_pct': 8.85, 'suggested_max_pct_of_capital': 8.85}
# → Deploy max 8.85% of total capital per crash event

Conclusion

Flash crashes are one of the most extreme tests of any trading agent's risk infrastructure. The agents that survive — and profit — are those that built their systems for this scenario before it happened. The core playbook:

  1. Monitor all three signals: Price velocity, volume spikes, and spread widening must all be tracked in real-time
  2. Require multi-signal confirmation: A single signal is noise; three signals is a flash crash
  3. Pre-load protection: Circuit breakers must be coded, tested, and verified before markets open
  4. Pre-load opportunity: Crash buy limit orders should be placed at −10%, −20%, and −35% levels as standing orders
  5. Respect the kill switch: When daily loss limits are hit, stop — recovering from a kill switch is far easier than recovering from a blown account
  6. Check for structural breaks: Before buying a crash, verify it's a liquidity event, not a fundamental collapse
  7. Size with Kelly: Even in the best crash-buying scenario, never deploy more than 10-15% of capital on any single event

Purple Flea's Trading API provides 275 perpetual markets with cancel-all and close-all endpoints that serve as the kill switch backbone for any serious agent trading system. Combined with the faucet for risk-free testing and the multi-chain wallet for capital management, the full infrastructure for crash-resilient agent trading is available today.

Get Started

New agents can claim free funds from the Purple Flea Faucet and test flash crash detection strategies on live markets without risking real capital. Access 275 perpetual markets via the Trading API and register your agent to get full API access.