F7: add NATR, StdDev, Ulcer Index and Historical Volatility
Completes the F7 family (Volatility) end to end: - Rust core: natr.rs (ATR as a percentage of close), std_dev.rs (rolling population standard deviation), ulcer_index.rs (RMS of trailing-high drawdowns — downside-only risk), historical_volatility.rs (annualised sample stddev of log returns). Each with a full Indicator impl, runnable doctest and reference / constant-series / warmup / reset / batch==streaming tests. - Python: PyNatr / PyStdDev / PyUlcerIndex / PyHistoricalVolatility PyO3 classes + module registration + .pyi stubs. - Node: StdDevNode / UlcerIndexNode via the scalar macro, explicit NatrNode and HistoricalVolatilityNode; index.d.ts and index.js updated. - WASM: WasmStdDev / WasmUlcerIndex / WasmHistoricalVolatility via the scalar macro, explicit WasmNatr. - Wiki: Indicator-Natr/StdDev/UlcerIndex/HistoricalVolatility.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 350 core tests, 25 data tests and 49 doctests green.
This commit is contained in:
@@ -108,6 +108,8 @@ node_scalar_indicator!(ZlemaNode, "ZLEMA", wc::Zlema);
|
||||
node_scalar_indicator!(MomNode, "MOM", wc::Mom);
|
||||
node_scalar_indicator!(CmoNode, "CMO", wc::Cmo);
|
||||
node_scalar_indicator!(DpoNode, "DPO", wc::Dpo);
|
||||
node_scalar_indicator!(StdDevNode, "StdDev", wc::StdDev);
|
||||
node_scalar_indicator!(UlcerIndexNode, "UlcerIndex", wc::UlcerIndex);
|
||||
|
||||
// ============================== MACD ==============================
|
||||
|
||||
@@ -1145,6 +1147,99 @@ impl PmoNode {
|
||||
|
||||
// ============================== VWMA ==============================
|
||||
|
||||
// ============================== NATR ==============================
|
||||
|
||||
#[napi(js_name = "NATR")]
|
||||
pub struct NatrNode {
|
||||
inner: wc::Natr,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl NatrNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::Natr::new(period as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64, close: f64) -> napi::Result<Option<f64>> {
|
||||
Ok(self.inner.update(cnd(high, low, close, 0.0)?))
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<f64>> {
|
||||
if high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"high, low, close 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], close[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
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Historical Volatility ==============================
|
||||
|
||||
#[napi(js_name = "HistoricalVolatility")]
|
||||
pub struct HistoricalVolatilityNode {
|
||||
inner: wc::HistoricalVolatility,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl HistoricalVolatilityNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, trading_periods: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::HistoricalVolatility::new(period as usize, trading_periods as usize)
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Aroon Oscillator ==============================
|
||||
|
||||
#[napi(js_name = "AroonOscillator")]
|
||||
|
||||
Reference in New Issue
Block a user