High-Frequency Trading and Latency Arbitrage › Market Impact and Flash Crashes

Michael BrenndoerferJanuary 1, 20266 min read
Quantitative FinanceAll chapters
Part 12 of 15Market Making and High-Frequency Trading
Chapter 66High-Frequency Trading and Latency Arbitrage › Market Impact and Flash Crashes
Reading section 15 of 18 · Market Impact and Flash Crashes · 6 minSection 15 of 18 · 6 min

Book contents

Section 15 of 18 · 6 min read

High-Frequency Trading and Latency Arbitrage › Market Impact and Flash CrashesOne section · Sections in this chapter15 / 18 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

HFT has fundamentally changed market dynamics, with both positive and negative effects.

Positive Contributions

Positive contributions include:

  • Improved liquidity: HFT market makers provide continuous quotes across many securities and market conditions, reducing bid-ask spreads for all market participants. Since HFT became prevalent, spreads have declined dramatically. Academic research estimates that spreads for liquid securities have narrowed by 50% or more, with particularly active securities seeing even greater improvements.

  • Price efficiency: By quickly arbitraging price discrepancies, HFT helps ensure that the same security trades at similar prices across venues, improving market efficiency.

  • Lower transaction costs: Tighter spreads and improved price discovery have reduced trading costs for all market participants.

Negative Effects and Risks

Negative effects and risks include:

  • Flash crashes: On May 6, 2010, the Dow Jones Industrial Average dropped nearly 1,000 points (about 9%) in minutes before recovering. The event was triggered by a large sell order that destabilized HFT market makers, who withdrew liquidity en masse.

  • Liquidity illusion: HFT liquidity can disappear instantly during stress, leaving markets illiquid precisely when liquidity is most needed. The 2010 Flash Crash and subsequent events demonstrated this vulnerability.

  • Arms race costs: The resources devoted to achieving marginal speed improvements (microwave towers, submarine cables, FPGA development) represent significant social costs with debatable benefits.

In[31]:
Code
def simulate_flash_crash(
    n_periods: int = 200,
    n_market_makers: int = 10,
    initial_price: float = 100.0,
    normal_volatility: float = 0.05,
    shock_magnitude: float = 2.0,  # Standard deviations
    mm_withdrawal_threshold: float = 1.5,  # Volatility multiple to withdraw
    mm_risk_limit: float = 500,  # Inventory limit before withdrawal
):
    """
    Simulate a flash crash scenario with HFT market maker withdrawal.

    Parameters:
    -----------
    n_periods : Number of time periods
    n_market_makers : Number of HFT market makers
    initial_price : Starting price
    normal_volatility : Normal volatility per period (%)
    shock_magnitude : Size of initial shock in std devs
    mm_withdrawal_threshold : Volatility level triggering withdrawal
    mm_risk_limit : Inventory level triggering withdrawal
    """
    np.random.seed(42)

    prices = [initial_price]
    active_mms = [n_market_makers]
    total_liquidity = [
        n_market_makers * 1000
    ]  # Shares available at best bid/ask

    # Market maker states
    mm_inventories = np.zeros(n_market_makers)
    mm_active = np.ones(n_market_makers, dtype=bool)

    recent_vol = normal_volatility

    for t in range(1, n_periods):
        # Large sell order at t=50
        if t == 50:
            shock = -shock_magnitude * normal_volatility
        else:
            shock = 0

        # Normal random price movement
        random_move = np.random.normal(0, normal_volatility)

        # Price impact depends on available liquidity
        liquidity_factor = total_liquidity[-1] / (n_market_makers * 1000)
        price_impact = 1 / max(
            liquidity_factor, 0.1
        )  # Higher impact with less liquidity

        price_change = (random_move + shock) * price_impact
        new_price = prices[-1] * (1 + price_change / 100)

        # Update recent volatility
        if t > 5:
            recent_returns = np.diff(np.log(prices[-5:])) * 100
            recent_vol = np.std(recent_returns)

        # Market makers update their state
        for i in range(n_market_makers):
            if mm_active[i]:
                # Accumulate inventory from the shock
                if t == 50:
                    mm_inventories[i] += np.random.exponential(200)

                # Check withdrawal conditions
                vol_exceeded = (
                    recent_vol > mm_withdrawal_threshold * normal_volatility
                )
                inventory_exceeded = abs(mm_inventories[i]) > mm_risk_limit

                if vol_exceeded or inventory_exceeded:
                    mm_active[i] = False

        # Update liquidity
        current_liquidity = mm_active.sum() * 1000

        # If liquidity gets very low, allow some recovery
        if t > 100 and recent_vol < normal_volatility * 1.2:
            for i in range(n_market_makers):
                if not mm_active[i] and np.random.random() < 0.1:
                    mm_active[i] = True
                    mm_inventories[i] = 0

        prices.append(new_price)
        active_mms.append(mm_active.sum())
        total_liquidity.append(current_liquidity)

    return {
        "prices": np.array(prices),
        "active_mms": np.array(active_mms),
        "total_liquidity": np.array(total_liquidity),
        "min_price": min(prices),
        "max_drawdown": (initial_price - min(prices)) / initial_price * 100,
    }


crash = simulate_flash_crash()
Out[32]:
Console
Flash Crash Simulation
========================================
Initial price: $100.00
Minimum price: $97.49
Maximum drawdown: 2.5%
Minimum active market makers: 0

The simulation triggers a severe liquidity event at period 50: all ten market makers withdraw and displayed liquidity falls from 10,000 shares to zero. The price response is much smaller than the accompanying liquidity collapse, with a minimum near $97.5 rather than a double-digit percentage crash. Apart from a brief return by two market makers around period 147, the simulated market does not recover its liquidity before the horizon ends. The result therefore illustrates synchronized withdrawal more clearly than it reproduces a full flash-crash price path.

Out[33]:
Visualization
Line chart of simulated asset price over 200 periods, with a coral dashed vertical line at period 50; price subsequently fluctuates between roughly $97.5 and $101.7.
Price trajectory around a simulated shock at period 50. The price falls from about $100 to a minimum near $97.5 before recovering unevenly, a much smaller move than the simultaneous collapse in modeled liquidity.
Line chart of active market makers over 200 periods, stable at 10 until period 50, then at zero except for a brief increase to two near period 147.
Number of active market makers over time. All ten remain active until period 50, then withdraw almost simultaneously; two briefly return near period 147, but participation otherwise stays at zero through the end of the simulation.
Area chart of total available liquidity over 200 periods, at 10,000 shares before period 50 and zero thereafter.
Total available liquidity starts at 10,000 shares and falls to zero at period 50 as all market makers withdraw. The model shows no sustained liquidity recovery during the remaining 150 periods.

The simulation illustrates the feedback loop that creates flash crashes: an initial shock causes price volatility, which triggers market maker risk limits, leading to liquidity withdrawal, which amplifies price movements further. The recovery occurs only when volatility subsides and market makers gradually return.