McClellan Volume Oscillator
Install and import#
npm install fintech-algorithmsimport { calculate } from "fintech-algorithms/market-breadth-and-internals/mcclellan-family/mcclellan-volume-oscillator";Signature#
calculate(records, cutoff)The McClellan construction applied to advancing minus declining *volume* rather than issue counts. Weighting participation by share volume gives a series that can move differently from the issue-count oscillator, but this package neither measures nor interprets that difference: divergence signals are excluded. Magnitude is only comparable within one declared volume unit and adjustment basis, because doubling every allocated volume doubles the input, both EMA states, and the oscillator.
Parameters#
| Name | Type | Notes |
|---|---|---|
records | BreadthRecord[] | Session records carrying session_sequence, revision, available_at, event_type, source_evidence_state, the six series identity fields (venue_id, universe_id, methodology_id, calendar_id, volume_unit, volume_adjustment_basis), and an exactly reconciling volume partition: up_volume, down_volume, unchanged_volume and a positive total_volume equal to their sum. |
cutoff | string | Point-in-time bound. Revisions arriving after the cutoff are ignored rather than applied, so the series is exactly what was computable at that moment. Breadth data is revised routinely, and a cumulative line silently rebuilt from revised inputs is not the line anyone traded. |
Returns#
{ status, reason_codes, points, latest_oscillator, latest_index_value, ignored_future_records, seed_policy, initial_index }
status is resolved, incomplete, ambiguous, or unsupported; only a resolved result carries points. Each point holds its session date, the up-minus-down volume input, both EMA states, the oscillator, and a warm-up status of warming_fast, warming_slow or ready — the first oscillator appears at observation 39. This topic is the oscillator only: it declares no cumulative seed, so index_value, latest_index_value and initial_index are always null. The Volume Summation Index is a separate construction.
Warm-up#
The first 39 observations positions are warm-up status: warming_fast, then warming_slow. Every point carries a warm-up status. The 19-period EMA resolves first and the 39-period EMA second, so the first oscillator value appears at observation 39. This topic declares no cumulative seed, so the index fields stay null throughout.
Errors#
- When a volume field is absent, non-finite or negative,
total_volumeis not positive, or the up/down/unchanged partition does not sum exactly to it — throws ValueError - When a record is otherwise malformed: a timestamp not ending in Z, a non-positive session_sequence, a negative revision, or an empty series-identity field — throws ValueError
- When evidence is missing or inconsistent: no record at the cutoff, a gap in the session sequence, a duplicate or non-contiguous revision, a cancelled session, a source not yet ready, or a break in series identity — returned as a non-resolved status with reason_codes; not thrown
Complexity: time O(n log n) for n records: sessions and per-session revisions are sorted before the head revision is taken, then the EMA recursion is a single linear pass,
space O(n).
Worked example#
verified This is the worked example published in the article, replayed by the test suite on every run. The output cannot drift.
Input#
[
{
"session_date": "2026-01-02",
"session_sequence": 1,
"effective_at": "2026-01-02T21:00:00Z",
"available_at": "2026-01-02T21:20:00Z",
"revision": 0,
"event_type": "upsert",
"source_evidence_state": "ready",
"venue_id": "SYNTH-X",
"universe_id": "SYNTH-STABLE-200",
"methodology_id": "close-vs-comparable-prior-close-v1",
"calendar_id": "SYNTH-WEEKDAY",
"volume_unit": "shares",
"volume_adjustment_basis": "reported-unadjusted",
"advances": 100
}
]"2026-04-01T00:00:00Z"Call#
calculate(records, cutoff)Returns#
object with 4 fields: status, latest_oscillator, latest_index_value, ignored_future_records
{
"status": "resolved",
"latest_oscillator": null,
"latest_index_value": null,
"ignored_future_records": 0
}Other exports#
This module also exports
transformRecord, calculateValues. Every module additionally exports run as an alias of its
primary function, and a meta object carrying its catalog id, domain, family,
shape and article URL.
Diagrams#
Calculation flow#
McClellan Volume Oscillator calculation flow
flowchart TD
A["Records available at knowledge cutoff"] --> B{"Contiguous, unique, ready evidence?"}
B -->|No| C["Withhold path and report reason"]
B -->|Yes| D["Apply McClellan Volume Oscillator input transform"]
D --> E["SMA-19 fast seed and 10% Trend"]
D --> F["SMA-39 slow seed and 5% Trend"]
E --> G{"Both states ready?"}
F --> G
G -->|No| H["Warm-up diagnostics"]
G -->|Yes| I["Fast minus slow oscillator"]
I --> J["Publish oscillator diagnostics"]
J --> K["Provenance and current regime"]
How it works#
This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.
References#
- Calculating the McClellan Oscillator — McClellan Financial Publications; Tom McClellan
- A Subtle Message in the Volume Summation Index — McClellan Financial Publications; Tom McClellan
- Understanding Oscillators and Other Indicators — McClellan Financial Publications
- A-D definition — Nasdaq, Inc.
- Evidence reconciliation