COVID Crash 2020: Four Halts, Peak to Trough
Di COVID crash of 2020, receipted: di March 9 halt minutes wey miss from di tape, four circuit breakers inside eight days, and di peak-to-trough map for SPY.
Di COVID crash of 2020 carry di S&P 500 from one record close on February 19 to one -34.2% peak-to-trough loss inside 23 trading days — with four market-wide circuit-breaker halts along di way, di first since 1997. Dis page dey replay di whole arc from di tape: di crash map, di March 9 halt session minute by minute (including di minutes wey dey miss from di data, wey be di best receipt for di page), di names wey fall di hardest, and wetin Treasuries and di options tape do alongside. Every number na one stored query; expand any panel for di SQL.
All the crash, for one row
Before the famous single day, na the map. One query wey pass through SPY daily regular-session closes dey pin the peak, the trough, the speed, and the recovery — plus the same measurement wey dem run for 2008 bear market for scale.
The exact SQL behind every number
WITH dailies AS (
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
argMaxIf(toFloat64(close), window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS c
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-02-01 00:00:00') AND window_start < toDateTime('2020-09-01 00:00:00')
GROUP BY d
),
peak AS (SELECT argMax(d, (c, -toInt32(d))) AS pd, max(c) AS pc FROM dailies WHERE d < toDate('2020-03-01')),
trough AS (SELECT argMin(d, (c, toInt32(d))) AS td, min(c) AS tc FROM dailies WHERE d < toDate('2020-04-01')),
gfc AS (
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
argMaxIf(toFloat64(close), window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS c
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2007-06-01 00:00:00') AND window_start < toDateTime('2009-01-01 00:00:00')
GROUP BY d
),
gpeak AS (SELECT argMax(d, (c, -toInt32(d))) AS pd, max(c) AS pc FROM gfc WHERE d < toDate('2008-01-01'))
SELECT
toString((SELECT pd FROM peak)) AS peak_date,
round((SELECT pc FROM peak), 2) AS peak_close,
toString((SELECT td FROM trough)) AS trough_date,
round((SELECT tc FROM trough), 2) AS trough_close,
round(((SELECT tc FROM trough) / (SELECT pc FROM peak) - 1) * 100, 1) AS decline_pct,
countIf(d > (SELECT pd FROM peak) AND d <= (SELECT td FROM trough)) AS sessions_peak_to_trough,
dateDiff('day', (SELECT pd FROM peak), (SELECT td FROM trough)) AS calendar_days,
countIf(d > (SELECT pd FROM peak) AND d <= (SELECT min(d) FROM dailies WHERE c <= 0.7 * (SELECT pc FROM peak))) AS sessions_to_minus_30,
(SELECT countIf(d > (SELECT pd FROM gpeak) AND d <= (SELECT min(d) FROM gfc WHERE c <= 0.7 * (SELECT pc FROM gpeak))) FROM gfc) AS gfc_2008_sessions_to_minus_30,
toString((SELECT min(d) FROM dailies WHERE d > (SELECT td FROM trough) AND c >= (SELECT pc FROM peak))) AS recovery_date,
countIf(d > (SELECT td FROM trough) AND d <= (SELECT min(d) FROM dailies WHERE d > (SELECT td FROM trough) AND c >= (SELECT pc FROM peak))) AS sessions_trough_to_recovery
FROM dailiesSPY peak close na $338.31 for 2020-02-19. The trough close, $222.51 for 2020-03-23, dey -34.2% below am — bear market wey dem squeeze enter 23 trading sessions, 33 calendar days. The "fastest thirty-percent drawdown on record" claim wey people dey quote, dem don receipt am for the same row: SPY close first breach 30% below the peak just 22 sessions in. The 2008 bear market, wey dem measure am the same way from im October 2007 peak close, need 250 sessions to lose the same 30%. Wetin take about one year for 2008, na about one month e take for 2020.
The dated skeleton of the crash, every number dem take from the panels for this page:
- February 19, 2020 — SPY record close, $338.31.
- March 9 — the first market-wide circuit breaker since 1997; SPY close -7.7% down. Dem don receipt am minute by minute below.
- March 12, 16, and 18 — three more Level-1 halts. March 16 close -11.6%, the worst single session of the crash.
- March 23 — the closing low: $222.51, -34.2% from the peak.
- March 24 — the turn. The stimulus rally of March 24 carry that tape.
- 2020-08-18 — SPY close above the February peak again, 103 sessions after the low.
March 9, 2020: di session wey trip di breaker
Market-wide circuit breakers dey key off di S&P 500, so dis panel dey read di halt through SPY — di S&P 500 ETF — instead of any oda proxy. For di weekend before, pandemic repricing don collide wit oil-supply shock (more on dat one below), and di market no really open in orderly way.
The exact SQL behind every number
WITH
(
SELECT argMaxIf(toFloat64(close), window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959)
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-03-06 00:00:00') AND window_start < toDateTime('2020-03-09 04:00:00')
) AS prior_rth_close,
(
SELECT (formatDateTime(toTimeZone(addMinutes(prev_bar, 1), 'America/New_York'), '%H:%i'), gap_minutes - 1)
FROM (
SELECT
lagInFrame(window_start) OVER (ORDER BY window_start ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_bar,
dateDiff('minute', prev_bar, window_start) AS gap_minutes
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-03-09 04:00:00') AND window_start < toDateTime('2020-03-09 23:59:00')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
)
WHERE prev_bar > toDateTime('2020-03-09 00:00:00')
ORDER BY gap_minutes DESC
LIMIT 1
) AS halt_gap
SELECT
round(prior_rth_close, 2) AS prior_close,
round(toFloat64(argMinIf(open, window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959)), 2) AS rth_open,
round((toFloat64(argMinIf(open, window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959)) / prior_rth_close - 1) * 100, 1) AS gap_pct,
halt_gap.1 AS halt_first_missing_et,
halt_gap.2 AS halt_missing_minutes,
round(minIf(toFloat64(low), (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959), 2) AS rth_low,
formatDateTime(toTimeZone(argMinIf(window_start, (toFloat64(low), toInt64(toUnixTimestamp(window_start))), (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959), 'America/New_York'), '%H:%i') AS low_et,
round(toFloat64(argMaxIf(close, window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959)), 2) AS rth_close,
round((toFloat64(argMaxIf(close, window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959)) / prior_rth_close - 1) * 100, 1) AS day_change_pct,
round(toFloat64(sum(volume)) / 1e6, 1) AS day_shares_m,
countIf((toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS rth_minute_bars
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-03-09 04:00:00') AND window_start < toDateTime('2020-03-09 23:59:00')Two numbers first. First, di gap: SPY open at $275.3, -7.4% below Friday close of $297.42 — and inside di first minutes di S&P 500 decline reach di 7% Level-1 threshold. Second, and better: 376 regular-session minute bars instead of di usual 390. Di tape stop after 9:34 am: from 09:35 ET, 14 minutes wey follow each oda no print any bar at all. Nothing trade anywhere. Di gap inside di data na di circuit breaker. Di session close at $274.4, -7.7% down, on 304.6 million shares.
How one session wey stop look
The exact SQL behind every number
SELECT
formatDateTime(toStartOfInterval(toTimeZone(window_start, 'America/New_York'), INTERVAL 30 MINUTE), '%H:%i') AS et_time,
round(toFloat64(argMax(close, window_start)), 2) AS bucket_close,
round(min(toFloat64(low)), 2) AS bucket_low,
round(toFloat64(sum(volume)) / 1e6, 1) AS shares_m
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-03-09 04:00:00') AND window_start < toDateTime('2020-03-09 23:59:00')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY et_time
ORDER BY et_timeThe opening bucket carry the crash, the halt, and the resumption all together: im low of $273.5 dey within pennies of the full day low of $273.45 — the afternoon print at 14:44 ET just retest the morning level. After the reopening, the tape no move anywhere: every half-hour close sit inside few dollars of the reopening price, and the day damage don already happen for the first minutes of clock time. Volume tell the same story the other way — the final half hour, at 46.7 million shares, na the heaviest bucket of the day as closing flows cross. Circuit breakers dey designed to produce exactly this shape: stop the waterfall, force one reopening auction, and make continuous trading resume for the repriced level. The market clock get this machinery permanently embedded now.
Four halts in eight sessions
March 9 na only the first one.
The exact SQL behind every number
SELECT
toString(et_date) AS session,
close_usd,
change_pct,
shares_m
FROM (
SELECT et_date, close_usd, shares_m,
round((close_usd / lagInFrame(close_usd) OVER (ORDER BY et_date ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) - 1) * 100, 1) AS change_pct
FROM (
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS et_date,
round(argMaxIf(toFloat64(close), window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959), 2) AS close_usd,
round(toFloat64(sum(volume)) / 1e6, 1) AS shares_m
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-03-06 00:00:00') AND window_start < toDateTime('2020-03-19 00:00:00')
GROUP BY et_date
)
)
WHERE toString(et_date) IN ('2020-03-09', '2020-03-12', '2020-03-16', '2020-03-18')
ORDER BY et_dateLevel-1 breakers still trigger for March 12 (close -9.6%), March 16 (close -11.6% at $239.41 — na the biggest one-day percentage drop for the S&P since 1987), and March 18 (halt intraday, close -5.2%). Four market-wide halts inside eight trading days, after twenty-three years wey no single one happen. See wetin no happen at all: every halt na Level 1, the 7% tier. The 13% and 20% tiers no reach — even on March 16, the cascade slow down after the halt and the session finish inside the Level-2 line.
Who fall pass
Index number dey hide the spread wey dey under am. If you run the same daily-close calculation for small number of big-cap stocks wey represent the market, you go see where the crash really land — both for March 9 itself and for the whole February 19 to March 23 fall.
The exact SQL behind every number
WITH dailies AS (
SELECT
ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
argMaxIf(toFloat64(close), window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS c
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('UAL', 'CCL', 'XOM', 'JPM', 'AAPL', 'WMT', 'SPY')
AND window_start >= toDateTime('2020-02-18 00:00:00') AND window_start < toDateTime('2020-03-25 00:00:00')
GROUP BY ticker, d
)
SELECT
ticker,
round((anyIf(c, d = toDate('2020-03-09')) / anyIf(c, d = toDate('2020-03-06')) - 1) * 100, 1) AS mar9_pct,
round((anyIf(c, d = toDate('2020-03-23')) / anyIf(c, d = toDate('2020-02-19')) - 1) * 100, 1) AS crash_pct
FROM dailies
GROUP BY ticker
ORDER BY crash_pct ASCThe name wey suffer pass for the panel, CCL (Carnival, the cruise operator), lose -19.9% for March 9 alone and -72.3% from peak to bottom. United Airlines drop -66.9% during the crash; ExxonMobil -47.9%; JPMorgan -42.6%. Compared to dem, SPY -34.2% look almost soft, Apple fall around the same level with the market at -31%, and Walmart — groceries, essentials, dem still dey open during lockdown — end the same period for just -2.9%. Travel and energy price change because the world stop; staples no really move.
The oil-price war, briefly
The energy row wey dey up don deserve im own one-paragraph explainer, since "oil-price war" carry di other half of the March 9 tori. OPEC+ production talks between Saudi Arabia and Russia end without deal on Friday, March 6, 2020. For di weekend, Saudi Arabia cut im official selling price and announce plan to raise output inside one world where travel demand don already dey shut down — na supply shock dem stack on top demand shock. Monday equity open carry both at once, and di receipt dey inside di panel wey dey up: ExxonMobil fall -12.2% that Monday against SPY's -7.7%.
De run go safety — and di week wey e break
Shares na only half of di tori. Di oda half na di Treasury market, wia di classic run go safety reach record level — and den, for one week wey pass, e run backwards.
The exact SQL behind every number
SELECT
toString(date) AS date,
round(yield_10_year, 2) AS ten_year_pct,
round(yield_3_month, 2) AS three_month_pct
FROM global_markets.treasury_yields
WHERE date >= '2020-02-18' AND date <= '2020-03-31'
AND isNotNull(yield_10_year)
ORDER BY dateDi 10-year yield stand at 1.56% on di February 19 equity peak. By March 9 — di halt session — e close at 0.54%, all-time low, as buyers accept half percent per year for ten years in exchange for safety. Den di pattern invert: enter March 18 di 10-year yield rise to 1.18% while shares keep dey fall — di "dash for cash," one week when funds, companies, and foreign holders sell wetin ever liquid, Treasuries among, to raise dollars. Meanwhile di 3-month bill close dat day at 0.02% — cash equivalents pin to zero. When di safest asset and di riskiest asset fall together, di tin wey dem dey buy na liquidity itself. Di yield curve carry dis episode permanently for im history.
Wetin the options tape dey show
SPY listed options — the market hedging venue wey dem dey record — leave their own receipt of the panic, and the time when e happen na the interesting part.
The exact SQL behind every number
SELECT
toString(toDate(toTimeZone(sip_timestamp, 'America/New_York'))) AS session,
round(sumIf(toFloat64(size), substring(ticker, 12, 1) = 'P') / 1e6, 2) AS put_contracts_m,
round(sumIf(toFloat64(size), substring(ticker, 12, 1) = 'C') / 1e6, 2) AS call_contracts_m,
round(sumIf(toFloat64(size), substring(ticker, 12, 1) = 'P') / sumIf(toFloat64(size), substring(ticker, 12, 1) = 'C'), 2) AS put_call_ratio
FROM global_markets.options_trades
WHERE ticker >= 'O:SPY2' AND ticker < 'O:SPY3'
AND sip_timestamp >= toDateTime('2020-03-02 00:00:00') AND sip_timestamp < toDateTime('2020-03-14 00:00:00')
GROUP BY session
ORDER BY sessionThe put/call volume ratio reach highest point at 1.95 — almost two puts trade for every call — on Friday, March 6, before the halt session. For March 9 itself, 4.35 million puts trade against 2.38 million calls, ratio of 1.83. By March 12 — one -9.6% session — the ratio don cool down to 1.32 as call volume start to rise alongside the puts. The heaviest put skew come for front of the cascade, not the bottom: protection dem buy am early, and the later, bigger down days trade on more two-sided options tape.
How market-wide halt take dey work
The mechanics, since dem no dey run am often enough for person to forget between uses: market-wide breakers dey key off the S&P 500 drop from the close before — 7% (Level 1) and 13% (Level 2) each one go trigger 15-minute halt if e hit before 3:25 pm ET; 20% (Level 3) go end the session completely. During the halt, nothing dey trade for any exchange; orders dey queue for reopening auction, wey go concentrate all the interest wey don gather into one price the same way the opening auction dey do each morning. This design start from the aftermath of the 2010 flash crash, wey show say the old thresholds no tight enough to fire even when real cascade happen.
March 2020 na the first — and so far only — live test for that design, and the verdict dey inside the panels above: four Level-1 halts, each one followed by orderly reopening, no Level 2 or Level 3 ever reach, and market wey dey function for every one of the 23 sessions of the fastest large drawdown for the index history. The rules remain standing after: the same 7/13/20 thresholds, wey dey referenced to the S&P 500, still dey in force today. The system bend; the plumbing hold.
COVID crash FAQ
Wetin cause the circuit breaker for March 9, 2020?
S&P 500 don fall 7% from where e close before — wey be the Level-1 market-wide threshold — inside the first few minutes wey market open. Trading stop for 15 minutes across board, then e reopen through auction. The halt dey show for this page minute tape as 14 consecutive missing bars wey start at 09:35 ET.
How much the stock market fall during COVID crash?
If we measure am with SPY daily closes, -34.2% — from $338.31 for February 19, 2020 reach $222.51 for March 23, 2020, across 23 trading sessions.
How many circuit breakers dem trigger for March 2020?
Four market-wide Level-1 halts: March 9, 12, 16, and 18. Dem be the first ones since October 1997.
Dem ever trigger Level 2 or Level 3 circuit breakers for 2020?
No. All the four 2020 halts na Level 1 (7%). No Level-2 (13%) or Level-3 (20%) market-wide halt don ever fire under the current design — not even for March 16, wey be the worst session of the crash at -11.6%.
The market fully recover from the COVID crash?
Yes. SPY close above im February 19, 2020 peak for 2020-08-18 — 103 trading sessions after the March 23 low, one of the fastest full recoveries from a bear market of this depth.
Every panel wey dey above na stored, versioned query over the historical tape — expand the SQL make you see each measurement. You wan feel this day instead of to read am? E dey among the playable scenarios for the Strasmore Labs trading simulator.