fintech-algorithms
Using a coding agent? Give it the skill: npx skills add IslamBaraka90/Fintech-Algorithms-Library What it does →

McClellan Volume Oscillator

Install and import#

bash
npm install fintech-algorithms
ts
import { 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#

NameTypeNotes
recordsBreadthRecord[]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.
cutoffstringPoint-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_volume is 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#

records
[
  {
    "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
  }
]
cutoff
"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#

McClellan Volume Oscillator — family transform
McClellan Volume Oscillator — worked state

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.

Read the article →

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

The rest of the McClellan Family family#