* feat(rvi): add Relative Volatility Index
Donald Dorsey's RSI-shaped volatility gauge. Partitions the rolling
population standard deviation of close into "up" samples (close rose
since the previous bar) and "down" samples (close fell), Wilder-smooths
each side, and reports 100 * AvgUp / (AvgUp + AvgDown). Output bounded
on [0, 100]; saturates at 100 in pure uptrends, 0 in pure downtrends,
and falls back to 50 on a completely flat series (same undefined-RS
convention as RSI).
Single period parameter (default 10) drives both the stddev window and
the Wilder smoothing constant. First emit lands at index 2*period - 2
(2*period - 1 bars are needed: period to fill the stddev window plus
period - 1 to seed the Wilder averages, overlapping by one bar).
Touchpoints: rvi.rs + mod.rs + lib.rs re-export, PyRvi + __init__.py +
test_new_indicators SCALAR + test_known_values uptrend reference,
RviNode + index.d.ts/index.js + indicators.test.js factory +
reference, WasmRvi via scalar macro, scalar-fuzz target, bench_scalar
entry, README + CHANGELOG.
* feat(parkinson): add Parkinson Volatility
Michael Parkinson's (1980) high-low realised volatility estimator.
Under a driftless Geometric-Brownian-Motion assumption, the extreme
range of a bar carries roughly 5x the variance information of the
close-to-close estimator, so for a given statistical efficiency
Parkinson needs five times fewer samples.
Formula:
sigma^2 = (1 / (4n * ln 2)) * Sum_{i=1..n} (ln(H_i / L_i))^2
out = sqrt(sigma^2) * sqrt(trading_periods) * 100
The output is annualised to a percent in the same style as
HistoricalVolatility (pass `trading_periods = 1` for the raw per-bar
sigma * 100 figure). Two parameters: `period` (default 20) for the
rolling window, `trading_periods` (default 252) for the annualisation
factor. First emit at index `period - 1`.
Touchpoints: parkinson.rs + mod.rs + lib.rs re-export,
PyParkinsonVolatility + __init__.py + test_new_indicators CANDLE_SCALAR
+ test_known_values zero-range reference, ParkinsonVolatilityNode +
index.d.ts/index.js + indicators.test.js factory + reference,
WasmParkinsonVolatility hand-rolled, candle-fuzz target,
bench_candle_input entry, README + CHANGELOG.
* feat(garman-klass): add Garman-Klass Volatility
Garman & Klass (1980) OHLC realised-volatility estimator. Extends
Parkinson's high-low estimator with an open-to-close term, lifting
statistical efficiency from ~5x to ~7.4x relative to close-to-close
stddev under driftless Geometric Brownian Motion.
Formula (per bar):
s_t = 0.5 * (ln(H_t / L_t))^2 - (2*ln(2) - 1) * (ln(C_t / O_t))^2
out = sqrt(max(mean(s_t over `period`), 0)) * sqrt(trading_periods) * 100
The per-bar sample can be marginally negative when the bar has a small
range relative to its open-to-close move; a max(., 0) clamp on the
rolling mean absorbs that and the FP cancellation noise before the
square root.
Still biased on data with meaningful overnight drift -- use Yang-Zhang
when gaps matter. Defaults: `period = 20`, `trading_periods = 252`
(annualised percent, same convention as HistoricalVolatility).
Touchpoints: garman_klass.rs + mod.rs + lib.rs re-export,
PyGarmanKlassVolatility + __init__.py + test_new_indicators
CANDLE_SCALAR + test_known_values zero-movement reference,
GarmanKlassVolatilityNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmGarmanKlassVolatility hand-rolled,
candle-fuzz target, bench_candle_input entry, README + CHANGELOG.
* feat(rogers-satchell): add Rogers-Satchell Volatility
Rogers, Satchell & Yoon (1994) OHLC realised-volatility estimator.
Unlike Garman-Klass, the per-bar sample is exact under arbitrary
Brownian drift -- the drift component cancels algebraically.
Formula (per bar):
s_t = ln(H_t / C_t) * ln(H_t / O_t) + ln(L_t / C_t) * ln(L_t / O_t)
out = sqrt(max(mean(s_t over `period`), 0)) * sqrt(trading_periods) * 100
Each per-bar sample is also non-negative by construction: with
`Candle::new` guaranteeing H >= max(O, L, C) and L <= min(O, H, C), the
four log factors have predictable signs (ln(H/.) >= 0, ln(L/.) <= 0),
so both products contribute >= 0. The max(., 0) clamp on the rolling
mean is only there to absorb FP cancellation.
Defaults: `period = 20`, `trading_periods = 252` (annualised percent,
same convention as HistoricalVolatility / Parkinson / Garman-Klass).
Touchpoints: rogers_satchell.rs + mod.rs + lib.rs re-export,
PyRogersSatchellVolatility + __init__.py + test_new_indicators
CANDLE_SCALAR + test_known_values zero-movement reference,
RogersSatchellVolatilityNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmRogersSatchellVolatility hand-rolled,
candle-fuzz target, bench_candle_input entry, README + CHANGELOG.
* feat(yang-zhang): add Yang-Zhang Volatility
Yang & Zhang (2000) drift- and gap-robust OHLC realised-volatility
estimator. Combines three independent components into a single estimate
with minimum variance:
overnight = sample_var(ln(O_t / C_{t-1})) over n bars (close-to-open)
open_close = sample_var(ln(C_t / O_t)) over n bars
rs = mean(ln(H/C)*ln(H/O) + ln(L/C)*ln(L/O)) over n bars
sigma^2_YZ = overnight + k*open_close + (1-k)*rs
k = 0.34 / (1.34 + (n+1)/(n-1))
out = sqrt(max(sigma^2_YZ, 0)) * sqrt(trading_periods) * 100
The overnight and open-to-close variances use Bessel's correction (the
sample estimator, divisor n-1), same convention as
HistoricalVolatility. The blending factor `k` is the one that
minimises estimator variance under driftless Geometric Brownian Motion
with overnight gaps.
This is the gold-standard OHLC estimator for assets with both
close-to-open gaps and intraday drift: equities, futures, and any
market that does not trade continuously. For pure intraday data (where
O_t == C_{t-1} and the open-to-close return is constant), the
overnight and open-close terms vanish and the estimator collapses to
(1-k) * Rogers-Satchell -- this is the indicator's
intraday_data_collapses_to_rs_only unit test.
Period >= 2 (Bessel correction needs >= 2 samples). First emit at
index `period` (the (period+1)-th bar): one bar seeds prev_close, the
next `period` fill the rolling windows. Defaults: `period = 20`,
`trading_periods = 252`.
Touchpoints: yang_zhang.rs + mod.rs + lib.rs re-export,
PyYangZhangVolatility + __init__.py + test_new_indicators
CANDLE_SCALAR + test_known_values zero-movement reference,
YangZhangVolatilityNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmYangZhangVolatility hand-rolled, candle-fuzz
target, bench_candle_input entry, README + CHANGELOG.
* fix(rvi): rename to RviVolatility to avoid clash with family-02 RVI
Family 02 (PR #40) ships a separate `Rvi` struct for Relative Vigor
Index. The two indicators have nothing to do with each other beyond
sharing the acronym, so disambiguate by giving the volatility one a
longer name everywhere:
- Rust crate: `Rvi` -> `RviVolatility`
- Rust file: `rvi.rs` -> `rvi_volatility.rs`
- Python: `RVI` -> `RVIVolatility`
- Node: `RVI` -> `RVIVolatility`
- WASM: `RVI` -> `RVIVolatility`
Once the two PRs are both merged, callers get `wickra::Rvi` for Vigor
and `wickra::RviVolatility` for Volatility. The shorter `RVI` acronym
stays with the Momentum family per the existing wiki pages and the
implementation that shipped first.
Updates: rvi_volatility.rs (renamed), mod.rs, lib.rs re-export,
bindings/python/src/lib.rs + __init__.py + tests, bindings/node/src/lib.rs
+ index.d.ts + index.js + __tests__, bindings/wasm/src/lib.rs,
fuzz/fuzz_targets/indicator_update.rs, crates/wickra/benches/indicators.rs,
README family-table label, CHANGELOG entry.
* test(volatility): Rename test_rvi -> test_rvi_volatility + drop dead match arms
The Python test test_rvi_pure_uptrend_saturates_at_one_hundred was
calling ta.RVI() expecting the volatility version, but ta.RVI now
means Family 02's Relative Vigor Index (candle input). Renamed to
ta.RVIVolatility to match the binding rename done at merge time.
In all four OHLC volatility tests, the existing `match (r, a) { ...,
_ => panic!() }` arm is dead in passing runs (every aligned pair is
either (None, None) or (Some, Some)). Codecov flagged it as a patch
miss on each of parkinson / garman_klass / rogers_satchell /
yang_zhang. Refactored per CLAUDE.md cold-path guidance to
`assert_eq!(r.is_some(), a.is_some()); if let (Some, Some) ...`.
200 lines
4.7 KiB
Rust
200 lines
4.7 KiB
Rust
//! Built-in indicators. Every indicator implements [`crate::Indicator`].
|
|
//!
|
|
//! Modules are organised internally by category (trend, momentum, volatility,
|
|
//! volume) but every public name is also re-exported flat from this module and
|
|
//! from the crate root for convenience.
|
|
|
|
mod accelerator_oscillator;
|
|
mod adl;
|
|
mod adx;
|
|
mod alligator;
|
|
mod alma;
|
|
mod apo;
|
|
mod aroon;
|
|
mod aroon_oscillator;
|
|
mod atr;
|
|
mod atr_trailing_stop;
|
|
mod awesome_oscillator;
|
|
mod awesome_oscillator_histogram;
|
|
mod balance_of_power;
|
|
mod bollinger;
|
|
mod bollinger_bandwidth;
|
|
mod cci;
|
|
mod cfo;
|
|
mod chaikin_oscillator;
|
|
mod chaikin_volatility;
|
|
mod chande_kroll_stop;
|
|
mod chandelier_exit;
|
|
mod choppiness_index;
|
|
mod cmf;
|
|
mod cmo;
|
|
mod connors_rsi;
|
|
mod coppock;
|
|
mod dema;
|
|
mod donchian;
|
|
mod dpo;
|
|
mod ease_of_movement;
|
|
mod elder_impulse;
|
|
mod ema;
|
|
mod evwma;
|
|
mod force_index;
|
|
mod frama;
|
|
mod garman_klass;
|
|
mod historical_volatility;
|
|
mod hma;
|
|
mod inertia;
|
|
mod jma;
|
|
mod kama;
|
|
mod keltner;
|
|
mod kst;
|
|
mod laguerre_rsi;
|
|
mod linreg;
|
|
mod linreg_angle;
|
|
mod linreg_slope;
|
|
mod macd;
|
|
mod mass_index;
|
|
mod mcginley_dynamic;
|
|
mod median_price;
|
|
mod mfi;
|
|
mod mom;
|
|
mod natr;
|
|
mod obv;
|
|
mod parkinson;
|
|
mod percent_b;
|
|
mod pgo;
|
|
mod pmo;
|
|
mod ppo;
|
|
mod psar;
|
|
mod roc;
|
|
mod rogers_satchell;
|
|
mod rsi;
|
|
mod rvi;
|
|
mod rvi_volatility;
|
|
mod sma;
|
|
mod smi;
|
|
mod smma;
|
|
mod stc;
|
|
mod std_dev;
|
|
mod stoch_rsi;
|
|
mod stochastic;
|
|
mod super_trend;
|
|
mod t3;
|
|
mod tema;
|
|
mod trima;
|
|
mod trix;
|
|
mod true_range;
|
|
mod tsi;
|
|
mod typical_price;
|
|
mod ulcer_index;
|
|
mod ultimate_oscillator;
|
|
mod vertical_horizontal_filter;
|
|
mod vidya;
|
|
mod vortex;
|
|
mod vpt;
|
|
mod vwap;
|
|
mod vwma;
|
|
mod weighted_close;
|
|
mod williams_r;
|
|
mod wma;
|
|
mod yang_zhang;
|
|
mod z_score;
|
|
mod zero_lag_macd;
|
|
mod zlema;
|
|
|
|
pub use accelerator_oscillator::AcceleratorOscillator;
|
|
pub use adl::Adl;
|
|
pub use adx::{Adx, AdxOutput};
|
|
pub use alligator::{Alligator, AlligatorOutput};
|
|
pub use alma::Alma;
|
|
pub use apo::Apo;
|
|
pub use aroon::{Aroon, AroonOutput};
|
|
pub use aroon_oscillator::AroonOscillator;
|
|
pub use atr::Atr;
|
|
pub use atr_trailing_stop::AtrTrailingStop;
|
|
pub use awesome_oscillator::AwesomeOscillator;
|
|
pub use awesome_oscillator_histogram::AwesomeOscillatorHistogram;
|
|
pub use balance_of_power::BalanceOfPower;
|
|
pub use bollinger::{BollingerBands, BollingerOutput};
|
|
pub use bollinger_bandwidth::BollingerBandwidth;
|
|
pub use cci::Cci;
|
|
pub use cfo::Cfo;
|
|
pub use chaikin_oscillator::ChaikinOscillator;
|
|
pub use chaikin_volatility::ChaikinVolatility;
|
|
pub use chande_kroll_stop::{ChandeKrollStop, ChandeKrollStopOutput};
|
|
pub use chandelier_exit::{ChandelierExit, ChandelierExitOutput};
|
|
pub use choppiness_index::ChoppinessIndex;
|
|
pub use cmf::ChaikinMoneyFlow;
|
|
pub use cmo::Cmo;
|
|
pub use connors_rsi::ConnorsRsi;
|
|
pub use coppock::Coppock;
|
|
pub use dema::Dema;
|
|
pub use donchian::{Donchian, DonchianOutput};
|
|
pub use dpo::Dpo;
|
|
pub use ease_of_movement::EaseOfMovement;
|
|
pub use elder_impulse::ElderImpulse;
|
|
pub use ema::Ema;
|
|
pub use evwma::Evwma;
|
|
pub use force_index::ForceIndex;
|
|
pub use frama::Frama;
|
|
pub use garman_klass::GarmanKlassVolatility;
|
|
pub use historical_volatility::HistoricalVolatility;
|
|
pub use hma::Hma;
|
|
pub use inertia::Inertia;
|
|
pub use jma::Jma;
|
|
pub use kama::Kama;
|
|
pub use keltner::{Keltner, KeltnerOutput};
|
|
pub use kst::{Kst, KstOutput};
|
|
pub use laguerre_rsi::LaguerreRsi;
|
|
pub use linreg::LinearRegression;
|
|
pub use linreg_angle::LinRegAngle;
|
|
pub use linreg_slope::LinRegSlope;
|
|
pub use macd::{MacdIndicator, MacdOutput};
|
|
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 obv::Obv;
|
|
pub use parkinson::ParkinsonVolatility;
|
|
pub use percent_b::PercentB;
|
|
pub use pgo::Pgo;
|
|
pub use pmo::Pmo;
|
|
pub use ppo::Ppo;
|
|
pub use psar::Psar;
|
|
pub use roc::Roc;
|
|
pub use rogers_satchell::RogersSatchellVolatility;
|
|
pub use rsi::Rsi;
|
|
pub use rvi::Rvi;
|
|
pub use rvi_volatility::RviVolatility;
|
|
pub use sma::Sma;
|
|
pub use smi::Smi;
|
|
pub use smma::Smma;
|
|
pub use stc::Stc;
|
|
pub use std_dev::StdDev;
|
|
pub use stoch_rsi::StochRsi;
|
|
pub use stochastic::{Stochastic, StochasticOutput};
|
|
pub use super_trend::{SuperTrend, SuperTrendOutput};
|
|
pub use t3::T3;
|
|
pub use tema::Tema;
|
|
pub use trima::Trima;
|
|
pub use trix::Trix;
|
|
pub use true_range::TrueRange;
|
|
pub use tsi::Tsi;
|
|
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 vortex::{Vortex, VortexOutput};
|
|
pub use vpt::VolumePriceTrend;
|
|
pub use vwap::{RollingVwap, Vwap};
|
|
pub use vwma::Vwma;
|
|
pub use weighted_close::WeightedClose;
|
|
pub use williams_r::WilliamsR;
|
|
pub use wma::Wma;
|
|
pub use yang_zhang::YangZhangVolatility;
|
|
pub use z_score::ZScore;
|
|
pub use zero_lag_macd::{ZeroLagMacd, ZeroLagMacdOutput};
|
|
pub use zlema::Zlema;
|