March 24, 2020: The Day Market Turned
Replay March 24, 2020 from the minute tape: crash, dash for cash, Fed package, sector breadth, and why the retest wey many expect never come.
For March 23, 2020, Federal Reserve announce open-ended asset purchases, meaning QE wey no get stated limit. E also announce something wey e never do before: facilities to buy corporate bonds. March 24, 2020 na market answer: COVID stimulus rally, one of the biggest single-day US equity gains wey ever happen, plus—na only hindsight show am—the second day of a new bull market. The bottom itself don close for afternoon one day before the announcement land.
Crash before the turn
The crash itself get im own page; here na the receipt wey this one dey stand on, every session from the all-time high reach the bottom, with the 10-year Treasury yield beside am for basis points:
| session | close USD | drawdown (%) | Y10 bps |
|---|---|---|---|
| 2020-02-19 | 338.31 | 0 | 156 |
| 2020-02-20 | 336.98 | -0.4 | 152 |
| 2020-02-21 | 333.49 | -1.4 | 146 |
| 2020-02-24 | 322.41 | -4.7 | 138 |
| 2020-02-25 | 312.7 | -7.6 | 133 |
| 2020-02-26 | 311.55 | -7.9 | 133 |
| 2020-02-27 | 297.55 | -12 | 130 |
| 2020-02-28 | 296.16 | -12.5 | 113 |
| 2020-03-02 | 309.08 | -8.6 | 110 |
| 2020-03-03 | 300.34 | -11.2 | 102 |
| 2020-03-04 | 312.91 | -7.5 | 102 |
| 2020-03-05 | 302.45 | -10.6 | 92 |
| 2020-03-06 | 297.42 | -12.1 | 74 |
| 2020-03-09 | 274.4 | -18.9 | 54 |
| 2020-03-10 | 288.44 | -14.7 | 76 |
| 2020-03-11 | 274.36 | -18.9 | 82 |
| 2020-03-12 | 248.1 | -26.7 | 88 |
| 2020-03-13 | 270.93 | -19.9 | 94 |
| 2020-03-16 | 239.41 | -29.2 | 73 |
| 2020-03-17 | 254.19 | -24.9 | 102 |
The exact SQL behind every number
WITH daily AS (
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
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-02-19 00:00:00') AND window_start < toDateTime('2020-03-24 04:00:00')
GROUP BY et_date
HAVING et_date >= toDate('2020-02-19') AND et_date <= toDate('2020-03-23') AND close_usd > 0
)
SELECT
toString(d.et_date) AS session,
d.close_usd,
round((d.close_usd / (SELECT max(close_usd) FROM daily) - 1) * 100, 1) AS drawdown_pct,
round(t.yield_10_year * 100) AS y10_bps
FROM daily AS d
LEFT JOIN global_markets.treasury_yields AS t ON t.date = d.et_date
ORDER BY d.et_dateFrom the all-time-high close of $338.31 on 2020-02-19, SPY fall reach $222.51 by 2020-03-23, na -34.2% drawdown, and every session for the fall dey inside the panel’s 24 rows.
The yield column na the main clue. Through 2020-03-09, the 10-year yield crash from 156 basis points reach a then-record 54. Na the classic flight to safety, as money comot stocks enter bonds. Then the pattern break. By 2020-03-18, the yield don pass double reach 118 basis points, while SPY drawdown deepen from -18.9% reach -28.8%. When stocks and Treasuries dey fall together, e show say forced selling dey happen. Holders dey raise cash by selling anything wey still get bid. By mid-March, the crisis don reach the plumbing of credit itself, no be just stock prices again.
Wetín Fed really announce, and wetín never work
Rate cuts don already happen before. The sequence get dates, and each entry dey readable for panel above:
- 2020-03-03, emergency half-point rate cut. Na the first cut between meetings since 2008. SPY close at $300.34, -11.2% below the high, and the fall continue.
- March 15, Sunday, rates cut reach zero, plus $700 billion asset-purchase program. For the next session, 2020-03-16 close -29.2% below the February high. E be among the worst single sessions for the whole crash.
- March 23, 8:00 am ET, Fed remove the limit. E talk say purchases of Treasuries and mortgage-backed securities go continue “in the amounts needed,” with no fixed end. The package include PMCCF and SMCCF, wey be the Fed’s first-ever corporate-bond-buying facilities. E also bring back TALF to support consumer credit. SPY still close at the bear-market low that afternoon.
- March 24, the session wey follow below. Overnight, Congress agree on the roughly $2 trillion CARES Act. Dem sign am on March 27, inside the three-day burst wey receipts further down record.
Two design differences separate the March 23 package from the earlier attempts. Fixed-size program get end date, and stressed market fit look past that date. But “in the amounts needed” no get that same limit. The corporate facilities also put public buyer directly inside credit markets where the market plumbing don seize. Whether na these differences cause the change na matter of judgment. The tape only record the sequence: two escalations, continued decline, third design, then the turn.
The day, for one row
| prior close | RTH open | gap (%) | RTH low | low ET | RTH high | RTH close | day change (%) | low vs prior (%) | day shares (m) | RTH minute bars |
|---|---|---|---|---|---|---|---|---|---|---|
| 222.51 | 234.42 | 5.4 | 233.8 | 09:31 | 244.1 | 243.59 | 9.5 | 5.1 | 232.4 | 390 |
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-23 00:00:00') AND window_start < toDateTime('2020-03-24 04:00:00')
) AS prior_rth_close
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,
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(maxIf(toFloat64(high), (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959), 2) AS rth_high,
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((minIf(toFloat64(low), (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 low_vs_prior_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-24 04:00:00') AND window_start < toDateTime('2020-03-24 23:59:00')From the March 23 close of $222.51, wey be the bear market final close, SPY gap up +5.4% open for $234.42. E print session low of $233.8 for the opening minutes (09:31 ET), and e no come back reach there again. The close for $243.59 mean +9.5% for the day. Rally wey e print the low for the first minute na mirror image of crash wey close for the low. But one row dey flatten the journey. The half-hour tape wey dey below show the fade wey the session absorb.
The way the session take shape
| ET time | bucket close | bucket low | shares (m) |
|---|---|---|---|
| 09:30 | 235.51 | 233.8 | 27.3 |
| 10:00 | 238.45 | 234.6 | 17.4 |
| 10:30 | 237.46 | 237.04 | 13.2 |
| 11:00 | 238.36 | 236.16 | 11.5 |
| 11:30 | 240.43 | 237.58 | 12.5 |
| 12:00 | 242.24 | 239.65 | 14.2 |
| 12:30 | 239.65 | 239.57 | 12.9 |
| 13:00 | 239.41 | 237.86 | 16.1 |
| 13:30 | 237.25 | 236.76 | 10.7 |
| 14:00 | 237.38 | 235.05 | 16.9 |
| 14:30 | 240.58 | 236.61 | 11.5 |
| 15:00 | 241.45 | 240.21 | 12.1 |
| 15:30 | 243.59 | 240.41 | 37.5 |
The exact SQL behind every number
SELECT
b.et_time AS et_time,
b.bucket_close AS bucket_close,
b.bucket_low AS bucket_low,
round((b.bar_shares + if(b.et_time = '15:30', p.print_shares, 0)) / 1e6, 1) AS shares_m
FROM
(
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,
toFloat64(sum(volume)) AS bar_shares
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2020-03-24 04:00:00') AND window_start < toDateTime('2020-03-24 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
) AS b
CROSS JOIN
(
SELECT toFloat64(maxIf(size, has(conditions, 8))) AS print_shares
FROM global_markets.stocks_trades
WHERE ticker = 'SPY'
AND sip_timestamp >= toDateTime('2020-03-24 20:00:00', 'UTC')
AND sip_timestamp < toDateTime('2020-03-24 20:15:00', 'UTC')
) AS p
ORDER BY et_timeThe panel no hide am: the path bend. The low of $233.8 print for the opening minute and price no come back there again, so that one-directional story get some truth. But after price reach $242.24 by the noon bucket, the tape give back ground for two hours. The 13:30 bucket close at $237.25. The 14:00 bucket print the afternoon’s deepest low, $235.05. Na the point wey the day fit turn into another failed bounce. Instead, the give-back hold well above the opening low. The final half hour bring the session’s highest bucket close, $243.59, on 37.5 million shares. Na the heaviest bucket for the 232.4-million-share day, even pass the open’s 27.3 million. Midday dip find buyers, then the session close near the highs on peak volume.
Broad rally, or narrow?
Index fit rise because handful mega-caps dey pull am up. But this one no be like that. Twelve funds — the three major index trackers plus nine S&P sector funds — all close for the same two trading sessions:
| ticker | Mar23 close | Mar24 close | change (%) |
|---|---|---|---|
| XLE | 23.53 | 27.39 | 16.4 |
| XLI | 48.75 | 54.97 | 12.8 |
| XLF | 17.62 | 19.85 | 12.7 |
| DIA | 185.87 | 206.96 | 11.3 |
| XLU | 44.92 | 49.76 | 10.8 |
| XLK | 70.2 | 77.64 | 10.6 |
| XLY | 87.42 | 95.77 | 9.6 |
| SPY | 222.51 | 243.59 | 9.5 |
| IWM | 99.83 | 109.17 | 9.4 |
| QQQ | 170.61 | 183.79 | 7.7 |
| XLV | 74.8 | 80.31 | 7.4 |
| XLP | 48.66 | 51.12 | 5.1 |
The exact SQL behind every number
SELECT
ticker,
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 AND toDate(toTimeZone(window_start, 'America/New_York')) = toDate('2020-03-23')), 2) AS mar23_close,
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 AND toDate(toTimeZone(window_start, 'America/New_York')) = toDate('2020-03-24')), 2) AS mar24_close,
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 AND toDate(toTimeZone(window_start, 'America/New_York')) = toDate('2020-03-24')) / argMaxIf(toFloat64(close), window_start, (toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959 AND toDate(toTimeZone(window_start, 'America/New_York')) = toDate('2020-03-23')) - 1) * 100, 1) AS change_pct
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('SPY', 'DIA', 'QQQ', 'IWM', 'XLE', 'XLI', 'XLF', 'XLK', 'XLY', 'XLV', 'XLU', 'XLP')
AND window_start >= toDateTime('2020-03-23 00:00:00') AND window_start < toDateTime('2020-03-25 00:00:00')
GROUP BY ticker
ORDER BY change_pct DESCAll 12 close higher. Energy (XLE) lead with +16.4%, while industrials (XLI, +12.8%) and financials (XLF, +12.7%) follow close behind. Dem be the parts of market wey economy dey affect most, and shutdown dey threaten dem first. Dow tracker DIA gain 11.3%. Na this move make headlines call that week’s session the “best day since 1933,” though that superlative come from the Dow’s longer price history. The smallest gains dey for the usual defensive sectors: consumer staples (XLP, +5.1%) and healthcare (XLV, +7.4%). Nasdaq-100’s QQQ (+7.7%) also trail SPY’s +9.5%. When cyclicals lead and every sector dey green, the pattern na opposite of narrow mega-cap squeeze. Breadth na the main feature of the day.
Ke historic e really be?
“Biggest day since 1933” na Dow statistic wey newspapers for that week report. Na wetin our own tape fit verify be this, based on wetin e cover:
| sessions wey dem measure | first session | Mar24 gain (%) | days wey bigger | Mar24 rank | dates of days wey bigger | burst3 gain (%) | bursts wey bigger | vol vs 30d | vol vs 250d | mar24 shares m |
|---|---|---|---|---|---|---|---|---|---|---|
| 5613 | 2003-09-10 | 9.5 | 2 | 3 | 2008-10-13, 2008-10-28 | 17.5 | 0 | 1.12 | 2.86 | 232 |
The exact SQL behind every number
SELECT
countIf(isFinite(day_pct)) AS sessions_measured,
toString(min(et_date)) AS first_session,
round(mar24_d, 1) AS mar24_gain_pct,
countIf(isFinite(day_pct) AND day_pct > mar24_d) AS bigger_days,
countIf(isFinite(day_pct) AND day_pct > mar24_d) + 1 AS mar24_rank,
arrayStringConcat(arraySort(groupArrayIf(toString(et_date), isFinite(day_pct) AND day_pct > mar24_d)), ', ') AS bigger_day_dates,
round(mar26_b, 1) AS burst3_gain_pct,
countIf(isFinite(burst3_pct) AND burst3_pct > mar26_b) AS bigger_bursts,
round(mar24_r30, 2) AS vol_vs_30d,
round(mar24_r250, 2) AS vol_vs_250d,
round(mar24_shares / 1e6, 0) AS mar24_shares_m
FROM (
SELECT
et_date, day_pct, burst3_pct,
max(if(et_date = toDate('2020-03-24'), day_pct, -999)) OVER () AS mar24_d,
max(if(et_date = toDate('2020-03-26'), burst3_pct, -999)) OVER () AS mar26_b,
max(if(et_date = toDate('2020-03-24'), shares / vol_avg_30, -999)) OVER () AS mar24_r30,
max(if(et_date = toDate('2020-03-24'), shares / vol_avg_250, -999)) OVER () AS mar24_r250,
max(if(et_date = toDate('2020-03-24'), shares, -999)) OVER () AS mar24_shares
FROM (
SELECT
et_date,
(close_usd / lagInFrame(close_usd, 1) OVER (ORDER BY et_date ASC ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) - 1) * 100 AS day_pct,
(close_usd / lagInFrame(close_usd, 3) OVER (ORDER BY et_date ASC ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) - 1) * 100 AS burst3_pct,
shares,
avg(shares) OVER (ORDER BY et_date ASC ROWS BETWEEN 30 PRECEDING AND 1 PRECEDING) AS vol_avg_30,
avg(shares) OVER (ORDER BY et_date ASC ROWS BETWEEN 250 PRECEDING AND 1 PRECEDING) AS vol_avg_250
FROM (
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS et_date,
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 close_usd,
toFloat64(sum(volume)) AS shares
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY' AND window_start < toDateTime('2026-01-01 00:00:00')
GROUP BY et_date
HAVING close_usd > 0
)
)
)
GROUP BY mar24_d, mar26_b, mar24_r30, mar24_r250, mar24_sharesSince 2003-09-10, wey be the first session for our minute data, SPY don print 5613 close-to-close daily returns. The +9.5% wey happen on March 24, 2020 rank #3: exactly 2 sessions beat am (2008-10-13, 2008-10-28), and both happen inside the October 2008 panic. The three-day sequence even rare pass: the +17.5% run from March 23 close reach March 26 close get 0 bigger three-session gains anywhere for the sample. Inside everything wey this warehouse fit check, nothing match that burst.
Volume dey tell the quieter story: 232 million SPY shares be 2.86× the trailing-year average, and only 1.12× the average for the previous 30 sessions. Against any normal period for history, the activity extraordinary well-well; but against the crash weeks wey come before am, na just another day inside the storm. Historic turns no dey announce themselves with historic volume.
The three-day burst
| session | close USD | change (%) | shares (m) |
|---|---|---|---|
| 2020-03-23 | 222.51 | -2.8 | 322.4 |
| 2020-03-24 | 243.59 | 9.5 | 232.4 |
| 2020-03-25 | 246.77 | 1.3 | 297.4 |
| 2020-03-26 | 261.39 | 5.9 | 253.8 |
The exact SQL behind every number
SELECT
toString(et_date) AS session,
close_usd,
round(if(prev_close = 0, NULL, (close_usd / prev_close - 1) * 100), 1) AS change_pct,
shares_m
FROM (
SELECT et_date, close_usd, shares_m,
lagInFrame(close_usd) OVER (ORDER BY et_date ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
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-20 00:00:00') AND window_start < toDateTime('2020-03-26 23:59:00')
GROUP BY et_date
)
)
WHERE et_date >= toDate('2020-03-23') AND et_date <= toDate('2020-03-26')
ORDER BY et_dateThe turn compound immediately: +9.5% on the 24th, +1.3% on the 25th, +5.9% on the 26th. Na three trading-session sequence wey wipe out weeks of collapse, and no other one for this sample pass am. The March 2020 pattern resemble March 2009 deliberately: for both cases, the decisive policy response come near the low, the first rally leg violent well, and the day wey opportunity reach maximum look that morning just like another bear-market bounce.
Rally hold?
As e dey happen, most people read am as “bear-market bounce,” and the skeptics get their own evidence: eleven days before, market don already make rally almost this size, from $248.1 on 2020-03-12 reach $270.93 on 2020-03-13. Then the session after that close at $239.41, the worst day for the whole crash. One green day no prove anything. Wetin make March 24 different na wetin happen after:
| feb19 ath close | mar23 bottom close | recovery date | sessions to new high | days low to new high | round trip days | lowest close after | lowest close date | closest retest pct | peak 2007 date | recovery 2013 date | round trip 2008 days |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 338.31 | 222.51 | 2020-08-18 | 103 | 148 | 181 | 243.59 | 2020-03-24 | 9.5 | 2007-10-09 | 2013-03-14 | 1983 |
The exact SQL behind every number
SELECT
round(ath_close, 2) AS feb19_ath_close,
round(bottom_close, 2) AS mar23_bottom_close,
toString(rec_date) AS recovery_date,
countIf(et_date > toDate('2020-03-23') AND et_date <= rec_date) AS sessions_to_new_high,
dateDiff('day', toDate('2020-03-23'), rec_date) AS days_low_to_new_high,
dateDiff('day', toDate('2020-02-19'), rec_date) AS round_trip_days,
round(minIf(close_usd, et_date > toDate('2020-03-23') AND et_date <= rec_date), 2) AS lowest_close_after,
toString(argMinIf(et_date, (close_usd, et_date), et_date > toDate('2020-03-23') AND et_date <= rec_date)) AS lowest_close_date,
round((minIf(close_usd, et_date > toDate('2020-03-23') AND et_date <= rec_date) / bottom_close - 1) * 100, 1) AS closest_retest_pct,
toString(peak07_date) AS peak_2007_date,
toString(rec07_date) AS recovery_2013_date,
dateDiff('day', peak07_date, rec07_date) AS round_trip_2008_days
FROM (
SELECT
et_date, close_usd, ath_close, bottom_close, peak07_date,
min(if(et_date > toDate('2020-03-23') AND close_usd >= ath_close, et_date, toDate('2099-01-01'))) OVER () AS rec_date,
min(if(et_date >= toDate('2008-01-01') AND close_usd >= peak07_close, et_date, toDate('2099-01-01'))) OVER () AS rec07_date
FROM (
SELECT
et_date, close_usd,
max(if(et_date = toDate('2020-02-19'), close_usd, -999)) OVER () AS ath_close,
max(if(et_date = toDate('2020-03-23'), close_usd, -999)) OVER () AS bottom_close,
max(if(et_date >= toDate('2007-01-01') AND et_date < toDate('2008-01-01'), (close_usd, et_date), (-999., toDate('1970-01-01')))) OVER () .1 AS peak07_close,
max(if(et_date >= toDate('2007-01-01') AND et_date < toDate('2008-01-01'), (close_usd, et_date), (-999., toDate('1970-01-01')))) OVER () .2 AS peak07_date
FROM (
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS et_date,
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 close_usd
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY' AND window_start < toDateTime('2021-01-01 00:00:00')
GROUP BY et_date
HAVING close_usd > 0
)
)
)
GROUP BY ath_close, bottom_close, rec_date, peak07_date, rec07_dateThe retest no happen. After March 23, the lowest close wey SPY ever print na $243.59, on 2020-03-24, the same day wey stimulus rally happen. Market no ever close again within 9.5% of the bottom. The pre-COVID high of $338.31 come back on 2020-08-18, 103 trading sessions after the low. Na 181-day round trip from all-time high go back to all-time high. The 2008 comparison, calculated the same way: SPY’s 2007-10-09 peak no see new closing high until 2013-03-14, 1983 days later. Two crises, two different speeds, but one market wey don change how e dey behave in between.
Wetin this day teach
Three lessons wey worth make person internalize. First, na the gap do the work: big part of the day gain don happen before market open. Anybody wey wait for regular-hours entry pay higher price throughout the day. Na the overnight gap mechanics dey show for maximum amplitude. Second, the bottom come before confirmation: March 23 close na the low. E only become clear after sessions like this one refuse to retest am. Third, velocity fit work for both sides: the same market structure wey compress -34.2% drawdown enter the 24 rows above also compress the first recovery into three sessions. Speed na feature of the market plumbing for that era, no be result of good news or bad news.
The skeptics for that week no be fools. Breadth, the burst and the retest wey never happen — every evidence wey separate March 24 from the failed rallies — fit only be measured with hindsight. The difference between bear-market rally and new bull market no dey visible on day one, but within months e dey obvious.
COVID rally FAQ
Wetin happen on March 24, 2020?
Na the first full session after Fed announce unlimited-QE. SPY open with gap of +5.4% and close +9.5%. Na the #3 biggest one-day gain for the 5613 sessions wey our minute tape cover. Only two days for October 2008 beat am.
Wetin Fed announce on March 23, 2020?
Fed announce open-ended purchases of Treasury and mortgage-backed securities, “for the amounts wey dem need.” E also announce PMCCF and SMCCF, wey be the first-ever facilities wey Fed use buy corporate bonds. Fed revive TALF for consumer credit too. Statement come out 8:00 am ET, before market open. But SPY still close that day for the bear-market low.
March 23, 2020 be the bottom of the COVID crash?
Yes, based on closing price. SPY record low close of $222.51 on March 23, some hours after Fed announcement. E never come back to that level. The lowest close after that happen on 2020-03-24, and e dey 9.5% above the bottom.
When stock market recover its pre-COVID high?
SPY first close above its February 19, 2020 all-time-high close on 2020-08-18. That happen 103 trading sessions after the bottom. The full round trip take 181 days, compared with 1983 days for the equivalent journey during the 2008 crash.
Person fit buy after the news and still do well?
Market open with gap of +5.4% before regular trading start. The day continue to rise. Every later entry cost pass the previous close. Even so, the market still remain far below where e reach by the August 2020 recovery.
Data notes
- The closes na last prints for regular session, from 9:30 am-4:00 pm ET minute tape. Dem fit differ from official auction closes by a few cents.
- Recovery dates na price-only. Total-return measure, with dividends reinvested, recover earlier by design.
- The 10-year yield join Treasury daily series on trading dates. Basis points mean hundredths of one percentage point.
- The rank panel sample start from 2003-09-10. For this page, “since 2003” mean that window, no be all the market history wey dem record.
Every panel above na stored, versioned query for the historical tape. Expand the SQL to see how dem measure each one. You wan feel this day instead of just reading about am? E dey among the playable scenarios for the Strasmore Labs trading simulator.