High-Low Index na Wetin? Market Breadth Formula
High-Low Index na 10-day average of Record High Percent. See formula with round numbers, plus why small sample size fit mislead for quiet market.
High-Low Index na market breadth measure wey dey come from two counts: na the 10-day simple moving average of Record High Percent. Record High Percent na new 52-week highs divided by the total of new 52-week highs plus new 52-week lows, multiply by 100. Reading wey pass 50 mean say more stocks dey print new highs than new lows. Reading below 50 mean say na the reverse. Formula short reach like that. Wetin follow na the arithmetic wey dem work out with round numbers, plus the sample-size flaw wey fit make reading wey look decisive depend on just a few stocks.
How dem dey calculate High-Low Index?
Na two steps, and both fit enter one line.
- Record High Percent, for one trading session: count the stocks for your universe wey close at new 52-week high, count the ones wey close at new 52-week low, divide the highs by the total of the two, then multiply by 100.
- High-Low Index: calculate the average of the last 10 daily Record High Percent readings.
Round numbers go make am clear. The tape print 90 new highs and 30 new lows. Highs plus lows na 120, and 90 divided by 120 na 0.75, so Record High Percent for that session na 75. Now take readings from ten sessions: 75, 70, 65, 60, 55, 50, 45, 40, 35 and 30. Their total na 525, and 525 divided by 10 na 52.5. High-Low Index go read 52.5 on a day wey that day own Record High Percent na 75.
Na that difference be the trade-off wey moving average dey create. Ten days of memory dey smooth a series wey fit swing from 100 go 0 overnight, but e go make the index arrive late. The index dey answer where breadth don dey over two weeks. E no dey answer wetin happen this afternoon.
Wetin count as new 52-week high?
Stock make new 52-week high when e price pass every price wey e print for the previous 52 weeks. Data providers no dey agree on the basis: some compare intraday highs and lows, while others compare closing prices only. The two methods fit give different counts for the same session. Na closing prices we dey use everywhere for this page. New 52-week highs and lows explain the list itself.
The panel below count both extremes for each session, using fixed basket of 40 large-cap US names, from April 1 reach July 31, 2026. Basket wey get that size keep the counts small enough to check by hand. Na this make the problem with the formula show clearly later.
The exact SQL behind every number
WITH daily_close AS (
SELECT ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
argMax(close, window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','MSFT','NVDA','AMZN','GOOGL','AVGO','JPM','JNJ','XOM','PG',
'KO','PEP','WMT','HD','CVX','MRK','PFE','ABBV','CSCO','ORCL',
'CRM','ADBE','MCD','NKE','VZ','T','DIS','BA','CAT','GE',
'IBM','MMM','UNH','LLY','COST','TGT','SBUX','GS','MS','LIN')
AND window_start >= toDateTime('2025-03-01 00:00:00')
AND window_start < toDateTime('2026-08-01 05:00:00')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, session_date
),
extremes AS (
SELECT cur.ticker AS ticker,
cur.session_date AS session_date,
cur.close_px AS close_px,
max(hist.close_px) AS high_52w,
min(hist.close_px) AS low_52w
FROM daily_close AS cur
INNER JOIN daily_close AS hist ON cur.ticker = hist.ticker
WHERE cur.session_date >= toDate('2026-04-01')
AND hist.session_date <= cur.session_date
AND hist.session_date > cur.session_date - 364
GROUP BY cur.ticker, cur.session_date, cur.close_px
),
daily AS (
SELECT session_date,
countIf(close_px >= high_52w) AS new_highs,
countIf(close_px <= low_52w) AS new_lows,
countIf(close_px >= high_52w) + countIf(close_px <= low_52w) AS names_at_extreme
FROM extremes
GROUP BY session_date
HAVING names_at_extreme > 0
)
SELECT session_date AS date,
formatDateTimeInJodaSyntax(session_date, 'MMM d') AS session_label,
new_highs,
new_lows
FROM daily
ORDER BY session_dateAcross 77 sessions, the two counts rarely rise together. For Jul 29, wey be the last session inside the window, 3 of the 40 names close at 52-week high, while 0 close at 52-week low. For Apr 1, wey be the first session, the split na 1 highs against 1 lows. Every other name for the basket remain somewhere inside its own range and no enter the calculation.
Wetin be good High-Low Index reading?
The 50 line na the pivot, where highs and lows dey balance. The usual strong marker na 70. For there, new highs don pass new lows by better than two-to-one across the full 10-day average. The usual weak marker na 30, wey be the opposite. Between 30 and 70, the index dey show say market extremes dey mixed. Na for this range e dey stay most times.
The panel below dey plot the raw daily Record High Percent against its own 10-day average for the same basket. You go see the lag for the chart, instead of us describing am.
The exact SQL behind every number
WITH daily_close AS (
SELECT ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
argMax(close, window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','MSFT','NVDA','AMZN','GOOGL','AVGO','JPM','JNJ','XOM','PG',
'KO','PEP','WMT','HD','CVX','MRK','PFE','ABBV','CSCO','ORCL',
'CRM','ADBE','MCD','NKE','VZ','T','DIS','BA','CAT','GE',
'IBM','MMM','UNH','LLY','COST','TGT','SBUX','GS','MS','LIN')
AND window_start >= toDateTime('2025-03-01 00:00:00')
AND window_start < toDateTime('2026-08-01 05:00:00')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, session_date
),
extremes AS (
SELECT cur.ticker AS ticker,
cur.session_date AS session_date,
cur.close_px AS close_px,
max(hist.close_px) AS high_52w,
min(hist.close_px) AS low_52w
FROM daily_close AS cur
INNER JOIN daily_close AS hist ON cur.ticker = hist.ticker
WHERE cur.session_date >= toDate('2026-04-01')
AND hist.session_date <= cur.session_date
AND hist.session_date > cur.session_date - 364
GROUP BY cur.ticker, cur.session_date, cur.close_px
),
daily AS (
SELECT session_date,
countIf(close_px >= high_52w) AS new_highs,
countIf(close_px >= high_52w) + countIf(close_px <= low_52w) AS names_at_extreme
FROM extremes
GROUP BY session_date
HAVING names_at_extreme > 0
),
rhp AS (
SELECT session_date,
round(100 * new_highs / names_at_extreme, 1) AS record_high_pct
FROM daily
),
smoothed AS (
SELECT session_date,
record_high_pct,
round(avg(record_high_pct) OVER (ORDER BY session_date
ROWS BETWEEN 9 PRECEDING AND CURRENT ROW), 1) AS high_low_index_pct,
count() OVER (ORDER BY session_date
ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) AS sessions_in_window
FROM rhp
)
SELECT session_date AS date,
formatDateTimeInJodaSyntax(session_date, 'MMM d') AS session_label,
record_high_pct,
high_low_index_pct
FROM smoothed
WHERE sessions_in_window = 10
ORDER BY session_dateThe plotted range start with Record High Percent at 100 on Apr 15, while the index dey at 52.5. E close at 100 on Jul 29, with the index at 64. Read each one against the 70 and 30 markers. Also read the distance between the two lines as the cost of smoothing. For any given day, the average still dey carry nine older sessions. The panel exclude the first nine sessions for the window, because 10-day average need 10 days.
Denominator no dey cover most of the market
Na this limitation you need remember. Record High Percent denominator na new highs plus new lows, and nothing else. Stock wey dey rest for middle of im 52-week range no dey numerator or denominator. The index no fit tell difference between session wey 900 out of 3,000 names reach extreme and another one wey na only 9 reach there. E go print both with the same confident two-digit number.
Imagine exchange wey get 6,000 names during one slow August session: 40 stocks make new highs, while 20 make new lows. Record High Percent na 40 divided by 60, or 66.7, and e dey look like broad advance. But na only 1% of the listings dem use calculate am. The other 5,940 names no get any vote.
The 40-name basket dey show this matter for screen in a way wey person fit count. Every session inside the window dey sort into bucket based on how many names print any extreme at all.
The exact SQL behind every number
WITH daily_close AS (
SELECT ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
argMax(close, window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','MSFT','NVDA','AMZN','GOOGL','AVGO','JPM','JNJ','XOM','PG',
'KO','PEP','WMT','HD','CVX','MRK','PFE','ABBV','CSCO','ORCL',
'CRM','ADBE','MCD','NKE','VZ','T','DIS','BA','CAT','GE',
'IBM','MMM','UNH','LLY','COST','TGT','SBUX','GS','MS','LIN')
AND window_start >= toDateTime('2025-03-01 00:00:00')
AND window_start < toDateTime('2026-08-01 05:00:00')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, session_date
),
extremes AS (
SELECT cur.ticker AS ticker,
cur.session_date AS session_date,
cur.close_px AS close_px,
max(hist.close_px) AS high_52w,
min(hist.close_px) AS low_52w
FROM daily_close AS cur
INNER JOIN daily_close AS hist ON cur.ticker = hist.ticker
WHERE cur.session_date >= toDate('2026-04-01')
AND hist.session_date <= cur.session_date
AND hist.session_date > cur.session_date - 364
GROUP BY cur.ticker, cur.session_date, cur.close_px
),
daily AS (
SELECT session_date,
countIf(close_px >= high_52w) AS new_highs,
countIf(close_px >= high_52w) + countIf(close_px <= low_52w) AS names_at_extreme
FROM extremes
GROUP BY session_date
HAVING names_at_extreme > 0
)
SELECT multiIf(names_at_extreme <= 3, '1 to 3 names',
names_at_extreme <= 9, '4 to 9 names',
'10 or more names') AS sample_size_bucket,
count() AS readings,
round(avg(names_at_extreme), 1) AS avg_names_in_denominator,
round(quantileDeterministic(0.5)(100 * new_highs / names_at_extreme,
cityHash64(session_date)), 1) AS median_record_high_pct,
round(quantileDeterministic(0.5)(abs(100 * new_highs / names_at_extreme - 50),
cityHash64(session_date)), 1) AS median_distance_from_50
FROM daily
GROUP BY sample_size_bucket
ORDER BY avg_names_in_denominatorFor the 39 sessions wey dey inside 1 to 3 names bucket, the whole reading rest on average of 2 names out of 40. Those sessions get median Record High Percent of 100 and median distance of 50 points from the 50 line. The 10 or more names sessions, with 1 of dem averaging 10 names, get median 70 and sit 20 points from the middle.
The arithmetic set limit on wetin thin session fit print. If three names reach extreme, the only possible readings na 0, 33.3, 66.7 and 100. If na two names, na only 0, 50 and 100. Reading of 66.7 wey come from two highs and one low get decimal point wey the sample no fit support. The simple habit wey fit correct this na: read the raw counts of highs and lows beside the index. Never read the index by itself.
Market breadth: High-Low Index vs advance/decline
Both measures dey ask how many stocks dey participate. But dem dey look am across different time horizons. Advance/decline breadth dey count every name wey close up or down compared with yesterday. The denominator na the whole universe, and e dey describe one day. High-Low Index dey count only names wey reach 52-week extreme. Its denominator small, and e dey show where prices dey compared with one full year of history. Market fit broadly rise for the day while only few names dey anywhere near 52-week high.
The panel below smooths both measures across the same 10 sessions, for the same basket, so dem fit dey on one scale.
The exact SQL behind every number
WITH daily_close AS (
SELECT ticker,
toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
argMax(close, window_start) AS close_px
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker IN ('AAPL','MSFT','NVDA','AMZN','GOOGL','AVGO','JPM','JNJ','XOM','PG',
'KO','PEP','WMT','HD','CVX','MRK','PFE','ABBV','CSCO','ORCL',
'CRM','ADBE','MCD','NKE','VZ','T','DIS','BA','CAT','GE',
'IBM','MMM','UNH','LLY','COST','TGT','SBUX','GS','MS','LIN')
AND window_start >= toDateTime('2025-03-01 00:00:00')
AND window_start < toDateTime('2026-08-01 05:00:00')
AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
GROUP BY ticker, session_date
),
extremes AS (
SELECT cur.ticker AS ticker,
cur.session_date AS session_date,
cur.close_px AS close_px,
max(hist.close_px) AS high_52w,
min(hist.close_px) AS low_52w
FROM daily_close AS cur
INNER JOIN daily_close AS hist ON cur.ticker = hist.ticker
WHERE cur.session_date >= toDate('2026-04-01')
AND hist.session_date <= cur.session_date
AND hist.session_date > cur.session_date - 364
GROUP BY cur.ticker, cur.session_date, cur.close_px
),
daily AS (
SELECT session_date,
countIf(close_px >= high_52w) AS new_highs,
countIf(close_px >= high_52w) + countIf(close_px <= low_52w) AS names_at_extreme
FROM extremes
GROUP BY session_date
HAVING names_at_extreme > 0
),
prior AS (
SELECT ticker,
session_date,
close_px,
lagInFrame(close_px, 1) OVER (PARTITION BY ticker ORDER BY session_date
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
FROM daily_close
),
breadth AS (
SELECT session_date,
round(100 * countIf(close_px > prev_close) / count(), 1) AS advancing_pct
FROM prior
WHERE session_date >= toDate('2026-04-01')
AND prev_close > 0
GROUP BY session_date
HAVING count() >= 20
),
combined AS (
SELECT d.session_date AS session_date,
round(100 * d.new_highs / d.names_at_extreme, 1) AS record_high_pct,
b.advancing_pct AS advancing_pct
FROM daily AS d
INNER JOIN breadth AS b ON d.session_date = b.session_date
),
smoothed AS (
SELECT session_date,
round(avg(record_high_pct) OVER (ORDER BY session_date
ROWS BETWEEN 9 PRECEDING AND CURRENT ROW), 1) AS high_low_index_pct,
round(avg(advancing_pct) OVER (ORDER BY session_date
ROWS BETWEEN 9 PRECEDING AND CURRENT ROW), 1) AS advancing_pct_10d,
count() OVER (ORDER BY session_date
ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) AS sessions_in_window
FROM combined
)
SELECT session_date AS date,
formatDateTimeInJodaSyntax(session_date, 'MMM d') AS session_label,
high_low_index_pct,
advancing_pct_10d
FROM smoothed
WHERE sessions_in_window = 10
ORDER BY session_dateThe pair starts the plotted range at 52.5 for High-Low Index and 52.8 for the 10-day advancing share on Apr 15. E ends at 64 and 52.5 on Jul 29, across 68 plotted sessions. The advancing share dey move inside a narrow band. For normal session, roughly half of the basket dey close higher. When you average 10 of those sessions, the line dey near the middle of the scale. High-Low Index no get that kind anchor. If no name inside the basket prints a 52-week low for 10 sessions straight, every daily reading go be 100, and the average go remain 100.
Breadth dey make more sense when you put participation measures beside am. Relative volume dey ask whether one name dey trade unusually heavily today compared with its own normal level. The week's biggest stock movers dey show which names actually move far, while unusual volume stocks this week dey show where the trading activity go.
High-Low Index FAQ
Wetin High-Low Index reading above 70 mean?
E mean say new 52-week highs pass new 52-week lows by more than two-to-one across the average of the last ten sessions. Na the conventional marker for strong tape. E no talk anything about how many stocks reach extreme level at all. Na that number you suppose check alongside am.
Wetin be the difference between Record High Percent and High-Low Index?
Record High Percent na the figure for one session: new highs divided by new highs plus new lows, multiply by 100. High-Low Index na the 10-day simple moving average of that figure. One describe today. The other describe the last two weeks.
High-Low Index fit be 0 or 100?
Yes. If no stock for the universe make new 52-week low for ten sessions straight, every daily reading go be 100, and the average go still be 100. When no stock make either high or low, the fraction get zero denominator, so that session no get defined reading.
High-Low Index na the same thing as advance/decline breadth?
No. Advance/decline breadth divide by the whole universe and describe direction for one day. High-Low Index divide by the small group of stocks wey dey 52-week extremes and describe their position against one year of prices.
How many stocks usually dey inside High-Low Index denominator?
For the 40-name basket wey we measure from April to July 2026, sessions inside the 1 to 3 names bucket average 2 names for the denominator. Exchange-wide counts dey bigger in absolute terms, but dem still be small part of all listings. Na why raw counts suppose dey for the screen beside the index.
Every count for this page come from stored, versioned query over closing prices. Open any panel to read the SQL behind am, or rebuild the same breadth series on basket of your own for the Strasmore terminal.