The governed definitions this build depends on. Read them first if a term is unfamiliar.
- PrimaryMcClellan Volume Summation IndexA cumulative breadth-volume index that adds each ready McClellan Volume Oscillator value to its prior level.
- PrerequisiteExponential Moving AverageAn Exponential Moving Average recursively gives recent observations greater influence than progressively older observations.
- PrerequisiteMcClellan Volume OscillatorA breadth-volume oscillator equal to the 10% Trend minus the 5% Trend of Up Volume minus Down Volume.
- PrerequisiteTraditional McClellan OscillatorA breadth oscillator equal to the 10% Trend minus the 5% Trend of daily advancing issues minus declining issues.
- Important10% TrendThe faster McClellan exponential trend that assigns 10 percent weight to the current breadth input.
- Important5% TrendThe slower McClellan exponential trend that assigns 5 percent weight to the current breadth input.
- ImportantCausal Knowledge CutoffThe time boundary that prevents information unavailable at that moment from influencing a calculation.
- ImportantCumulative Starting LevelThe declared initial constant from which a cumulative recurrence begins before accepted increments are added.
- ImportantCumulative StateA persisted running value updated by adding each newly accepted increment to the previous accumulated value.
- ImportantDown VolumeDown Volume is reported share volume allocated to declining issues under a declared methodology.
- ImportantEMA Period-Label EquivalenceEMA period-label equivalence maps a conventional period label to its smoothing constant.
- ImportantNet Up-Down VolumeNet Up-Down Volume is Up Volume minus Down Volume for one validated session.
- ImportantNo-Imputation Missing-Session PolicyA policy that blocks or pauses calculation instead of converting a missing required session into a zero observation.
- ImportantProcessing CheckpointA persisted valid state and provenance bundle from which deterministic processing can safely resume.
- ImportantReady PointThe first eligible observation at which every state required for an indicator output has been initialized.
- ImportantRecursive StateA stored calculation value that a recurrence carries from one accepted observation into the next update.
- ImportantRevision LineageAn ordered chain connecting each correction to the exact earlier record it supersedes.
- ImportantSMA-Seeded EMA InitializationAn EMA initialization policy that uses the arithmetic mean of the first eligible observations as the initial recursive state.
- ImportantSmoothing ConstantA smoothing constant controls how far an exponential state moves toward each new observation.
- ImportantSuffix RecalculationRecomputing a corrected observation and every later output whose recursive state may depend on it.
- ImportantTotal VolumeTotal Volume is the reported trading volume for a declared market scope and session.
- ImportantUnchanged VolumeUnchanged Volume is reported share volume allocated to unchanged issues under a declared methodology.
- ImportantUp VolumeUp Volume is reported share volume allocated to advancing issues under a declared methodology.
- ImportantVolume Adjustment BasisA volume adjustment basis declares how reported volume values are represented and adjusted within a series.
- ImportantWarm-Up PeriodThe initial span in which an indicator is accumulating enough eligible history to satisfy its declared initialization policy.
What cumulative participation path is produced by adding each ready Volume Oscillator reading? McClellan Volume Summation Index answers that question only when the input unit, warm-up, seed, and evidence cutoff remain explicit.
The numerical path below is synthetic teaching data. The methodology statements are sourced; every displayed input, EMA, oscillator, and index value is author-derived.
The decision before the formula
Choose this index when the cumulative history of raw volume breadth is the object of analysis. The index carries both EMA memory and cumulative memory, making revisions propagate through two state layers.
The six-topic McClellan family has three input choices—raw issue counts, ratio-adjusted issue breadth, and raw allocated volume—and two output choices—an oscillator or a cumulative Summation Index. Sharing the 10%/5% engine does not make those choices interchangeable.
| Question | This topic’s answer |
|---|---|
| What is observed? | Up Volume minus Down Volume in reported shares |
| What memory is retained? | Dual EMA state plus cumulative state |
| What happens when source scale doubles? | Doubling allocated volume doubles every oscillator increment and therefore doubles the zero-seeded cumulative path. |
| What is withheld? | The volume partition is empty or inconsistent, so the cumulative suffix is withheld. |
From evidence to input
The package first resolves one ready revision for every required session at the requested knowledge cutoff. Missing, cancelled, provisional, duplicated, or identity-incompatible evidence is not converted into a plausible zero.
Only after that resolution does the topic transform the record:
Then the shared tracking engine updates:
The 10% and 5% tracking rates correspond to the conventional 19- and 39-period labels because
The labels do not turn exponential memory into finite windows. Old observations decay; they do not disappear exactly.
The important boundary is left to right: source resolution precedes recursion, and accumulation—when present—follows a ready oscillator.
Warm-up is observable state
The package uses the simple average of observations 1–19 as the first fast state and the simple average of observations 1–39 as the first slow state. Before observation 39, the oscillator is null because both states do not yet coexist.
The declared pre-output seed is 0. It translates the level; each daily increment still equals the oscillator.
The canonical cross-language fixture uses a balanced warm-up because it makes exact parity checks easy. The guided lab uses a separate nonzero path to teach the state transition instead of opening on a blank chart.
A nonzero 60-session teaching path
The lab moves through a broad advance, cooling, reacceleration, reversal, and stress/recovery regime.
| Observation | Regime | Input | Fast | Slow | Oscillator | Index |
|---|---|---|---|---|---|---|
| 39 | reversal | 800,000 | 36,478.0323 | 225,641 | -189,163 | -189,163 |
| 40 | stress and recovery | 1,200,000 | 152,830 | 274,359 | -121,529 | -310,692 |
| 45 | stress and recovery | -800,000 | 345,441 | 369,243 | -23,802.2724 | -241,390 |
| 60 | stress and recovery | 200,000 | 124,050 | 191,039 | -66,989.0970 | -1,634,751 |
At observation 39, the fast and slow states are already different. That makes the first ready oscillator a genuine state comparison rather than a zero placeholder. By observation 45, the current input and the two memory states reveal how the algorithm reacts to a regime change.
Notice the sequence, not merely the last number: the input changes first, the fast state reacts more, the slow state follows, and the output records their separation.
The scale comparison that distinguishes this topic
The lab can double the source scale without changing the direction pattern.
| Source scale at observation 45 | Input | Oscillator | Index |
|---|---|---|---|
| 1× | -800,000 | -23,802.2724 | -241,390 |
| 2× | -1,600,000 | -47,604.5449 | -482,780 |
Doubling allocated volume doubles every oscillator increment and therefore doubles the zero-seeded cumulative path. This is the family-level reason to name the input unit before interpreting magnitude.
The highlighted cell is this topic. Move horizontally to change the input unit; move vertically to add or remove cumulative memory.
Data contract: a number is not enough
Issue-count topics require a reconciled universe partition and stable classification method. Ratio-adjusted topics also require a positive advances-plus-declines denominator. Volume topics require nonnegative Up, Down, and Unchanged Volume that reconcile exactly to positive Total Volume, with stable venue, unit, and adjustment basis.
Every record carries session order, effective time, availability time, revision, evidence state, and series identity. A correction changes the dependent EMA suffix; a cumulative topic also changes every later index value.
Failure is a visible result
The failure scenario stops at observation 45 and reports why the next update is withheld: The volume partition is empty or inconsistent, so the cumulative suffix is withheld. The chart preserves the last valid path but does not draw a fictional recursive suffix.
This distinction matters operationally:
- a validated input of zero updates both EMA states;
- a missing or provisional observation performs no update;
- a broken partition is invalid, not neutral;
- a future revision cannot change an earlier knowledge-cutoff result;
- a series-identity change starts a new governed series.
Implementation without hidden assumptions
The reference code separates three responsibilities:
- resolve evidence at the causal cutoff;
- transform one validated record into the topic’s declared input unit;
- update unrounded fast, slow, oscillator, and optional cumulative state.
That separation makes a production review easier: a correct subtraction cannot conceal a broken source partition, and a correct recurrence cannot conceal future information.
Python and TypeScript share the same canonical fixture and reason-code semantics. The teaching dataset is intentionally separate, so richer pedagogy does not turn the implementation test fixture into presentation data.
Evidence boundary and historical decision
McClellan Financial’s first-party material supports the 10%/5% engine and the topic-specific transform or accumulation rule. The package’s SMA initialization, software statuses, causal revision schema, teaching seed, and synthetic scenarios are explicit implementation choices.
A named historical example is deferred for this topic. A reproducible case would require the exact point-in-time universe or volume allocation, source timestamps, corrections, adjustment basis, calibration lineage, and redistribution permission. A synthetic path is better evidence of the mechanism than an incompletely specified market number.
Methodology pages also contain interpretive language and historical thresholds. Those statements are not promoted here into universal trading rules, predictive claims, or evidence of profitability.
Explore the guided lab
Open the guided playground, then:
- inspect the nonzero observation-39 state;
- step through the stress/recovery regime;
- select the expanded-source comparison;
- watch the scale diagnostic explain what changed and what stayed invariant;
- select invalid evidence and step to observation 45;
- reset and confirm the same deterministic state.
Summary
McClellan Volume Summation Index is not defined only by fast minus slow. It is defined by its input unit, its two initialization states, its optional cumulative seed, and its evidence boundary. Doubling allocated volume doubles every oscillator increment and therefore doubles the zero-seeded cumulative path.
Next: New Highs–New Lows.
Primary references
See the annotated source record and the canonical technical contract.
Rendered from the canonical Mermaid sources linked by this article.
McClellan Volume Summation Index calculation flow
Purpose: show that evidence, input units, recursive memory, and optional cumulative state are separate decisions.
Takeaway: Doubling allocated volume doubles every oscillator increment and therefore doubles the zero-seeded cumulative path.
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.
Only the formulas and historical naming are sourced facts. The fixture is synthetic; its arithmetic, seed choice, validation schema, error states, and software interfaces are explicit author choices.
MCO-01 — Calculating the McClellan Oscillator
- Organization or authors: McClellan Financial Publications; Tom McClellan
- Source type: Maintained first-party methodology note
- Publication or effective date: Not stated unless shown on the source page
- Version: Web or PDF edition accessed 2026-07-25
- URL: Calculating the McClellan Oscillator
- Accessed: 2026-07-25
- Jurisdiction: United States market-breadth methodology
- Supports: 10% and 5% EMA recurrences, raw A-D input, oscillator subtraction, and the 19/39 label equivalence.
- Limitations: It does not prescribe this package's SMA seed, causal cutoff, revision schema, or software error states.
MCO-03 — The McClellan Oscillator and Summation Index
- Organization or authors: McClellan Financial Publications
- Source type: Maintained first-party overview
- Publication or effective date: Not stated unless shown on the source page
- Version: Web or PDF edition accessed 2026-07-25
- URL: The McClellan Oscillator and Summation Index
- Accessed: 2026-07-25
- Jurisdiction: United States market-breadth methodology
- Supports: The Summation Index as cumulative oscillator values and the historical +1000 convention for the classic issue-count series.
- Limitations: A published absolute level depends on calibration and source history; a fresh local seed does not reproduce a vendor series.
MCO-04 — A Subtle Message in the Volume Summation Index
- Organization or authors: McClellan Financial Publications; Tom McClellan
- Source type: First-party calculation explanation
- Publication or effective date: Not stated unless shown on the source page
- Version: Web or PDF edition accessed 2026-07-25
- URL: A Subtle Message in the Volume Summation Index
- Accessed: 2026-07-25
- Jurisdiction: United States market-breadth methodology
- Supports: Up Volume minus Down Volume as input, the 10% Trend minus 5% Trend Volume Oscillator, and cumulative Volume Summation Index.
- Limitations: The article's market interpretation is not used as evidence of predictive performance.
MCO-05 — Understanding Oscillators and Other Indicators
- Organization or authors: McClellan Financial Publications
- Source type: First-party technical PDF
- Publication or effective date: Not stated unless shown on the source page
- Version: Web or PDF edition accessed 2026-07-25
- URL: Understanding Oscillators and Other Indicators
- Accessed: 2026-07-25
- Jurisdiction: United States market-breadth methodology
- Supports: The general Summation Index recurrence and zero starting constant for the Volume Summation Index.
- Limitations: Historical publication data conventions do not define a universal modern volume feed or adjustment basis.
NASD-01 — A-D definition
- Organization or authors: Nasdaq, Inc.
- Source type: Official exchange glossary
- Publication or effective date: Not stated unless shown on the source page
- Version: Web or PDF edition accessed 2026-07-25
- URL: A-D definition
- Accessed: 2026-07-25
- Jurisdiction: United States market-breadth methodology
- Supports: Advancing issues minus declining issues as a signed market-breadth quantity.
- Limitations: It does not define this package's universe, volume allocation, revisions, or EMA initialization.
Evidence reconciliation
- Sourced fact: First-party methodology describes the Volume Summation Index as the ongoing running total of Volume Oscillator readings and identifies zero as the traditional volume-series starting constant.
- Implementation choice: SMA seeds at observations 19 and 39, display-only rounding, atomic withholding for unresolved evidence, and the documented accumulator seed.
- Synthetic teaching input: the 60-session fixture under
datasets/. - Author-derived calculation: every transformed input, EMA, oscillator, and cumulative value in the worked example.
- Historical-example decision: deferred. A named market series would require a reproducible universe, final breadth or volume allocation, revision history, calibration history, access timestamp, and redistribution permission.
- Empirical claim intentionally excluded: passing calculation tests does not establish forecasting or trading value.
Full dependency-light reference implementations in both supported languages.
/** Reference implementation for McClellan Volume Summation Index. */
export type RecordRow = Record<string, unknown>;
export const MODE = "raw_volume_sum";
export const INITIAL_INDEX: number | null = 0.0;
const FAST_ALPHA = 0.10;
const SLOW_ALPHA = 0.05;
const IDENTITY_FIELDS = ["venue_id", "universe_id", "methodology_id", "calendar_id", "volume_unit", "volume_adjustment_basis"] as const;
function numberValue(value: unknown, name: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${name} must be a finite number`);
return value;
}
function timestamp(value: unknown, name: string): number {
if (typeof value !== "string" || !value.endsWith("Z")) throw new Error(`${name} must be an RFC 3339 UTC timestamp ending in Z`);
const parsed = Date.parse(value);
if (!Number.isFinite(parsed)) throw new Error(`${name} is invalid`);
return parsed;
}
export function transformRecord(record: RecordRow): number {
if (MODE.startsWith("ratio_issue")) {
const advances = numberValue(record.advances, "advances");
const declines = numberValue(record.declines, "declines");
if (advances < 0 || declines < 0) throw new Error("issue counts must be non-negative");
const denominator = advances + declines;
if (denominator === 0) throw new Error("ratio-adjusted breadth is undefined when advances + declines is zero");
return 1000 * (advances - declines) / denominator;
}
if (MODE.startsWith("raw_issue")) {
const advances = numberValue(record.advances, "advances");
const declines = numberValue(record.declines, "declines");
if (advances < 0 || declines < 0) throw new Error("issue counts must be non-negative");
return advances - declines;
}
const up = numberValue(record.up_volume, "up_volume");
const down = numberValue(record.down_volume, "down_volume");
const unchanged = numberValue(record.unchanged_volume, "unchanged_volume");
const total = numberValue(record.total_volume, "total_volume");
if (Math.min(up, down, unchanged, total) < 0) throw new Error("volume fields must be non-negative");
if (total <= 0 || up + down + unchanged !== total) throw new Error("volume partition must be positive and reconcile exactly");
return up - down;
}
export function calculateValues(values: readonly number[]) {
const data = values.map((value) => numberValue(value, "input value"));
let fast: number | null = null;
let slow: number | null = null;
let indexState = INITIAL_INDEX;
return data.map((value, offset) => {
const position = offset + 1;
if (position === 19) fast = data.slice(0, 19).reduce((a, b) => a + b, 0) / 19;
else if (position > 19) fast = fast! + FAST_ALPHA * (value - fast!);
if (position === 39) slow = data.slice(0, 39).reduce((a, b) => a + b, 0) / 39;
else if (position > 39) slow = slow! + SLOW_ALPHA * (value - slow!);
const oscillator = slow === null ? null : fast! - slow;
let indexValue: number | null = null;
if (oscillator !== null && INITIAL_INDEX !== null) {
indexState = indexState! + oscillator;
indexValue = indexState;
}
return {
observation: position, input_value: value, fast_ema: fast, slow_ema: slow,
oscillator, index_value: indexValue,
status: position < 19 ? "warming_fast" : position < 39 ? "warming_slow" : "ready",
};
});
}
export function calculate(records: readonly RecordRow[], cutoff: string) {
const cutoffTime = timestamp(cutoff, "cutoff");
const retained: RecordRow[] = [];
let ignoredFuture = 0;
for (const record of records) {
if (timestamp(record.available_at, "available_at") > cutoffTime) ignoredFuture += 1;
else retained.push(record);
}
if (!retained.length) return {status: "incomplete", reason_codes: ["no_evidence_at_cutoff"], points: [], ignored_future_records: ignoredFuture};
const groups = new Map<number, RecordRow[]>();
for (const record of retained) {
const sequence = record.session_sequence;
const revision = record.revision;
if (!Number.isInteger(sequence) || (sequence as number) <= 0) throw new Error("session_sequence must be a positive integer");
if (!Number.isInteger(revision) || (revision as number) < 0) throw new Error("revision must be a non-negative integer");
const bucket = groups.get(sequence as number) ?? [];
bucket.push(record);
groups.set(sequence as number, bucket);
}
const sequences = [...groups.keys()].sort((a, b) => a - b);
for (let i = 1; i < sequences.length; i += 1) {
if (sequences[i] !== sequences[i - 1] + 1) return {status: "incomplete", reason_codes: ["missing_session_sequence"], points: [], ignored_future_records: ignoredFuture};
}
const resolved: RecordRow[] = [];
let identity: string | null = null;
for (const sequence of sequences) {
const revisions = groups.get(sequence)!;
const numbers = revisions.map((row) => row.revision as number);
if (new Set(numbers).size !== numbers.length) return {status: "ambiguous", reason_codes: ["duplicate_revision"], points: [], ignored_future_records: ignoredFuture};
const sorted = [...numbers].sort((a, b) => a - b);
if (sorted.some((value, index) => value !== index)) return {status: "ambiguous", reason_codes: ["broken_revision_chain"], points: [], ignored_future_records: ignoredFuture};
const head = revisions.reduce((a, b) => (a.revision as number) > (b.revision as number) ? a : b);
if (head.event_type === "cancel") return {status: "incomplete", reason_codes: ["cancelled_session"], points: [], ignored_future_records: ignoredFuture};
if (head.source_evidence_state !== "ready") return {status: "incomplete", reason_codes: ["source_not_ready"], points: [], ignored_future_records: ignoredFuture};
const currentIdentity = IDENTITY_FIELDS.map((field) => head[field]);
if (currentIdentity.some((value) => typeof value !== "string" || !value)) throw new Error("series identity fields must be non-empty strings");
const encoded = JSON.stringify(currentIdentity);
if (identity === null) identity = encoded;
else if (identity !== encoded) return {status: "unsupported", reason_codes: ["series_identity_break"], points: [], ignored_future_records: ignoredFuture};
transformRecord(head);
resolved.push(head);
}
const points = calculateValues(resolved.map(transformRecord)).map((point, index) => ({...point, session_date: resolved[index].session_date}));
return {
status: "resolved", reason_codes: [], points,
latest_oscillator: points.at(-1)?.oscillator ?? null,
latest_index_value: points.at(-1)?.index_value ?? null,
ignored_future_records: ignoredFuture, seed_policy: "sma_19_39", initial_index: INITIAL_INDEX,
};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.