COVID Crash 2020: vier Handelsunterbrechungen
Analyse des COVID Crashs 2020: Erfahren Sie alles über die vier Circuit Breaker, die fehlenden Daten vom 9. März und den Kursverlauf des SPY im Detail.
Der COVID-Crash von 2020 führte den S&P 500 von einem Rekordhoch am 19. Februar zu einem Verlust von -34.2% vom Höchststand zum Tiefpunkt innerhalb von 23 Handelstagen. Dabei kam es zu vier handelsweiten Handelsunterbrechungen durch Circuit Breaker, die ersten seit 1997. Diese Seite rekonstruiert den gesamten Verlauf anhand der Marktdaten: die Karte des Crashs, die minutengenaue Aufzeichnung der Sitzung mit Handelsunterbrechung am 9. März (einschließlich der Minuten, die in den Daten fehlen und die präziseste Belegführung auf dieser Seite darstellen), die am stärksten fallenden Titel sowie die gleichzeitige Entwicklung bei den Treasuries und den Optionen. Jeder Wert basiert auf einer gespeicherten Abfrage; Sie können jedes Panel erweitern, um den SQL-Code einzusehen.
Der gesamte Crash in einer Zeile
Vor dem berühmten einzelnen Tag: die Übersicht. Eine Abfrage der täglichen Schlusskurse der regulären Sitzung des SPY zeigt den Höchststand, das Tief, die Geschwindigkeit und die Erholung – ergänzt durch dieselbe Messung des Bärenmarktes von 2008 zum Vergleich.
Das exakte SQL hinter jeder Zahl
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 dailiesDer Höchststand des SPY-Schlusskurses lag bei $338.31 am 2020-02-19. Der Tiefpunkt des Schlusskurses lag bei $222.51 am 2020-03-23, was -34.2% unter dem Höchststand liegt – ein Bärenmarkt, der in 23 Handelssitzungen bzw. 33 Kalendertagen komprimiert wurde. Die oft zitierte Behauptung des „schnellsten Rückgangs von dreißig Prozent in der Geschichte“ findet sich in derselben Zeile wieder: Der Schlusskurs des SPY fiel nach nur 22 Sitzungen um mehr als 30 % unter den Höchststand. Der Bärenmarkt von 2008, identisch gemessen ab seinem Höchststand im Oktober 2007, benötigte 250 Sitzungen für denselben Verlust von 30 %. Was im Jahr 2008 etwa ein Jahr dauerte, dauerte im Jahr 2020 etwa einen Monat.
Der zeitliche Ablauf des Crashs, alle Zahlen stammen aus den Tabellen auf dieser Seite:
- 19. Februar 2020 — Rekord-Schlusskurs des SPY, $338.31.
- 9. März — der erste marktweite Circuit Breaker seit 1997; der SPY schließt -7.7% im Minus. Die minutengenaue Aufstellung folgt unten.
- 12., 16. und 18. März — drei weitere Level-1-Handelsunterbrechungen. Der 16. März schließt mit einem Minus von -11.6%, der schlechteste Einzeltag des Crashs.
- 23. März — das Schluss-Tief: $222.51, -34.2% unter dem Höchststand.
- 24. März — die Trendwende. Die Stimulus-Rallye vom 24. März leitet diese Phase ein.
- 2020-08-18 — der SPY schließt wieder über dem Februar-Höchststand, 103 Sitzungen nach dem Tiefpunkt.
9. März 2020: Die Sitzung, die den Circuit Breaker auslöste
Die marktweiten Circuit Breaker orientieren sich am S&P 500. Daher zeigt diese Übersicht die Handelsunterbrechung anhand von SPY – dem S&P 500 ETF – statt über andere Proxys. Am vorangegangenen Wochenende trafen Neubewertungen aufgrund der Pandemie auf einen Öl-Angebots-Schock (Details dazu unten), weshalb der Markt nie geordnet eröffnen konnte.
Das exakte SQL hinter jeder Zahl
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')Zwei Zahlen vorab. Erstens die Differenz: SPY eröffnete bei $275.3, was -7.4% unter dem Freitagsschluss von $297.42 liegt – und innerhalb der ersten Minuten erreichte der Rückgang des S&P 500 die Level-1-Schwelle von 7 %. Zweitens, und besser: 376 Minuten-Bars der regulären Sitzung anstelle der üblichen 390. Der Tape stoppt nach 9:34 Uhr: Ab 09:35 ET drucken 14 aufeinanderfolgende Minuten überhaupt keine Bars. Es wurde nirgendwo gehandelt. Die Lücke in den Daten ist der Circuit Breaker. Die Sitzung schloss bei $274.4, einem Minus von -7.7%, bei einem Volumen von 304.6 Millionen Aktien.
Die Struktur einer unterbrochenen Sitzung
Das exakte SQL hinter jeder Zahl
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_timeDie erste Handelsspanne umfasst den Crash, die Unterbrechung und die Wiederaufnahme gleichzeitig: Das Tief von $273.5 liegt nur wenige Cent über dem Tagestief von $273.45 — der Kurs um 14:44 ET testete lediglich das Niveau vom Morgen. Nach der Wiederaufnahme veränderte sich der Kurs kaum: Jeder halbstündliche Schlusskurs lag nur wenige Dollar vom Wiederaufnahmepreis entfernt. Der gesamte Verlust entstand bereits in den ersten Minuten. Das Volumen bestätigte diesen Verlauf: Die letzte halbe Stunde mit 46.7 Millionen Aktien war aufgrund der Schlusskurs-Order das volumenstärkste Zeitfenster des Tages. Circuit Breaker sind genau für dieses Muster konzipiert: Sie stoppen den Verkaufssturz, erzwingen eine Wiederaufnahme-Auktion und ermöglichen den kontinuierlichen Handel auf dem neu bewerteten Niveau. Die Uhr des Marktes hat diese Mechanismen nun dauerhaft integriert.
Vier Handelsunterbrechungen in acht Sitzungen
Der 9. März war erst der Anfang.
Das exakte SQL hinter jeder Zahl
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_dateAuch am 12. März (Schlusskurs -9.6%), am 16. März (Schlusskurs -11.6% bei $239.41 – der größte prozentuale Tagesverlust des S&P seit 1987) und am 18. März (intraday unterbrochen, Schlusskurs -5.2%) wurden Level-1-Breaker ausgelöst. Vier marktweite Unterbrechungen in acht Handelstagen, nachdem es zweiundzwanzig Jahre lang keine gab. Beachten Sie, was nicht geschah: Jede Unterbrechung war eine Level-1-Unterbrechung, also die 7%-Stufe. Die 13%- und 20%-Stufen wurden nie erreicht – selbst am 16. März verlangsamte sich die Verkaufswelle nach der Unterbrechung, und die Sitzung endete innerhalb der Level-2-Linie.
Wer am stärksten verlor
Ein Indexwert verbirgt die zugrunde liegende Streuung. Die gleiche Berechnung der Schlusskurse für eine Auswahl repräsentativer Large-Caps zeigt, wo der Crash tatsächlich stattfand – sowohl am 9. März als auch über den gesamten Rückgang vom 19. Februar bis zum 23. März.
Das exakte SQL hinter jeder Zahl
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 ASCDer am stärksten betroffene Wert in der Auswahl, CCL (Carnival, der Kreuzfahrtbetreiber), verlor allein am 9. März -19.9% und insgesamt -72.3% vom Höchst- zum Tiefpunkt. United Airlines verlor während des Crashs -66.9%; ExxonMobil -47.9%; JPMorgan -42.6%. Im Vergleich dazu wirkt der Rückgang von SPY mit -34.2% fast moderat, Apple fiel mit etwa -31% ähnlich wie der Markt, und Walmart – Lebensmittel, Güter des täglichen Bedarfs, während der Lockdowns geöffnet – schloss im selben Zeitraum mit nur -2.9% ab. Reisen und Energie wurden aufgrund der weltweiten Stilllegung neu bewertet; Konsumgüter bewegten sich kaum.
Der Ölpreiskrieg, kurz zusammengefasst
Der oben beschriebene Energiekonflikt erfordert eine kurze Erläuterung, da der Begriff „Ölpreiskrieg“ die andere Hälfte der Berichterstattung vom 9. März darstellt. Die Verhandlungen über die OPEC+-Produktion zwischen Saudi-Arabien und Russland endeten am Freitag, den 6. März 2020, ohne eine Einigung. Am Wochenende senkte Saudi-Arabien seine offiziellen Verkaufspreise und kündigte an, die Förderung zu erhöhen, während die Reisebedarfe bereits massiv zurückgingen – ein Angebotsschock zusätzlich zu einem Nachfrageschock. Der Börsenhandel am Montag spiegelte beide Effekte gleichzeitig wider, was die Ergebnisse in der obigen Tabelle zeigt: ExxonMobil fiel an diesem Montag um -12.2%, während der SPY um -7.7% sank.
Die Flucht in Sicherheit – und die Woche, in der sie umkehrte
Aktien machten nur die Hälfte der Geschichte aus. Die andere Hälfte war der Anleihenmarkt, an dem die klassische Flucht in Sicherheit ein Rekordniveau erreichte – und dann, für eine bemerkenswerte Woche, in die entgegengesetzte Richtung lief.
Das exakte SQL hinter jeder Zahl
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 dateDie Rendite der 10-jährigen Staatsanleihen lag am Aktien-Höchststand vom 19. Februar bei 1.56%. Bis zum 9. März – der Sitzung mit Handelsstopp – schloss sie bei 0.54%, einem Allzeittief, wobei Käufer eine halbe Prozentpunkt-Rendite pro Jahr für ein Jahrzehnt im Austausch für Sicherheit akzeptierten. Dann kehrte sich das Muster um: Bis zum 18. März stieg die 10-jährige Rendite auf 1.18%, während die Aktien weiter fielen – der „Dash for Cash“, eine Woche, in der Fonds, Unternehmen und ausländische Anleger alles Liquide verkauften, einschließlich Staatsanleihen, um Dollar zu beschaffen. In der Zwischenzeit schloss der 3-Monats-T-Bill an diesem Tag bei 0.02% – Cash-Äquivalente bei nahezu Null. Wenn das sicherste Asset und das riskanteste Asset gleichzeitig fallen, ist das gekaufte Gut die Liquidität selbst. Die Zinskurve hält diesen Vorfall dauerhaft in ihrer Historie fest.
Was der Optionsmarkt zeigt
Die an der Börse gelisteten Optionen von SPY – der primäre Handelsplatz für Absicherungen – spiegelten die Panik wider. Der Zeitpunkt der Entwicklung ist dabei entscheidend.
Das exakte SQL hinter jeder Zahl
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 sessionDas Put/Call-Volumen erreichte am Freitag, den 6. März, vor der Handelsunterbrechung einen Höchststand von 1.95 – es wurden fast zwei Puts für jeden Call gehandelt. Am 9. März selbst wurden 4.35 Millionen Puts gegenüber 2.38 Millionen Calls gehandelt, was einem Verhältnis von 1.83 entspricht. Bis zum 12. März – einer -9.6%-Sitzung – hatte sich das Verhältnis auf 1.32 entspannt, da das Call-Volumen parallel zum Put-Volumen anstieg. Die stärkste Put-Gewichtung trat zu Beginn der Abwärtsbewegung auf, nicht am Ende: Absicherungen wurden frühzeitig gekauft, während die späteren, größeren Verlusttage durch ein ausgewogeneres Optionsvolumen gekennzeichnet waren.
Wie ein marktweiter Handelsstopp tatsächlich funktioniert
Die Mechanik ist selten in Gebrauch und wird daher oft vergessen: Marktweite Breaker basieren auf dem Rückgang des S&P 500 gegenüber dem Vortag. Ein Rückgang von 7 % (Level 1) oder 13 % (Level 2) löst jeweils einen 15-minütigen Stopp aus, sofern dies vor 15:25 Uhr ET geschieht. Ein Rückgang von 20 % (Level 3) beendet die Sitzung vollständig. Während des Stopps findet an keiner Börse Handel statt. Die Aufträge werden für eine Wiedereröffnungsauktion gesammelt. Diese konzentriert das angesammelte Interesse auf einen einzigen Preis, ähnlich wie es die Eröffnungsauktion jeden Morgen tut. Dieses Design entstand nach dem Flash Crash von 2010, da die alten Schwellenwerte als zu niedrig galten, um selbst bei einer echten Kaskade auszulösen.
Der März 2020 war der erste – und bisher einzige – Praxistest dieses Designs. Das Ergebnis ist in den obenstehenden Diagrammen ersichtlich: vier Level-1-Stopps, die jeweils durch eine geordnete Wiedereröffnung gefolgt wurden. Level 2 oder Level 3 wurden nie erreicht. Der Markt blieb in jeder der 23 Sitzungen während des schnellsten großen Drawdowns in der Geschichte des Index funktionsfähig. Die Regeln wurden danach beibehalten: Dieselben Schwellenwerte von 7/13/20 %, bezogen auf den S&P 500, sind auch heute noch in Kraft. Das System war unter Belastung, aber die Infrastruktur blieb stabil.
COVID crash FAQ
What triggered the circuit breaker on March 9, 2020?
The S&P 500 falling 7% from its prior close — the Level-1 market-wide threshold — within the opening minutes. Trading halted for 15 minutes market-wide, then reopened via auction. The halt is visible in this page's minute tape as 14 consecutive missing bars starting at 09:35 ET.
How much did the stock market fall in the COVID crash?
Measured on SPY's daily closes, -34.2% — from $338.31 on February 19, 2020 to $222.51 on March 23, 2020, across 23 trading sessions.
How many circuit breakers were hit in March 2020?
Four market-wide Level-1 halts: March 9, 12, 16, and 18. They were the first since October 1997.
Were Level 2 or Level 3 circuit breakers ever hit in 2020?
No. All four 2020 halts were Level 1 (7%). No Level-2 (13%) or Level-3 (20%) market-wide halt has ever fired under the current design — not even on March 16, the crash's worst session at -11.6%.
Did the market fully recover from the COVID crash?
Yes. SPY closed above its February 19, 2020 peak on 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 above is a stored, versioned query over the historical tape — expand the SQL to see each measurement. Want to feel this day instead of reading it? It is one of the playable scenarios in the Strasmore Labs trading simulator.