feat(ao-histogram): add Awesome Oscillator Histogram

AO - SMA(AO, sma_period). A configurable variant of the existing
AcceleratorOscillator (which fixes fast=5, slow=34, sma=5).
Three parameters; defaults match Bill Williams' Accelerator.

Touchpoints: awesome_oscillator_histogram.rs + mod.rs + lib.rs
re-export, PyAoHist + __init__.py + test_new_indicators CANDLE_SCALAR
+ test_known_values flat reference, AwesomeOscillatorHistogramNode +
index.d.ts/index.js + indicators.test.js factory + reference,
WasmAoHist, candle-fuzz target, README + CHANGELOG.
This commit is contained in:
kingchenc
2026-05-24 21:43:26 +02:00
parent c6d5710cd6
commit e043a0dd9b
14 changed files with 409 additions and 14 deletions
+5
View File
@@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **Family 03 — MACD & Price Oscillators.** `AwesomeOscillatorHistogram`:
`AO SMA(AO, sma_period)`. A configurable variant of the existing
`AcceleratorOscillator` (which fixes `(fast, slow, sma) = (5, 34, 5)`).
Three parameters; defaults match Bill Williams' Accelerator. Exposed
in all four bindings.
- **Family 03 — MACD & Price Oscillators.** `APO` (Absolute Price
Oscillator): `EMA(close, fast) EMA(close, slow)`. Like MACD's line
without the signal EMA. Default `(fast = 12, slow = 26)`. `fast` must
+2 -2
View File
@@ -109,7 +109,7 @@ python -m benchmarks.compare_libraries
## Indicators
72 streaming-first indicators across eight families. Every one passes the
73 streaming-first indicators across eight families. Every one passes the
`batch == streaming` equivalence test, reference-value tests, and reset
semantics tests.
@@ -118,7 +118,7 @@ semantics tests.
| Moving Averages | SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, ZLEMA, T3, VWMA |
| Momentum Oscillators | RSI (Wilder), Stochastic, CCI, ROC, Williams %R, MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator |
| Trend & Directional | MACD, ADX (+DI/-DI), Aroon, TRIX, Aroon Oscillator, Vortex, Mass Index, Choppiness Index, Vertical Horizontal Filter |
| Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power, APO |
| Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power, APO, AO Histogram |
| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility |
| Trailing Stops | Parabolic SAR, SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop |
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement |
@@ -102,6 +102,7 @@ const candleScalar = {
MedianPrice: { make: () => new wickra.MedianPrice(), step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
WeightedClose: { make: () => new wickra.WeightedClose(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
AcceleratorOscillator: { make: () => new wickra.AcceleratorOscillator(5, 34, 5), step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
AwesomeOscillatorHistogram: { make: () => new wickra.AwesomeOscillatorHistogram(5, 34, 5), step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
BalanceOfPower: { make: () => new wickra.BalanceOfPower(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
ChoppinessIndex: { make: () => new wickra.ChoppinessIndex(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
TrueRange: { make: () => new wickra.TrueRange(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
@@ -260,6 +261,16 @@ test('LinRegAngle of a unit-slope series is 45 degrees', () => {
assert.ok(Math.abs(out[4] - 45) < 1e-9);
});
test('AwesomeOscillatorHistogram on a flat median converges to zero', () => {
const n = 50;
const out = new wickra.AwesomeOscillatorHistogram(3, 5, 3).batch(
Array(n).fill(11),
Array(n).fill(9),
);
// warmup = 5 + 3 - 1 = 7.
for (let i = 6; i < n; i++) assert.ok(Math.abs(out[i]) < 1e-12);
});
test('APO(3, 5) on a flat series converges to zero', () => {
const out = new wickra.APO(3, 5).batch(Array(30).fill(42));
for (let i = 0; i < 4; i++) assert.ok(Number.isNaN(out[i]));
+2 -1
View File
@@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, KAMA, APO, T3, TSI, PMO, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA } = nativeBinding
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, KAMA, APO, AwesomeOscillatorHistogram, T3, TSI, PMO, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA } = nativeBinding
module.exports.version = version
module.exports.SMA = SMA
@@ -350,6 +350,7 @@ module.exports.AwesomeOscillator = AwesomeOscillator
module.exports.Aroon = Aroon
module.exports.KAMA = KAMA
module.exports.APO = APO
module.exports.AwesomeOscillatorHistogram = AwesomeOscillatorHistogram
module.exports.T3 = T3
module.exports.TSI = TSI
module.exports.PMO = PMO
+52
View File
@@ -1068,6 +1068,58 @@ impl AroonNode {
}
}
#[napi(js_name = "AwesomeOscillatorHistogram")]
pub struct AwesomeOscillatorHistogramNode {
inner: wc::AwesomeOscillatorHistogram,
}
#[napi]
impl AwesomeOscillatorHistogramNode {
#[napi(constructor)]
pub fn new(fast: u32, slow: u32, sma_period: u32) -> napi::Result<Self> {
Ok(Self {
inner: wc::AwesomeOscillatorHistogram::new(
clamp_period(fast),
clamp_period(slow),
clamp_period(sma_period),
)
.map_err(map_err)?,
})
}
#[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<f64>> {
Ok(self.inner.update(cnd(high, low, low, 0.0)?))
}
#[napi]
pub fn batch(&mut self, high: Vec<f64>, low: Vec<f64>) -> napi::Result<Vec<f64>> {
if high.len() != low.len() {
return Err(NapiError::from_reason(
"high and low must be equal length".to_string(),
));
}
let mut out = Vec::with_capacity(high.len());
for i in 0..high.len() {
out.push(
self.inner
.update(cnd(high[i], low[i], low[i], 0.0)?)
.unwrap_or(f64::NAN),
);
}
Ok(out)
}
#[napi]
pub fn reset(&mut self) {
self.inner.reset();
}
#[napi(js_name = "isReady")]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[napi(js_name = "warmupPeriod")]
pub fn warmup_period(&self) -> u32 {
self.inner.warmup_period() as u32
}
}
#[napi(js_name = "APO")]
pub struct ApoNode {
inner: wc::Apo,
@@ -57,6 +57,7 @@ from ._wickra import (
StochRSI,
UltimateOscillator,
APO,
AwesomeOscillatorHistogram,
PPO,
DPO,
Coppock,
@@ -139,6 +140,7 @@ __all__ = [
"StochRSI",
"UltimateOscillator",
"APO",
"AwesomeOscillatorHistogram",
"PPO",
"DPO",
"Coppock",
+63
View File
@@ -812,6 +812,68 @@ impl PyKama {
}
}
// ============================== AwesomeOscillatorHistogram ==============================
#[pyclass(
name = "AwesomeOscillatorHistogram",
module = "wickra._wickra",
skip_from_py_object
)]
#[derive(Clone)]
struct PyAoHist {
inner: wc::AwesomeOscillatorHistogram,
}
#[pymethods]
impl PyAoHist {
#[new]
#[pyo3(signature = (fast=5, slow=34, sma_period=5))]
fn new(fast: usize, slow: usize, sma_period: usize) -> PyResult<Self> {
Ok(Self {
inner: wc::AwesomeOscillatorHistogram::new(fast, slow, sma_period).map_err(map_err)?,
})
}
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<f64>> {
let c = extract_candle(candle)?;
Ok(self.inner.update(c))
}
fn batch<'py>(
&mut self,
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray1<f64>>> {
let h = high
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
let l = low
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
if h.len() != l.len() {
return Err(PyValueError::new_err("high and low must be equal length"));
}
let mut out = Vec::with_capacity(h.len());
for i in 0..h.len() {
let candle = wc::Candle::new(l[i], h[i], l[i], l[i], 0.0, 0).map_err(map_err)?;
out.push(self.inner.update(candle).unwrap_or(f64::NAN));
}
Ok(out.into_pyarray(py))
}
fn reset(&mut self) {
self.inner.reset();
}
fn is_ready(&self) -> bool {
self.inner.is_ready()
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
fn __repr__(&self) -> String {
let (f, s, k) = self.inner.periods();
format!("AwesomeOscillatorHistogram(fast={f}, slow={s}, sma_period={k})")
}
}
// ============================== APO ==============================
#[pyclass(name = "APO", module = "wickra._wickra", skip_from_py_object)]
@@ -4540,6 +4602,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyHma>()?;
m.add_class::<PyKama>()?;
m.add_class::<PyApo>()?;
m.add_class::<PyAoHist>()?;
m.add_class::<PyCci>()?;
m.add_class::<PyRoc>()?;
m.add_class::<PyWilliamsR>()?;
@@ -66,6 +66,16 @@ def test_rsi_wilder_textbook_first_value():
assert math.isclose(out[14], 70.464, abs_tol=0.05)
def test_awesome_oscillator_histogram_flat_series_converges_to_zero():
# Flat median price -> AO = 0 -> SMA(AO) = 0 -> AOHist = 0.
n = 50
high = np.full(n, 11.0)
low = np.full(n, 9.0)
out = ta.AwesomeOscillatorHistogram(3, 5, 3).batch(high, low)
# warmup = slow + sma - 1 = 5 + 3 - 1 = 7.
np.testing.assert_allclose(out[6:], 0.0, atol=1e-12)
def test_apo_constant_series_converges_to_zero():
# Both EMAs reproduce a constant exactly, so APO = 0 after warmup.
out = ta.APO(3, 5).batch(np.full(30, 42.0, dtype=np.float64))
@@ -139,6 +139,10 @@ CANDLE_SCALAR = {
lambda: ta.AcceleratorOscillator(5, 34, 5),
lambda ind, h, l, c, v: ind.batch(h, l),
),
"AwesomeOscillatorHistogram": (
lambda: ta.AwesomeOscillatorHistogram(5, 34, 5),
lambda ind, h, l, c, v: ind.batch(h, l),
),
"BalanceOfPower": (
# The streaming 6-tuple feeds open == close, so batch matches with
# the close column standing in for open.
+41
View File
@@ -1902,6 +1902,47 @@ impl WasmRollingVwap {
}
}
#[wasm_bindgen(js_name = AwesomeOscillatorHistogram)]
pub struct WasmAoHist {
inner: wc::AwesomeOscillatorHistogram,
}
#[wasm_bindgen(js_class = AwesomeOscillatorHistogram)]
impl WasmAoHist {
#[wasm_bindgen(constructor)]
pub fn new(fast: usize, slow: usize, sma_period: usize) -> Result<WasmAoHist, JsError> {
Ok(Self {
inner: wc::AwesomeOscillatorHistogram::new(fast, slow, sma_period).map_err(map_err)?,
})
}
pub fn update(&mut self, high: f64, low: f64) -> Result<Option<f64>, JsError> {
let c = make_candle(high, low, low, 0.0)?;
Ok(self.inner.update(c))
}
pub fn batch(&mut self, high: &[f64], low: &[f64]) -> Result<Float64Array, JsError> {
if high.len() != low.len() {
return Err(JsError::new("high and low must be equal length"));
}
let mut out = Vec::with_capacity(high.len());
for i in 0..high.len() {
let c = make_candle(high[i], low[i], low[i], 0.0)?;
out.push(self.inner.update(c).unwrap_or(f64::NAN));
}
Ok(Float64Array::from(out.as_slice()))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = AwesomeOscillator)]
pub struct WasmAo {
inner: wc::AwesomeOscillator,
@@ -0,0 +1,198 @@
//! Awesome Oscillator Histogram.
use crate::error::{Error, Result};
use crate::indicators::awesome_oscillator::AwesomeOscillator;
use crate::indicators::sma::Sma;
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// "Awesome Oscillator Histogram" — the difference between the Awesome
/// Oscillator and its `sma_period`-bar `SMA`. Positive bars mean `AO` is
/// trending up (bullish acceleration); negative bars mean `AO` is trending
/// down (bearish acceleration).
///
/// ```text
/// AO = SMA(median, fast) SMA(median, slow)
/// AOHist = AO SMA(AO, sma_period)
/// ```
///
/// With Williams' default `sma_period = 5`, this collapses to the existing
/// `AcceleratorOscillator` for `fast = 5, slow = 34, sma_period = 5`; for any
/// other parameterisation this is a more flexible variant.
///
/// # Example
///
/// ```
/// use wickra_core::{AwesomeOscillatorHistogram, Candle, Indicator};
///
/// let mut hist = AwesomeOscillatorHistogram::classic();
/// let mut last = None;
/// for i in 0..80 {
/// let p = 100.0 + f64::from(i);
/// let candle = Candle::new(p, p + 0.5, p - 0.5, p, 1.0, i64::from(i)).unwrap();
/// last = hist.update(candle);
/// }
/// assert!(last.is_some());
/// ```
#[derive(Debug, Clone)]
pub struct AwesomeOscillatorHistogram {
fast_period: usize,
slow_period: usize,
sma_period: usize,
ao: AwesomeOscillator,
sma: Sma,
}
impl AwesomeOscillatorHistogram {
/// # Errors
/// - [`Error::PeriodZero`] if any period is zero.
/// - [`Error::InvalidPeriod`] if `fast >= slow`.
pub fn new(fast: usize, slow: usize, sma_period: usize) -> Result<Self> {
if fast == 0 || slow == 0 || sma_period == 0 {
return Err(Error::PeriodZero);
}
if fast >= slow {
return Err(Error::InvalidPeriod {
message: "AwesomeOscillatorHistogram fast must be strictly less than slow",
});
}
Ok(Self {
fast_period: fast,
slow_period: slow,
sma_period,
ao: AwesomeOscillator::new(fast, slow)?,
sma: Sma::new(sma_period)?,
})
}
/// Bill Williams' Accelerator-equivalent defaults `(5, 34, 5)`.
pub fn classic() -> Self {
Self::new(5, 34, 5).expect("classic Awesome Oscillator Histogram parameters are valid")
}
/// Configured `(fast_period, slow_period, sma_period)`.
pub const fn periods(&self) -> (usize, usize, usize) {
(self.fast_period, self.slow_period, self.sma_period)
}
}
impl Indicator for AwesomeOscillatorHistogram {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
let ao = self.ao.update(candle)?;
let sma = self.sma.update(ao)?;
Some(ao - sma)
}
fn reset(&mut self) {
self.ao.reset();
self.sma.reset();
}
fn warmup_period(&self) -> usize {
// AO emits at `slow` candles; the SMA then needs `sma_period - 1`
// more AO values to fill its window.
self.slow_period + self.sma_period - 1
}
fn is_ready(&self) -> bool {
self.sma.is_ready()
}
fn name(&self) -> &'static str {
"AwesomeOscillatorHistogram"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::BatchExt;
use approx::assert_relative_eq;
fn candle(price: f64, ts: i64) -> Candle {
Candle::new(price, price + 0.5, price - 0.5, price, 1.0, ts).unwrap()
}
#[test]
fn rejects_zero_period() {
assert!(matches!(
AwesomeOscillatorHistogram::new(0, 34, 5),
Err(Error::PeriodZero)
));
assert!(matches!(
AwesomeOscillatorHistogram::new(5, 0, 5),
Err(Error::PeriodZero)
));
assert!(matches!(
AwesomeOscillatorHistogram::new(5, 34, 0),
Err(Error::PeriodZero)
));
}
#[test]
fn rejects_fast_geq_slow() {
assert!(matches!(
AwesomeOscillatorHistogram::new(34, 5, 5),
Err(Error::InvalidPeriod { .. })
));
}
#[test]
fn accessors_and_metadata() {
let hist = AwesomeOscillatorHistogram::classic();
assert_eq!(hist.periods(), (5, 34, 5));
assert_eq!(hist.warmup_period(), 38);
assert_eq!(hist.name(), "AwesomeOscillatorHistogram");
}
#[test]
fn constant_series_converges_to_zero() {
// AO of a flat series is 0; SMA of 0 is 0; difference is 0.
let mut hist = AwesomeOscillatorHistogram::new(3, 5, 3).unwrap();
let candles: Vec<Candle> = (0..30).map(|i| candle(42.0, i)).collect();
let out = hist.batch(&candles);
for v in out.iter().skip(hist.warmup_period() - 1).flatten() {
assert_relative_eq!(*v, 0.0, epsilon = 1e-12);
}
}
#[test]
fn warmup_emits_first_value_at_warmup_period() {
let mut hist = AwesomeOscillatorHistogram::new(2, 4, 3).unwrap();
assert_eq!(hist.warmup_period(), 6);
let candles: Vec<Candle> = (0..8)
.map(|i| candle(10.0 + f64::from(i), i64::from(i)))
.collect();
let out = hist.batch(&candles);
for v in out.iter().take(5) {
assert!(v.is_none());
}
assert!(out[5].is_some());
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..100_i64)
.map(|i| candle(100.0 + (i as f64 * 0.3).sin() * 5.0, i))
.collect();
let batch = AwesomeOscillatorHistogram::classic().batch(&candles);
let mut b = AwesomeOscillatorHistogram::classic();
let streamed: Vec<_> = candles.iter().map(|c| b.update(*c)).collect();
assert_eq!(batch, streamed);
}
#[test]
fn reset_clears_state() {
let mut hist = AwesomeOscillatorHistogram::classic();
let candles: Vec<Candle> = (0..80)
.map(|i| candle(10.0 + f64::from(i), i64::from(i)))
.collect();
hist.batch(&candles);
assert!(hist.is_ready());
hist.reset();
assert!(!hist.is_ready());
}
}
+2
View File
@@ -13,6 +13,7 @@ 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;
@@ -86,6 +87,7 @@ 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;
+11 -10
View File
@@ -45,16 +45,17 @@ pub mod indicators;
pub use error::{Error, Result};
pub use indicators::{
AcceleratorOscillator, Adl, Adx, AdxOutput, Apo, Aroon, AroonOscillator, AroonOutput, Atr,
AtrTrailingStop, AwesomeOscillator, BalanceOfPower, BollingerBands, BollingerBandwidth,
BollingerOutput, Cci, ChaikinMoneyFlow, ChaikinOscillator, ChaikinVolatility, ChandeKrollStop,
ChandeKrollStopOutput, ChandelierExit, ChandelierExitOutput, ChoppinessIndex, Cmo, Coppock,
Dema, Donchian, DonchianOutput, Dpo, EaseOfMovement, Ema, ForceIndex, HistoricalVolatility,
Hma, Kama, Keltner, KeltnerOutput, LinRegAngle, LinRegSlope, LinearRegression, MacdIndicator,
MacdOutput, MassIndex, MedianPrice, Mfi, Mom, Natr, Obv, PercentB, Pmo, Ppo, Psar, Roc,
RollingVwap, Rsi, Sma, Smma, StdDev, StochRsi, Stochastic, StochasticOutput, SuperTrend,
SuperTrendOutput, Tema, Trima, Trix, TrueRange, Tsi, TypicalPrice, UlcerIndex,
UltimateOscillator, VerticalHorizontalFilter, VolumePriceTrend, Vortex, VortexOutput, Vwap,
Vwma, WeightedClose, WilliamsR, Wma, ZScore, Zlema, T3,
AtrTrailingStop, AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, BollingerBands,
BollingerBandwidth, BollingerOutput, Cci, ChaikinMoneyFlow, ChaikinOscillator,
ChaikinVolatility, ChandeKrollStop, ChandeKrollStopOutput, ChandelierExit,
ChandelierExitOutput, ChoppinessIndex, Cmo, Coppock, Dema, Donchian, DonchianOutput, Dpo,
EaseOfMovement, Ema, ForceIndex, HistoricalVolatility, Hma, Kama, Keltner, KeltnerOutput,
LinRegAngle, LinRegSlope, LinearRegression, MacdIndicator, MacdOutput, MassIndex, MedianPrice,
Mfi, Mom, Natr, Obv, PercentB, Pmo, Ppo, Psar, Roc, RollingVwap, Rsi, Sma, Smma, StdDev,
StochRsi, Stochastic, StochasticOutput, SuperTrend, SuperTrendOutput, Tema, Trima, Trix,
TrueRange, Tsi, TypicalPrice, UlcerIndex, UltimateOscillator, VerticalHorizontalFilter,
VolumePriceTrend, Vortex, VortexOutput, Vwap, Vwma, WeightedClose, WilliamsR, Wma, ZScore,
Zlema, T3,
};
pub use ohlcv::{Candle, Tick};
pub use traits::{BatchExt, Chain, Indicator};
+6 -1
View File
@@ -24,7 +24,8 @@
use libfuzzer_sys::fuzz_target;
use wickra_core::{
AcceleratorOscillator, Adl, Adx, Aroon, AroonOscillator, Atr, AtrTrailingStop,
AwesomeOscillator, BalanceOfPower, BatchExt, Candle, Cci, ChaikinMoneyFlow, ChaikinOscillator,
AwesomeOscillator, AwesomeOscillatorHistogram, BalanceOfPower, BatchExt, Candle, Cci,
ChaikinMoneyFlow, ChaikinOscillator,
ChaikinVolatility, ChandeKrollStop, ChandelierExit, ChoppinessIndex, Donchian, EaseOfMovement,
ForceIndex, Indicator, Keltner, MassIndex, MedianPrice, Mfi, Natr, Obv, Psar, RollingVwap,
Stochastic, SuperTrend, TrueRange, TypicalPrice, UltimateOscillator, VolumePriceTrend, Vortex,
@@ -97,6 +98,10 @@ fuzz_target!(|data: Vec<f64>| {
drive(|| Cci::new(20).unwrap(), &candles);
drive(|| WilliamsR::new(14).unwrap(), &candles);
drive(|| AwesomeOscillator::new(5, 34).unwrap(), &candles);
drive(
|| AwesomeOscillatorHistogram::new(5, 34, 5).unwrap(),
&candles,
);
drive(|| AcceleratorOscillator::new(5, 34, 5).unwrap(), &candles);
drive(|| UltimateOscillator::new(7, 14, 28).unwrap(), &candles);
drive(BalanceOfPower::new, &candles);