The governed definitions this build depends on. Read them first if a term is unfamiliar.
- PrimaryHigh-Low IndexHigh-Low Index is new highs as a percentage of the combined new-high and new-low count.
- PrerequisiteNew 52-Week HighNew 52-Week High identifies a security whose selected price reaches a maximum over the declared 52-week window.
- PrerequisiteNew 52-Week LowNew 52-Week Low identifies a security whose selected price reaches a minimum over the declared 52-week window.
- ImportantHigh-Low BreadthHigh-Low Breadth describes market participation using counts or transformations of securities making new highs and new lows.
- ImportantHigh-Low RatioHigh-Low Ratio compares the number of new highs with the number of new lows by division.
- ImportantThreshold CrossingThreshold Crossing occurs when a measured series moves across a declared level under an explicit comparison rule.
An index level can look healthy while participation underneath it is narrow. High-Low Index makes one precise internal question auditable: Across the latest ten valid sessions, what average share of all new extremes were new highs? The output is descriptive evidence, not a forecast or trading instruction.
The decision this measure owns
Choose the index when a bounded composition measure with deliberate ten-session memory is preferable to raw scale or an unbounded ratio.
The family contains nearby measures because “breadth” is not one calculation. A raw difference, a ratio, a bounded smoother, and three moving-average horizons can all be correct while answering different questions. The implementation must name the universe, session, method, and knowledge cutoff before it publishes any of them.
Freeze the evidence before the formula
Use stable security identifiers and membership effective for the evaluated session. Record both effective_at and available_at: the first says when the market state applies; the second says when a system could know it. A correction received tomorrow cannot enter today's decision retroactively.
The selected formula follows the maintained StockCharts High-Low Index convention. That provider convention establishes the transform used here; it does not establish universal thresholds or investment performance.
Every number in this tutorial is labelled synthetic. No provider observation is republished.
Formula and unit
The output unit is percentage points. Calculate at full precision and round only for display. Keep the diagnostics that explain the result—counts, denominator, window, comparison flags, status, reason, and series identity.
Work the canonical state
First convert every daily pair into Record High Percent: 100 × NH / (NH + NL). Only then average ten daily percentages. Ten Record High Percent values—75, 58.3333, 33.3333, 50, 83.3333, 25, 66.6667, 41.6667, 91.6667, and 16.6667—sum to 541.6667 and average to 54.1667.
This order matters. Summing ten highs and ten lows first would weight high-activity sessions more heavily and answer a different question.
The first visual preserves the existing parity fixture. It separates ready inputs, author-derived arithmetic, and the display-rounded output.
Inspect the decision boundary
A 0/0 session has no Record High Percent. Any ten-session window containing it is incomplete; the algorithm never treats it as a neutral 50.
The second visual makes the non-obvious branch visible. It is deliberately synthetic, so the learner can inspect the state without mistaking it for a historical market claim.
Follow the algorithm's branch
The formula runs only after identity, causal availability, and completeness checks pass. An incomplete, warming, unbounded, or unsupported result is part of the algorithm—not an exception to hide in a log.
Reference implementation
The Python and TypeScript modules use the same fixture and return the same value, null state, reason code, and diagnostics. Their order is intentional:
- validate session, identifiers, method, and causal cutoff;
- validate the topic-specific count or price-window contract;
- compute the smallest readable transform;
- publish value and evidence state together.
The implementations are dependency-free. The official pandas rolling-mean documentation is recorded only for standard rolling terminology where relevant; the code does not delegate correctness to pandas.
Run the signature experiment
Open visuals/animated/playground.html. The signature demonstration is: Rolling tray: place ten Record High Percent cards into a buffer, average them, then contaminate one card with 0/0 and withhold the index.
Use Step and Back to inspect one transition, then Play/Pause to follow the same transition function. Switch to the comparison scenario and read the diagnostic, not just the line. Finally choose the failure scenario and confirm that Reset restores the canonical state. Reduced-motion Play advances one deterministic step without starting a timer.
Compare the sibling, not just the number
Unlike the High-Low Ratio, this transform is bounded from 0 to 100 and weights sessions equally after converting each session to a percentage.
That comparison is the purpose of the family map:
Use the map to choose an input object first—new-extreme events or price relative to a moving baseline—then choose transform and memory. Do not select a measure merely because a charting platform exposes it.
Failure and misuse boundaries
- Never backfill today's roster into an older session.
- Never combine provider totals whose security types, sessions, intervals, equality rules, or adjustments differ.
- Never turn absent or future evidence into zero.
- Never infer predictive power, causation, or profitability from implementation correctness.
- Never hide a changed denominator behind a plausible percentage.
For this topic, the decisive rule is: A 0/0 session has no Record High Percent. Any ten-session window containing it is incomplete; the algorithm never treats it as a neutral 50.
Historical-example decision
The decision remains deferred. A named reconstruction would require point-in-time membership, stable security identity, exact methodology, adjustment or high/low lineage, availability and correction times, and redistribution rights. A screenshot or current provider symbol cannot establish those facts. The synthetic case teaches the mechanism without manufacturing historical certainty.
What the learner can now audit
You can calculate HLI_t = SMA10(100 × NH_t / (NH_t + NL_t)), explain its unit, reproduce its canonical value, distinguish its invalid states, and choose it over a sibling for a reason. Review High-Low Ratio if the immediate input concept is unfamiliar. Continue with Percent Above 20-Day MA to see the next transform or horizon.
Primary and provider-method records, applicability, versions, and limitations are maintained in ../REFERENCES.md.
Rendered from the canonical Mermaid sources linked by this article.
Topic decision flow — High-Low Index
Purpose: expose the branch that is unique to HLI_t = SMA10(100 × NH_t / (NH_t + NL_t)) after identity and availability have passed.
Takeaway: A 0/0 session has no Record High Percent. Any ten-session window containing it is incomplete; the algorithm never treats it as a neutral 50.
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
Access date for web sources: 2026-07-25. Source roles are deliberately limited to the claims each record can support.
NASDAQ-FUNDAMENTAL — Nasdaq Fundamental Data
- Organization: Nasdaq
- Source type: Official exchange data-product documentation
- Publication/effective date: Current product page
- Version: Web page accessed 2026-07-25
- URL: https://www.nasdaqtrader.com/Trader.aspx?id=FD
- Accessed: 2026-07-25
- Jurisdiction/applicability: Nasdaq-listed U.S. equities; provider field availability
- Supports: Security-master attributes plus daily and 52-week high/low market-summary fields are distributed on a T+1 basis.
- Limitations: Field availability does not define this package's eligible universe, equality rule, adjustment policy, or redistribution rights.
TRADINGVIEW-HIGHLOW — How High/Low and New High/New Low Are Calculated
- Organization: TradingView
- Source type: Official provider support documentation
- Publication/effective date: Current support article
- Version: Web page accessed 2026-07-25
- URL: https://www.tradingview.com/support/solutions/43000753745-how-are-high-low-and-new-high-new-low-calculated/
- Accessed: 2026-07-25
- Jurisdiction/applicability: TradingView screener convention
- Supports: One provider compares the latest daily high with a period high using a greater-than-or-equal rule.
- Limitations: The interval, date-range, session, and equality choices are provider-specific and are not universal market rules.
STOCKCHARTS-INDEX — High-Low Index
- Organization: StockCharts ChartSchool
- Source type: Maintained provider-method documentation
- Publication/effective date: Current documentation
- Version: Web page accessed 2026-07-25
- URL: https://chartschool.stockcharts.com/table-of-contents/market-indicators/high-low-index
- Accessed: 2026-07-25
- Jurisdiction/applicability: StockCharts High-Low Index convention
- Supports: Record High Percent is
100 × NH/(NH+NL)and the High-Low Index is its ten-day simple moving average. - Limitations: Threshold interpretations are provider practice, not mathematical or performance guarantees.
PANDAS-ROLLING — Rolling Mean Documentation
- Organization: pandas project
- Source type: Official maintained software documentation
- Publication/effective date: pandas 3.0.5 documentation
- Version: 3.0.5, accessed 2026-07-25
- URL: https://pandas.pydata.org/docs/reference/api/pandas.api.typing.Rolling.mean.html
- Accessed: 2026-07-25
- Jurisdiction/applicability: Rolling-window software semantics
- Supports: A rolling object can calculate a window mean; the package's arithmetic-mean language is compatible with standard rolling terminology.
- Limitations: The reference implementations are dependency-free and do not call pandas; this source does not define market breadth.
Evidence decision
Historical example: deferred. A named count-series reconstruction requires a complete point-in-time universe, exact provider classification, availability/correction lineage, stable identifiers, and redistribution rights. The public package uses labelled synthetic teaching data and makes no prediction, causation, threshold-performance, or profitability claim.
Full dependency-light reference implementations in both supported languages.
/** Point-in-time reference implementation for High-Low Index. */
export type HighLowRecord = {
session_date: string;
available_at: string;
source_evidence_state: string;
venue_id: string;
universe_id: string;
methodology_id: string;
lookback_sessions: number;
new_highs: number;
new_lows: number;
eligible_issues: number;
overlap_issues?: number;
};
const MODE: "net" | "ratio" | "index" = "index";
const INDEX_WINDOW = 10;
const identityFields = ["venue_id", "universe_id", "methodology_id", "lookback_sessions"] as const;
function time(value: unknown, field: string): number {
if (typeof value !== "string" || !/(Z|[+-]\d\d:\d\d)$/.test(value)) throw new Error(`${field} must be offset-aware`);
const parsed = Date.parse(value);
if (!Number.isFinite(parsed)) throw new Error(`${field} must be a valid timestamp`);
return parsed;
}
function count(value: unknown, field: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) throw new Error(`${field} must be a non-negative safe integer`);
return value as number;
}
export function calculate(records: HighLowRecord[], decisionTime: string) {
const cutoff = time(decisionTime, "decision_time");
if (!records.length) throw new Error("records must not be empty");
let previousSession = "";
let identity: string | null = null;
const rhp: number[] = [];
return records.map((row) => {
if (!row.session_date || row.session_date <= previousSession) throw new Error("session_date values must be unique and strictly increasing");
previousSession = row.session_date;
const currentIdentity = JSON.stringify(identityFields.map((field) => row[field]));
if (identity === null) identity = currentIdentity;
else if (identity !== currentIdentity) return {status: "unsupported", reason: "series_identity_changed", value: null};
const highs = count(row.new_highs, "new_highs");
const lows = count(row.new_lows, "new_lows");
const eligible = count(row.eligible_issues, "eligible_issues");
const overlap = count(row.overlap_issues ?? 0, "overlap_issues");
if (highs > eligible || lows > eligible || overlap > Math.min(highs, lows)) throw new Error("high/low counts cannot exceed the eligible universe");
if (time(row.available_at, "available_at") > cutoff) return {session_date: row.session_date, status: "incomplete", reason: "future_evidence", value: null};
if (row.source_evidence_state !== "ready") return {session_date: row.session_date, status: "incomplete", reason: "evidence_not_ready", value: null};
let value: number | null = null;
let status = "resolved";
let reason: string | null = null;
if (MODE === "net") value = highs - lows;
else if (MODE === "ratio") {
if (lows === 0) {
status = highs > 0 ? "unbounded" : "incomplete";
reason = highs > 0 ? "zero_new_lows" : "no_new_extremes";
} else value = highs / lows;
} else {
const denominator = highs + lows;
if (denominator === 0) {
rhp.push(Number.NaN);
status = "incomplete";
reason = "no_new_extremes";
} else {
rhp.push(100 * highs / denominator);
if (rhp.length < INDEX_WINDOW) {
status = "warming";
reason = "requires_10_sessions";
} else if (rhp.slice(-INDEX_WINDOW).some((item) => !Number.isFinite(item))) {
status = "incomplete";
reason = "undefined_record_high_percent_in_window";
} else value = rhp.slice(-INDEX_WINDOW).reduce((sum, item) => sum + item, 0) / INDEX_WINDOW;
}
}
return {session_date: row.session_date, new_highs: highs, new_lows: lows, value, status, reason};
});
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.