feat: Family 07 Volume - 6 new volume-flow indicators (#45)

* feat(kvo): add Klinger Volume Oscillator

Stephen J. Klinger's trend-aware volume-force MACD. Each bar produces a 'volume force' (vf) signed by the local trend (+1 / -1 / carry) and scaled by the ratio of the current accumulation horizon to its previous trend. KVO = EMA(vf, fast) - EMA(vf, slow), classic (34, 55).

Rust core (Kvo) with 7 unit tests (rejects zero / fast>=slow, accessors, constant series collapses to 0, warmup lands at slow+1, batch == streaming, reset clears state), plus Python (PyKvo + KVO export), Node (KvoNode), and WASM (WasmKvo) bindings. Fuzz target adds Kvo to the candle-input sweep, bench adds the candle-input KVO benchmark, README counter 71 -> 72 + family table row, CHANGELOG [Unreleased].

* feat(volume-oscillator): add Volume Oscillator (VO)

Percent difference between a fast and a slow SMA of the bar volume: 100 * (SMA(vol, fast) - SMA(vol, slow)) / SMA(vol, slow). Default (14, 28). The line stays near zero in stable conditions; positive readings show rising short-term participation, negative readings show waning interest.

Rust core (VolumeOscillator) with 8 unit tests (period validation, accessors, constant volume == 0, zero-volume window defensive branch, two reference values verified algebraically, batch == streaming, reset), plus Python (PyVolumeOscillator + VolumeOscillator export), Node (VolumeOscillatorNode), and WASM (WasmVolumeOscillator) bindings. Fuzz target adds VolumeOscillator to the candle-input sweep, bench adds the volume_oscillator benchmark, README counter 72 -> 73 + family table row, CHANGELOG [Unreleased].

* feat(nvi-pvi): add Negative & Positive Volume Index

Paul Dysart's cumulative volume-flow indices, popularised by Norman Fosback in 'Stock Market Logic'. Both run from a 1000.0 baseline and only update on a specific direction of volume change:

- NVI updates on volume-contraction bars (volume_t < volume_{t-1}), absorbing the percent close change. Tracks the 'smart money' leg per Fosback.
- PVI updates on volume-expansion bars (volume_t > volume_{t-1}). Tracks the 'crowd' leg.

Both expose with_baseline(f64) for custom starting indexes. The NVI/PVI pair is listed as a single line in indicator-ideas/families/07-volume.md and shares the same lifecycle/test/binding surface, so they ship as one commit.

Rust core (Nvi, Pvi) with 9 unit tests each (accessors, baseline seed, volume direction branches, zero-prev-close guard, custom baseline, batch == streaming, reset), plus Python (PyNvi/PyPvi + NVI/PVI exports), Node (NviNode/PviNode), and WASM (WasmNvi/WasmPvi) bindings. Fuzz target adds Nvi+Pvi to the candle-input sweep, bench adds nvi+pvi entries, README counter 73 -> 75 + family table row, CHANGELOG [Unreleased].

* feat(family-07): add Williams A/D, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index

Finishes the volume-flow family with the remaining (new) entries from
indicator-ideas/families/07-volume.md.

Indicators added:

- Williams A/D (`WilliamsAD`): Larry Williams' volume-less cumulative
  accumulation/distribution line. Anchors each bar's contribution to
  the previous close via true-high/true-low (gap-aware).
- Anchored VWAP (`AnchoredVwap`): cumulative VWAP whose accumulation
  starts at a user-chosen anchor bar. Exposes `set_anchor()` (queued
  to the next `update`) for click-to-anchor workflows. Reset clears
  both state and pending-anchor flag.
- Demand Index (`DemandIndex`): James Sibbet's smoothed buying-vs-
  selling pressure, in the streaming-friendly textbook form
  `EMA(volume * close-return * (1 + range/close), period)`.
- Time Segmented Volume (`Tsv`): Don Worden's rolling window-sum of
  `(close_t - close_{t-1}) * volume_t`. Default `period = 18`.
- Volume Zone Oscillator (`Vzo`): Walid Khalil's normalised volume-flow
  oscillator bounded in `[-100, +100]`, defined as
  `100 * EMA(signed_volume) / EMA(volume)`.
- Market Facilitation Index (`MarketFacilitationIndex`): Bill Williams'
  per-bar `(high - low) / volume`. Returns `None` on zero-volume bars.

All six indicators ship with unit tests (`rejects_zero_period` where
applicable, `accessors_and_metadata`, constant-series behaviour,
batch == streaming equivalence, reset semantics, and reference-value
or saturation-extreme tests), Python / Node / WASM bindings, fuzz
coverage in `indicator_update_candle`, a `bench_candle_input` line per
indicator, README + CHANGELOG entries, and Python reference-value
tests in `test_new_indicators.py`.

The README indicator counter advances 75 -> 81.

* test(family-07): cover defensive cold paths + Default impls

- ad_oscillator: exercise `value()` after first emission.
- kvo: cover the `cm == 0.0` zero-OHLC defensive branch.
- nvi / pvi: exercise the Default impls.
This commit is contained in:
kingchenc
2026-05-25 19:15:22 +02:00
committed by GitHub
parent 6287bd48c1
commit 880a0e7430
23 changed files with 4155 additions and 38 deletions
+20
View File
@@ -6,11 +6,13 @@
mod acceleration_bands;
mod accelerator_oscillator;
mod ad_oscillator;
mod adl;
mod adx;
mod adxr;
mod alligator;
mod alma;
mod anchored_vwap;
mod apo;
mod aroon;
mod aroon_oscillator;
@@ -34,6 +36,7 @@ mod cmo;
mod connors_rsi;
mod coppock;
mod dema;
mod demand_index;
mod donchian;
mod double_bollinger;
mod dpo;
@@ -53,6 +56,7 @@ mod jma;
mod kama;
mod keltner;
mod kst;
mod kvo;
mod laguerre_rsi;
mod linreg;
mod linreg_angle;
@@ -60,12 +64,14 @@ mod linreg_channel;
mod linreg_slope;
mod ma_envelope;
mod macd;
mod market_facilitation_index;
mod mass_index;
mod mcginley_dynamic;
mod median_price;
mod mfi;
mod mom;
mod natr;
mod nvi;
mod obv;
mod parkinson;
mod percent_b;
@@ -73,6 +79,7 @@ mod pgo;
mod pmo;
mod ppo;
mod psar;
mod pvi;
mod roc;
mod rogers_satchell;
mod rsi;
@@ -96,17 +103,20 @@ mod trima;
mod trix;
mod true_range;
mod tsi;
mod tsv;
mod ttm_squeeze;
mod typical_price;
mod ulcer_index;
mod ultimate_oscillator;
mod vertical_horizontal_filter;
mod vidya;
mod volume_oscillator;
mod vortex;
mod vpt;
mod vwap;
mod vwap_stddev_bands;
mod vwma;
mod vzo;
mod wave_trend;
mod weighted_close;
mod williams_r;
@@ -118,11 +128,13 @@ mod zlema;
pub use acceleration_bands::{AccelerationBands, AccelerationBandsOutput};
pub use accelerator_oscillator::AcceleratorOscillator;
pub use ad_oscillator::AdOscillator;
pub use adl::Adl;
pub use adx::{Adx, AdxOutput};
pub use adxr::Adxr;
pub use alligator::{Alligator, AlligatorOutput};
pub use alma::Alma;
pub use anchored_vwap::AnchoredVwap;
pub use apo::Apo;
pub use aroon::{Aroon, AroonOutput};
pub use aroon_oscillator::AroonOscillator;
@@ -146,6 +158,7 @@ pub use cmo::Cmo;
pub use connors_rsi::ConnorsRsi;
pub use coppock::Coppock;
pub use dema::Dema;
pub use demand_index::DemandIndex;
pub use donchian::{Donchian, DonchianOutput};
pub use double_bollinger::{DoubleBollinger, DoubleBollingerOutput};
pub use dpo::Dpo;
@@ -165,6 +178,7 @@ pub use jma::Jma;
pub use kama::Kama;
pub use keltner::{Keltner, KeltnerOutput};
pub use kst::{Kst, KstOutput};
pub use kvo::Kvo;
pub use laguerre_rsi::LaguerreRsi;
pub use linreg::LinearRegression;
pub use linreg_angle::LinRegAngle;
@@ -172,12 +186,14 @@ pub use linreg_channel::{LinRegChannel, LinRegChannelOutput};
pub use linreg_slope::LinRegSlope;
pub use ma_envelope::{MaEnvelope, MaEnvelopeOutput};
pub use macd::{MacdIndicator, MacdOutput};
pub use market_facilitation_index::MarketFacilitationIndex;
pub use mass_index::MassIndex;
pub use mcginley_dynamic::McGinleyDynamic;
pub use median_price::MedianPrice;
pub use mfi::Mfi;
pub use mom::Mom;
pub use natr::Natr;
pub use nvi::Nvi;
pub use obv::Obv;
pub use parkinson::ParkinsonVolatility;
pub use percent_b::PercentB;
@@ -185,6 +201,7 @@ pub use pgo::Pgo;
pub use pmo::Pmo;
pub use ppo::Ppo;
pub use psar::Psar;
pub use pvi::Pvi;
pub use roc::Roc;
pub use rogers_satchell::RogersSatchellVolatility;
pub use rsi::Rsi;
@@ -208,17 +225,20 @@ pub use trima::Trima;
pub use trix::Trix;
pub use true_range::TrueRange;
pub use tsi::Tsi;
pub use tsv::Tsv;
pub use ttm_squeeze::{TtmSqueeze, TtmSqueezeOutput};
pub use typical_price::TypicalPrice;
pub use ulcer_index::UlcerIndex;
pub use ultimate_oscillator::UltimateOscillator;
pub use vertical_horizontal_filter::VerticalHorizontalFilter;
pub use vidya::Vidya;
pub use volume_oscillator::VolumeOscillator;
pub use vortex::{Vortex, VortexOutput};
pub use vpt::VolumePriceTrend;
pub use vwap::{RollingVwap, Vwap};
pub use vwap_stddev_bands::{VwapStdDevBands, VwapStdDevBandsOutput};
pub use vwma::Vwma;
pub use vzo::Vzo;
pub use wave_trend::{WaveTrend, WaveTrendOutput};
pub use weighted_close::WeightedClose;
pub use williams_r::WilliamsR;