Is a High Put/Call Ratio Bullish?
Contrarians call a high put/call ratio bullish. See the percentile bands for equity and index options, and what actually followed the extreme readings.
A high put/call ratio is the classic contrarian bullish reading: unusually heavy put buying is taken as a sign the crowd has already braced for a fall. Acting on that reading runs into one problem. "High" has no fixed value, and a reading of 1.15 on your screen can be an extreme on single-stock options and an ordinary session on index products.
What the question needs is a reference distribution. This page gives one for both books, then measures what followed the high readings. The put/call ratio explainer covers how the ratio is built, and everything below starts from that definition.
Two notes on the measurement. Every ratio here counts contracts traded that day, which makes it a volume ratio; open interest counts positions still open and sits at a different level (volume versus open interest separates the two). The numbers also come from a fixed basket, ten household single-stock names against three broad-market ETFs, rather than a whole-market tape, so read the shape of the distribution rather than the last decimal.
What counts as a high put/call ratio?
A percentile answers the question a level cannot. The curve below covers every session from January 2022 through July 2026, one ratio per session for each book.
The exact SQL behind every number
WITH book_days AS (
SELECT date AS day,
if(underlying_symbol IN ('SPY', 'QQQ', 'IWM'), 'index_etf', 'equity') AS book,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'P') AS put_volume,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'C') AS call_volume
FROM global_markets.options_greeks
WHERE date >= toDate('2022-01-01')
AND date <= toDate('2026-07-31')
AND volume > 0
AND underlying_symbol IN ('SPY', 'QQQ', 'IWM', 'AAPL', 'MSFT', 'NVDA', 'AMZN',
'META', 'TSLA', 'GOOGL', 'JPM', 'KO', 'XOM')
GROUP BY day, book
HAVING call_volume > 0
),
paired AS (
SELECT day,
toFloat64(sumIf(put_volume, book = 'equity'))
/ toFloat64(sumIf(call_volume, book = 'equity')) AS equity_pcr,
toFloat64(sumIf(put_volume, book = 'index_etf'))
/ toFloat64(sumIf(call_volume, book = 'index_etf')) AS index_pcr
FROM book_days
GROUP BY day
HAVING sumIf(call_volume, book = 'equity') > 0
AND sumIf(call_volume, book = 'index_etf') > 0
),
curves AS (
SELECT arrayMap(v -> round(v, 2),
quantilesDeterministic(0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99)
(equity_pcr, cityHash64(day))) AS eq,
arrayMap(v -> round(v, 2),
quantilesDeterministic(0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99)
(index_pcr, cityHash64(day))) AS idx
FROM paired
)
SELECT z.1 AS percentile,
z.2 AS equity_pcr,
z.3 AS index_etf_pcr
FROM (
SELECT arrayJoin(arrayZip(['p05', 'p10', 'p25', 'p50', 'p75', 'p90', 'p95', 'p99'], eq, idx)) AS z
FROM curves
)On the single-stock basket, the median session came in at 0.67 puts per call. The top decile of sessions started at 0.89, and the most extreme one percent cleared 1.34. The quiet end is just as useful for calibration: one session in twenty finished at or below 0.48.
The ETF column is a different distribution at every point on the curve. Its median session read 1.45, above the one-to-one line that the single-stock median sits well under, and its own top decile began at 1.77. A ratio quoted without naming its book is close to meaningless. That 1.15 print means two different things depending on which column it came from.
Why an index ratio above 1 is normal and an equity ratio above 1 is not
Split the same volume by underlying and the levels separate cleanly.
The exact SQL behind every number
WITH symbol_days AS (
SELECT underlying_symbol AS symbol,
date AS day,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'P') AS put_volume,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'C') AS call_volume
FROM global_markets.options_greeks
WHERE date >= toDate('2026-01-01')
AND date <= toDate('2026-07-31')
AND volume > 0
AND underlying_symbol IN ('SPY', 'QQQ', 'IWM', 'AAPL', 'MSFT', 'NVDA', 'AMZN',
'META', 'TSLA', 'GOOGL', 'JPM', 'KO', 'XOM')
GROUP BY symbol, day
HAVING call_volume > 0
)
SELECT symbol,
round(toFloat64(sum(put_volume)) / toFloat64(sum(call_volume)), 2) AS put_call_ratio,
round(100 * countIf(put_volume > call_volume) / count(), 1) AS above_one_pct,
count() AS observations
FROM symbol_days
GROUP BY symbol
ORDER BY put_call_ratio DESCOver the first seven months of 2026, IWM sat at the top of the table at 2.73 puts per call, finishing above one on 98.6% of its 144 sessions. At the bottom, XOM traded 0.38, above one on 2.8% of sessions.
The gap is structural. Broad-market ETF options are where portfolio hedging is done: a manager holding two hundred positions buys index puts rather than puts on every holding, and that flow has no matching call trade beside it. Single-stock option volume leans the other way, toward calls bought for upside and calls written against stock already owned. A "total" ratio blends both books, which puts it between the two and moves it with the day's product mix as much as with sentiment.
One more level shift is worth knowing. Index and ETF volume now carries a large share of same-day expiries (0DTE options), a category that did not trade at anything like this scale when the familiar thresholds were written down. Old levels do not transfer to a tape with a different composition.
Is a high put/call ratio bullish? What followed the extremes
The contrarian case is testable. Sort every session into fifths by that day's equity ratio, then measure how the S&P 500 tracker moved over the following 5 and 20 sessions.
The exact SQL behind every number
WITH raw AS (
SELECT date AS day,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'P') AS put_volume,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'C') AS call_volume
FROM global_markets.options_greeks
WHERE date >= toDate('2022-01-01')
AND date <= toDate('2026-07-31')
AND volume > 0
AND underlying_symbol IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'META',
'TSLA', 'GOOGL', 'JPM', 'KO', 'XOM')
GROUP BY day
HAVING call_volume > 0
),
equity_pcr AS (
SELECT day, toFloat64(put_volume) / toFloat64(call_volume) AS pcr
FROM raw
),
spy_daily AS (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS day,
toFloat64(argMax(close, window_start)) AS close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2022-01-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-08-01')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY day
),
forward AS (
SELECT day,
close,
any(close) OVER (ORDER BY day ASC ROWS BETWEEN 5 FOLLOWING AND 5 FOLLOWING) AS close_5,
any(close) OVER (ORDER BY day ASC ROWS BETWEEN 20 FOLLOWING AND 20 FOLLOWING) AS close_20
FROM spy_daily
),
joined AS (
SELECT p.day AS day,
p.pcr AS pcr,
100 * (f.close_5 / f.close - 1) AS fwd_5d_pct,
100 * (f.close_20 / f.close - 1) AS fwd_20d_pct
FROM equity_pcr AS p
INNER JOIN forward AS f ON p.day = f.day
WHERE f.close_5 > 0 AND f.close_20 > 0
),
cuts AS (
SELECT quantilesDeterministic(0.2, 0.4, 0.6, 0.8)(pcr, cityHash64(day)) AS q
FROM joined
)
SELECT multiIf(pcr < q[1], 'lowest fifth',
pcr < q[2], 'second fifth',
pcr < q[3], 'middle fifth',
pcr < q[4], 'fourth fifth',
'highest fifth') AS pcr_band,
count() AS observations,
round(min(pcr), 2) AS band_floor_ratio,
round(quantileDeterministic(0.5)(fwd_5d_pct, cityHash64(day)), 2) AS fwd_5d_median_pct,
round(quantileDeterministic(0.5)(fwd_20d_pct, cityHash64(day)), 2) AS fwd_20d_median_pct,
round(quantileDeterministic(0.25)(fwd_20d_pct, cityHash64(day)), 2) AS fwd_20d_p25_pct,
round(quantileDeterministic(0.75)(fwd_20d_pct, cityHash64(day)), 2) AS fwd_20d_p75_pct
FROM joined CROSS JOIN cuts
GROUP BY pcr_band
ORDER BY band_floor_ratio ASCThe calmest fifth of sessions starts at 0.34 and the busiest fifth begins at 0.81, so the top band holds every reading a contrarian would call stretched. Across those 226 sessions, the median 20-session move measured 3%, against 0.91% after the calmest fifth. Five sessions out, the same two bands read 0.82% and 0.3%.
Medians hide the part that matters. Inside the top fifth, the middle half of 20-session outcomes ran from -1.82% to 5.71%. Inside the calmest fifth, the middle half ran from -0.83% to 2.67%. Those two ranges cover much of the same ground. The band a session falls into moves the center of the distribution, and it barely narrows the range around that center, which is the part a rule of thumb leaves out.
One caveat on weight. January 2022 through July 2026 holds one bear market and the recovery that followed, roughly 226 sessions per band. That is a real sample and it is one stretch of market history.
Do extreme readings cluster at market lows?
They cluster in falling months, which is a different claim. Group every month from 2022 onward by how the tracker moved inside it, then count how many top-decile put/call readings landed in each group.
The exact SQL behind every number
WITH raw AS (
SELECT date AS day,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'P') AS put_volume,
sumIf(volume, upper(substring(ticker, length(ticker) - 8, 1)) = 'C') AS call_volume
FROM global_markets.options_greeks
WHERE date >= toDate('2022-01-01')
AND date <= toDate('2026-07-31')
AND volume > 0
AND underlying_symbol IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'META',
'TSLA', 'GOOGL', 'JPM', 'KO', 'XOM')
GROUP BY day
HAVING call_volume > 0
),
equity_pcr AS (
SELECT day, toFloat64(put_volume) / toFloat64(call_volume) AS pcr
FROM raw
),
cut AS (
SELECT quantileDeterministic(0.90)(pcr, cityHash64(day)) AS p90
FROM equity_pcr
),
by_month AS (
SELECT toStartOfMonth(day) AS month_start,
countIf(pcr >= cut.p90) AS extreme_reads,
quantileDeterministic(0.5)(pcr, cityHash64(day)) AS median_pcr
FROM equity_pcr CROSS JOIN cut
GROUP BY month_start
),
spy_daily AS (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS day,
toFloat64(argMax(close, window_start)) AS close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2022-01-01')
AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY day
),
spy_month AS (
SELECT toStartOfMonth(day) AS month_start,
100 * (argMax(close, day) / argMin(close, day) - 1) AS move_pct
FROM spy_daily
GROUP BY month_start
)
SELECT multiIf(s.move_pct <= -3, 'index down 3% or more',
s.move_pct < 0, 'index down under 3%',
s.move_pct < 3, 'index up under 3%',
'index up 3% or more') AS index_move_bucket,
count() AS observations,
round(avg(m.extreme_reads), 1) AS top_decile_reads_avg,
round(quantileDeterministic(0.5)(m.median_pcr, cityHash64(m.month_start)), 2) AS typical_pcr_ratio
FROM by_month AS m
INNER JOIN spy_month AS s ON m.month_start = s.month_start
GROUP BY index_move_bucket
ORDER BY min(s.move_pct) ASCMonths where the tracker finished 3% or more lower averaged 4.2 top-decile readings apiece, against 1.9 in months it gained 3% or more. Each group's median daily reading sits in the last column, at 0.75 for the worst group and 0.62 for the best. The two ends of the table hold 12 and 22 months.
Extreme put/call readings and falling prices show up together. That is co-occurrence, and the forward numbers in the previous section are what it is worth. The ratio is a same-day measurement of a same-day tape, so its spikes sit inside declines rather than in front of them. A low is a label that gets attached afterwards.
Reading a level against a distribution
Two habits fall out of the numbers above.
First, compare like with like. A reading means something only against the same book's own history over a comparable window, which is exactly what a percentile table gives you and what a remembered threshold does not.
Second, watch the denominator. The ratio falls when call volume surges even if put buying never changes. A drop in the ratio and a spike in it can each come from one side of the fraction moving alone, so reading put volume and call volume separately keeps the arithmetic visible.
The same discipline applies to every sentiment gauge quoted as a bare number. The VIX and a 30% implied volatility print are unreadable without their own distributions, and contrarian sentiment rules hold up under data only when the reference level is measured rather than assumed.
FAQ
Is a high put/call ratio bullish?
The contrarian reading treats heavy put buying as a crowd already positioned for a fall. Measured against the single-stock basket above, sessions in the top fifth of readings were followed by a median 20-session move of 3% in the S&P 500 tracker, against 0.91% after the calmest fifth, with the middle half of top-fifth outcomes spanning -1.82% to 5.71%.
What is a normal put/call ratio?
It depends entirely on which options are counted. On a basket of ten household single-stock names, the median session from 2022 through July 2026 read 0.67 puts per call. On three broad-market ETFs over the same sessions, the median read 1.45.
Why is the index put/call ratio higher than the equity put/call ratio?
Broad index and ETF options are the standard vehicle for portfolio hedging, and a hedger buying index puts has no matching call trade beside it. Single-stock volume leans toward calls bought for upside and calls written against shares already held, which keeps that book's ratio lower.
Does the put/call ratio work for market timing?
The measurement above shows the distribution of what followed each fifth of readings, and the spread inside any one band is far wider than the distance between band medians. Top-decile readings also concentrate in months the index finished lower, at 4.2 per month in the worst group.
What is the difference between a volume put/call ratio and an open interest one?
A volume ratio counts contracts traded during a session, so it resets every day and reacts fast. An open interest ratio counts positions still outstanding, which changes slowly and sits at its own level. The two are not interchangeable, and a threshold borrowed from one will misread the other.
How these numbers are built
- Source: one row per option contract per session, carrying that session's traded volume. Puts and calls are separated from the OCC contract code, where the last eight characters are the strike and the character before them is C for a call and P for a put. Open the SQL under any panel for the exact table and filters.
- Every ratio on this page is a volume ratio. Open interest is not part of the computation, so nothing here is an open-interest ratio.
- No implied-volatility quality filter is applied. Contracts enter on traded volume alone, since these panels count trading activity rather than measure volatility.
- The basket: AAPL, MSFT, NVDA, AMZN, META, TSLA, GOOGL, JPM, KO and XOM for the single-stock book, plus SPY, QQQ and IWM for the index-ETF book. Exchange-published ratios cover the whole market and include index products such as SPX, so their levels sit elsewhere.
- Index prices are the last regular-session print of each day for the S&P 500 tracker. Forward returns drop the final 20 sessions of the sample, which have no 20-session future yet. Monthly moves run from the first session's close to the last session's close inside each month.
Every panel above expands to the SQL behind it, and the same percentile queries run against any underlying you follow on the Strasmore terminal.