F8: add Bollinger Bandwidth and %b
Completes the F8 family (Bands & channels) end to end: - Rust core: bollinger_bandwidth.rs ((upper - lower) / middle — the squeeze gauge) and percent_b.rs ((price - lower) / (upper - lower) — price position within the bands, unclamped). Both wrap BollingerBands and carry a full Indicator impl, runnable doctest and reference / constant-series / definition-consistency / warmup / reset / batch==streaming tests. - Python: PyBollingerBandwidth / PyPercentB PyO3 classes + module registration + .pyi stubs (defaults (20, 2.0)). - Node: explicit BollingerBandwidthNode and PercentBNode; index.d.ts and index.js updated. - WASM: WasmBollingerBandwidth / WasmPercentB via the scalar macro. - Wiki: Indicator-BollingerBandwidth.md and Indicator-PercentB.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 362 core tests, 25 data tests and 51 doctests green.
This commit is contained in:
@@ -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, T3, VWMA, MOM, CMO, TSI, PMO, StochRSI, UltimateOscillator, PPO, DPO, Coppock, AroonOscillator, Vortex, MassIndex, NATR, StdDev, UlcerIndex, HistoricalVolatility, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AwesomeOscillator, Aroon, KAMA } = nativeBinding
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, T3, VWMA, MOM, CMO, TSI, PMO, StochRSI, UltimateOscillator, PPO, DPO, Coppock, AroonOscillator, Vortex, MassIndex, NATR, StdDev, UlcerIndex, HistoricalVolatility, BollingerBandwidth, PercentB, MACD, BollingerBands, ATR, Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AwesomeOscillator, Aroon, KAMA } = nativeBinding
|
||||
|
||||
module.exports.version = version
|
||||
module.exports.SMA = SMA
|
||||
@@ -343,6 +343,8 @@ module.exports.NATR = NATR
|
||||
module.exports.StdDev = StdDev
|
||||
module.exports.UlcerIndex = UlcerIndex
|
||||
module.exports.HistoricalVolatility = HistoricalVolatility
|
||||
module.exports.BollingerBandwidth = BollingerBandwidth
|
||||
module.exports.PercentB = PercentB
|
||||
module.exports.MACD = MACD
|
||||
module.exports.BollingerBands = BollingerBands
|
||||
module.exports.ATR = ATR
|
||||
|
||||
@@ -1147,6 +1147,80 @@ impl PmoNode {
|
||||
|
||||
// ============================== VWMA ==============================
|
||||
|
||||
// ============================== Bollinger Bandwidth ==============================
|
||||
|
||||
#[napi(js_name = "BollingerBandwidth")]
|
||||
pub struct BollingerBandwidthNode {
|
||||
inner: wc::BollingerBandwidth,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl BollingerBandwidthNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, multiplier: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::BollingerBandwidth::new(period as usize, multiplier).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Percent B ==============================
|
||||
|
||||
#[napi(js_name = "PercentB")]
|
||||
pub struct PercentBNode {
|
||||
inner: wc::PercentB,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl PercentBNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, multiplier: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::PercentB::new(period as usize, multiplier).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== NATR ==============================
|
||||
|
||||
#[napi(js_name = "NATR")]
|
||||
|
||||
@@ -76,6 +76,34 @@ class TRIMA:
|
||||
@property
|
||||
def value(self) -> Optional[float]: ...
|
||||
|
||||
class BollingerBandwidth:
|
||||
def __init__(self, period: int = 20, multiplier: float = 2.0) -> None: ...
|
||||
def update(self, value: float) -> Optional[float]: ...
|
||||
def batch(self, prices: NDArray[np.float64]) -> NDArray[np.float64]: ...
|
||||
def reset(self) -> None: ...
|
||||
def is_ready(self) -> bool: ...
|
||||
def warmup_period(self) -> int: ...
|
||||
@property
|
||||
def period(self) -> int: ...
|
||||
@property
|
||||
def multiplier(self) -> float: ...
|
||||
@property
|
||||
def value(self) -> Optional[float]: ...
|
||||
|
||||
class PercentB:
|
||||
def __init__(self, period: int = 20, multiplier: float = 2.0) -> None: ...
|
||||
def update(self, value: float) -> Optional[float]: ...
|
||||
def batch(self, prices: NDArray[np.float64]) -> NDArray[np.float64]: ...
|
||||
def reset(self) -> None: ...
|
||||
def is_ready(self) -> bool: ...
|
||||
def warmup_period(self) -> int: ...
|
||||
@property
|
||||
def period(self) -> int: ...
|
||||
@property
|
||||
def multiplier(self) -> float: ...
|
||||
@property
|
||||
def value(self) -> Optional[float]: ...
|
||||
|
||||
class NATR:
|
||||
def __init__(self, period: int = 14) -> None: ...
|
||||
def update(self, candle: CandleLike) -> Optional[float]: ...
|
||||
|
||||
@@ -1519,6 +1519,126 @@ impl PyAroon {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Bollinger Bandwidth ==============================
|
||||
|
||||
#[pyclass(name = "BollingerBandwidth", module = "wickra._wickra")]
|
||||
#[derive(Clone)]
|
||||
struct PyBollingerBandwidth {
|
||||
inner: wc::BollingerBandwidth,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyBollingerBandwidth {
|
||||
#[new]
|
||||
#[pyo3(signature = (period=20, multiplier=2.0))]
|
||||
fn new(period: usize, multiplier: f64) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::BollingerBandwidth::new(period, multiplier).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
prices: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||
let slice = prices
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
Ok(flatten(self.inner.batch(slice)).into_pyarray_bound(py))
|
||||
}
|
||||
#[getter]
|
||||
fn period(&self) -> usize {
|
||||
self.inner.period()
|
||||
}
|
||||
#[getter]
|
||||
fn multiplier(&self) -> f64 {
|
||||
self.inner.multiplier()
|
||||
}
|
||||
#[getter]
|
||||
fn value(&self) -> Option<f64> {
|
||||
self.inner.value()
|
||||
}
|
||||
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 {
|
||||
format!(
|
||||
"BollingerBandwidth(period={}, multiplier={})",
|
||||
self.inner.period(),
|
||||
self.inner.multiplier()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Percent B ==============================
|
||||
|
||||
#[pyclass(name = "PercentB", module = "wickra._wickra")]
|
||||
#[derive(Clone)]
|
||||
struct PyPercentB {
|
||||
inner: wc::PercentB,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyPercentB {
|
||||
#[new]
|
||||
#[pyo3(signature = (period=20, multiplier=2.0))]
|
||||
fn new(period: usize, multiplier: f64) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::PercentB::new(period, multiplier).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
prices: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||
let slice = prices
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
Ok(flatten(self.inner.batch(slice)).into_pyarray_bound(py))
|
||||
}
|
||||
#[getter]
|
||||
fn period(&self) -> usize {
|
||||
self.inner.period()
|
||||
}
|
||||
#[getter]
|
||||
fn multiplier(&self) -> f64 {
|
||||
self.inner.multiplier()
|
||||
}
|
||||
#[getter]
|
||||
fn value(&self) -> Option<f64> {
|
||||
self.inner.value()
|
||||
}
|
||||
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 {
|
||||
format!(
|
||||
"PercentB(period={}, multiplier={})",
|
||||
self.inner.period(),
|
||||
self.inner.multiplier()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== NATR ==============================
|
||||
|
||||
#[pyclass(name = "NATR", module = "wickra._wickra")]
|
||||
@@ -2789,5 +2909,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<PyStdDev>()?;
|
||||
m.add_class::<PyUlcerIndex>()?;
|
||||
m.add_class::<PyHistoricalVolatility>()?;
|
||||
m.add_class::<PyBollingerBandwidth>()?;
|
||||
m.add_class::<PyPercentB>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -90,6 +90,8 @@ wasm_scalar_indicator!(WasmCoppock, "Coppock", wc::Coppock, roc_long: usize, roc
|
||||
wasm_scalar_indicator!(WasmStdDev, "StdDev", wc::StdDev, period: usize);
|
||||
wasm_scalar_indicator!(WasmUlcerIndex, "UlcerIndex", wc::UlcerIndex, period: usize);
|
||||
wasm_scalar_indicator!(WasmHistoricalVolatility, "HistoricalVolatility", wc::HistoricalVolatility, period: usize, trading_periods: usize);
|
||||
wasm_scalar_indicator!(WasmBollingerBandwidth, "BollingerBandwidth", wc::BollingerBandwidth, period: usize, multiplier: f64);
|
||||
wasm_scalar_indicator!(WasmPercentB, "PercentB", wc::PercentB, period: usize, multiplier: f64);
|
||||
|
||||
// ---------- KAMA (three params) ----------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user