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:
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user