feat: Family 05 Bands & Channels - 11 new price-envelope indicators (#43)

* feat(bands-channels): add Family 05 with 11 indicators

Eleven price-envelope overlays organised into a new "Bands & Channels"
family, exposed across all four bindings (Rust core, Python, Node, WASM)
plus fuzz/test/bench/docs coverage:

- MaEnvelope - SMA centerline with fixed-percent envelope (the oldest
  band overlay still in regular use).
- AccelerationBands (Price Headley) - momentum-biased bands that widen
  with the bar's relative range (H - L) / (H + L).
- StarcBands (Stoller Average Range Channel) - SMA(close) +/- k*ATR;
  Keltner's SMA-centerline sibling.
- AtrBands - close-anchored envelope of width k*ATR; the standard
  volatility-targeting stop/target band.
- HurstChannel - SMA centerline wrapped by the rolling high-low range
  (Brian Millard / Hurst-cycle channel).
- LinRegChannel - rolling OLS endpoint +/- k * population stddev of the
  residuals; dispersion about the trend rather than the mean.
- StandardErrorBands - regression line +/- k * OLS standard error
  (denominator n - 2) for prediction-interval bands.
- DoubleBollinger (Kathy Lien) - two concentric BB envelopes
  (typically +/- 1 sigma and +/- 2 sigma) for the zone-partition setup.
- TtmSqueeze (John Carter) - BB-inside-KC squeeze flag paired with a
  detrended-close linear-regression momentum reading.
- FractalChaosBands - Bill Williams 5-bar fractal high/low envelope.
- VwapStdDevBands - cumulative VWAP with volume-weighted population
  standard deviation bands.

Each indicator ships:
- Core impl with the full Indicator trait, classic() where applicable,
  and unit tests (rejects_zero_period / multiplier, accessors, flat
  market, monotonic ordering, batch == streaming, reset, plus
  algebraically verifiable reference values).
- Python PyO3 binding with multi-column NumPy batch (PyArray2).
- Node napi binding with #[napi(object)] struct + interleaved flat
  batch.
- WASM wasm-bindgen binding via Object/Reflect for update +
  Float64Array for batch.
- Fuzz coverage in fuzz_targets/indicator_update{,_candle}.rs.
- Python streaming-vs-batch parametric test + reference test.
- Node streaming-vs-interleaved-batch test + reference test.
- Criterion microbench under crates/wickra/benches/indicators.rs.

README family table, README indicator-count line, and CHANGELOG
Unreleased entry updated: indicator total rises from 71 to 82 across
nine families. Wiki pages are updated in a separate commit in the
wickra.wiki repo.

* test(acceleration-bands): cover sum_hl==0 zero-price guard

Exercises line 104 (`0.0` branch of the `sum_hl == 0.0` guard) which
was the last patch-coverage miss on the family-05 PR. `Candle::new`
accepts a fully-zero bar so the branch is reachable in principle —
add a degenerate-candle unit test to hit it.
This commit is contained in:
kingchenc
2026-05-25 18:37:12 +02:00
committed by GitHub
parent 3ea0f12b7a
commit 54194a4ff8
25 changed files with 5482 additions and 33 deletions
+36 -5
View File
@@ -15,11 +15,12 @@
use libfuzzer_sys::fuzz_target;
use wickra_core::{
Alma, Apo, BatchExt, BollingerBands, Cfo, Cmo, ConnorsRsi, Coppock, Dema, Dpo, ElderImpulse,
Ema, Frama, HistoricalVolatility, Hma, Indicator, Jma, Kama, Kst, LaguerreRsi, LinRegAngle,
LinRegSlope, LinearRegression, MacdIndicator, McGinleyDynamic, Mom, Pmo, Ppo, Roc, Rsi,
RviVolatility, Sma, Smma, Stc, StdDev, StochRsi, T3, Tema, Trima, Trix, Tsi, UlcerIndex,
VerticalHorizontalFilter, Vidya, Wma, ZScore, ZeroLagMacd, Zlema,
Alma, Apo, BatchExt, BollingerBands, Cfo, Cmo, ConnorsRsi, Coppock, Dema, DoubleBollinger, Dpo,
ElderImpulse, Ema, Frama, HistoricalVolatility, Hma, Indicator, Jma, Kama, Kst, LaguerreRsi,
LinRegAngle, LinRegChannel, LinRegSlope, LinearRegression, MaEnvelope, MacdIndicator,
McGinleyDynamic, Mom, Pmo, Ppo, Roc, Rsi, RviVolatility, Sma, Smma, StandardErrorBands, Stc,
StdDev, StochRsi, T3, Tema, Trima, Trix, Tsi, UlcerIndex, VerticalHorizontalFilter, Vidya, Wma,
ZScore, ZeroLagMacd, Zlema,
};
/// Drive a single streaming + batch run through one scalar indicator. Marked
@@ -120,4 +121,34 @@ fuzz_target!(|data: Vec<f64>| {
}
let _ = BollingerBands::new(20, 2.0).unwrap().batch(&data);
}
// --- Family 05: scalar-input band/channel indicators (multi-output) ---
{
let mut env = MaEnvelope::new(20, 0.025).unwrap();
for &x in &data {
let _ = env.update(x);
}
let _ = MaEnvelope::new(20, 0.025).unwrap().batch(&data);
}
{
let mut ch = LinRegChannel::new(20, 2.0).unwrap();
for &x in &data {
let _ = ch.update(x);
}
let _ = LinRegChannel::new(20, 2.0).unwrap().batch(&data);
}
{
let mut seb = StandardErrorBands::new(21, 2.0).unwrap();
for &x in &data {
let _ = seb.update(x);
}
let _ = StandardErrorBands::new(21, 2.0).unwrap().batch(&data);
}
{
let mut db = DoubleBollinger::new(20, 1.0, 2.0).unwrap();
for &x in &data {
let _ = db.update(x);
}
let _ = DoubleBollinger::new(20, 1.0, 2.0).unwrap().batch(&data);
}
});
+60 -8
View File
@@ -23,14 +23,15 @@
use libfuzzer_sys::fuzz_target;
use wickra_core::{
AcceleratorOscillator, Adl, Adx, Alligator, Aroon, AroonOscillator, Atr, AtrTrailingStop,
AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, BatchExt, Candle, Cci,
ChaikinMoneyFlow, ChaikinOscillator,
ChaikinVolatility, ChandeKrollStop, ChandelierExit, ChoppinessIndex, Donchian, EaseOfMovement,
Evwma, ForceIndex, GarmanKlassVolatility, Indicator, Inertia, Keltner, MassIndex, MedianPrice,
Mfi, Natr, Obv, ParkinsonVolatility, Pgo, Psar, RogersSatchellVolatility, RollingVwap, Rvi, Smi,
Stochastic, SuperTrend, TrueRange, TypicalPrice, UltimateOscillator, VolumePriceTrend, Vortex,
Vwap, Vwma, WeightedClose, WilliamsR, YangZhangVolatility,
AccelerationBands, AcceleratorOscillator, Adl, Adx, Alligator, Aroon, AroonOscillator, Atr,
AtrBands, AtrTrailingStop, AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower,
BatchExt, Candle, Cci, ChaikinMoneyFlow, ChaikinOscillator, ChaikinVolatility, ChandeKrollStop,
ChandelierExit, ChoppinessIndex, Donchian, EaseOfMovement, Evwma, ForceIndex, FractalChaosBands,
GarmanKlassVolatility, HurstChannel, Indicator, Inertia, Keltner, MassIndex, MedianPrice, Mfi,
Natr, Obv, ParkinsonVolatility, Pgo, Psar, RogersSatchellVolatility, RollingVwap, Rvi, Smi,
StarcBands, Stochastic, SuperTrend, TrueRange, TtmSqueeze, TypicalPrice, UltimateOscillator,
VolumePriceTrend, Vortex, Vwap, VwapStdDevBands, Vwma, WeightedClose, WilliamsR,
YangZhangVolatility,
};
/// Convert a flat `f64` stream into a `Vec<Candle>` by chunking it into
@@ -143,4 +144,55 @@ fuzz_target!(|data: Vec<f64>| {
}
let _ = Stochastic::new(14, 3).unwrap().batch(&candles);
}
// --- Family 05: candle-input band/channel indicators (multi-output) ---
{
let mut ab = AccelerationBands::new(20, 0.001).unwrap();
for c in &candles {
let _ = ab.update(*c);
}
let _ = AccelerationBands::new(20, 0.001).unwrap().batch(&candles);
}
{
let mut sb = StarcBands::new(6, 15, 2.0).unwrap();
for c in &candles {
let _ = sb.update(*c);
}
let _ = StarcBands::new(6, 15, 2.0).unwrap().batch(&candles);
}
{
let mut atrb = AtrBands::new(14, 3.0).unwrap();
for c in &candles {
let _ = atrb.update(*c);
}
let _ = AtrBands::new(14, 3.0).unwrap().batch(&candles);
}
{
let mut hc = HurstChannel::new(10, 0.5).unwrap();
for c in &candles {
let _ = hc.update(*c);
}
let _ = HurstChannel::new(10, 0.5).unwrap().batch(&candles);
}
{
let mut ts = TtmSqueeze::new(20, 2.0, 1.5).unwrap();
for c in &candles {
let _ = ts.update(*c);
}
let _ = TtmSqueeze::new(20, 2.0, 1.5).unwrap().batch(&candles);
}
{
let mut fc = FractalChaosBands::new(2).unwrap();
for c in &candles {
let _ = fc.update(*c);
}
let _ = FractalChaosBands::new(2).unwrap().batch(&candles);
}
{
let mut vb = VwapStdDevBands::new(2.0).unwrap();
for c in &candles {
let _ = vb.update(*c);
}
let _ = VwapStdDevBands::new(2.0).unwrap().batch(&candles);
}
});