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.
4.4 KiB
NATR
Normalized Average True Range — ATR expressed as a percentage of price, so volatility is comparable across instruments.
Quick reference
| Field | Value |
|---|---|
| Family | Volatility |
| Sub-category | Range-average |
| Input type | Candle (uses high, low, close) |
| Output type | f64 |
| Output range | [0, ∞) (percent) |
| Default parameters | period = 14 (Python) |
| Warmup period | period |
| Interpretation | Average true range as a percent of the close. |
Formula
NATR = 100 · ATR(period) / close
Atr measures volatility in raw price units — a 2.0
ATR is large on a $10 stock and tiny on a $5000 index. Dividing by the
current close converts it to a percentage, so a NATR of 2.0 always
means "the average true range is 2 % of price". That makes NATR readings
comparable across a portfolio, and stop or position-size rules expressed
as a NATR multiple behave consistently regardless of price level.
Parameters
| Name | Type | Default | Valid range | Description |
|---|---|---|---|---|
period |
usize |
14 (Python) |
>= 1 |
Wilder smoothing period of the underlying ATR. 0 errors with Error::PeriodZero. |
The Python binding defaults period to 14.
Inputs / Outputs
From crates/wickra-core/src/indicators/natr.rs:
impl Indicator for Natr {
type Input = Candle;
type Output = f64;
// update(&mut self, input: Candle) -> Option<f64>
}
NATR is a candle-input indicator: it reads high, low and
close. In Python the streaming update accepts a 6-tuple or a dict; the
batch helper takes high, low, close numpy arrays. Node and WASM
expose update(high, low, close) and batch(high, low, close).
Warmup
Natr::new(period).warmup_period() == period — identical to the
underlying Atr, which is Wilder-seeded over period true ranges.
Edge cases
- Flat market. A market with no range has
ATR = 0, soNATR = 0(flat_market_yields_zeropins this). - Zero close. NATR is undefined against a
0.0close; the indicator reports0.0for that bar. - Identity. NATR equals
100 · ATR / closebar for bar (natr_is_atr_over_close_as_percentpins this). - Reset.
natr.reset()clears the underlying ATR.
Examples
Rust
use wickra::{BatchExt, Candle, Indicator, Natr};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut natr = Natr::new(14)?;
let candles: Vec<Candle> = (0..40)
.map(|i| {
let p = 100.0 + f64::from(i);
Candle::new(p, p + 2.0, p - 2.0, p, 10.0, i64::from(i)).unwrap()
})
.collect();
let out = natr.batch(&candles);
println!("warmup_period = {}", natr.warmup_period());
println!("last = {:?}", out.last().unwrap());
Ok(())
}
Python
import numpy as np
import wickra as ta
natr = ta.NATR(14)
high = np.arange(102.0, 142.0)
low = high - 4.0
close = high - 2.0
print(natr.batch(high, low, close)[-1])
Node
const ta = require('wickra');
const natr = new ta.NATR(14);
const high = Array.from({ length: 40 }, (_, i) => 102 + i);
const low = high.map((h) => h - 4);
const close = high.map((h) => h - 2);
console.log(natr.batch(high, low, close).at(-1));
Interpretation
Natr is the tool of choice whenever an ATR-based rule must work across
instruments or across long stretches of time where the price level
drifts. A volatility filter like "skip entries when NATR > 5" or a stop
at "entry − 3 × NATR %" stays meaningful on any symbol. Use raw
Atr only when you specifically want the answer in
price units (e.g. to place a stop a fixed number of points away).
Common pitfalls
- Feeding it scalar prices. It needs
high/low/close. - Confusing it with ATR. NATR is a percentage; an ATR-multiple stop and a NATR-multiple stop are different distances.
References
NATR is the percentage-normalised ATR as implemented by TA-Lib (NATR);
the underlying ATR is Wilder's from New Concepts in Technical Trading
Systems (1978).
See also
- Indicator-Atr.md — the price-unit original.
- Indicator-HistoricalVolatility.md — return-based annualised volatility.
- Indicators-Overview.md — the full taxonomy.