F11: add SuperTrend, Chandelier Exit, Chande Kroll Stop and ATR Trailing Stop

- Rust core: super_trend.rs (SuperTrend — ATR-banded trailing stop with
  flip logic; SuperTrendOutput { value, direction }), chandelier_exit.rs
  (Chandelier Exit — ATR stop hung off the window's highest high / lowest
  low; ChandelierExitOutput { long_stop, short_stop }),
  chande_kroll_stop.rs (Chande Kroll Stop — a two-stage ATR stop;
  ChandeKrollStopOutput { stop_long, stop_short }), atr_trailing_stop.rs
  (ATR Trailing Stop — a single ratcheting close-based stop). Each with a
  full Indicator impl, runnable doctest and reference / property / warmup
  / reset / batch==streaming tests.
- Python: PySuperTrend / PyChandelierExit / PyChandeKrollStop /
  PyAtrTrailingStop PyO3 classes (struct outputs as tuples and (n, 2)
  arrays) + module registration + .pyi stubs.
- Node: explicit SuperTrendNode / ChandelierExitNode / ChandeKrollStopNode
  / AtrTrailingStopNode with SuperTrendValue / ChandelierExitValue /
  ChandeKrollStopValue objects; index.d.ts and index.js updated.
- WASM: WasmSuperTrend / WasmChandelierExit / WasmChandeKrollStop /
  WasmAtrTrailingStop.
- Wiki: Indicator-SuperTrend/ChandelierExit/ChandeKrollStop/
  AtrTrailingStop.md plus rows in the "Trailing stop" table of
  Indicators-Overview.md and entries in Home.md.
- Add clippy.toml with doc-valid-idents for the proper noun "LeBeau".

cargo fmt + clippy (core/wickra/data/wasm/node) clean; 427 core tests,
25 data tests and 61 doctests green.
This commit is contained in:
kingchenc
2026-05-22 19:42:14 +02:00
parent 0b11a523a0
commit 21bbd521b3
18 changed files with 2643 additions and 13 deletions
+4
View File
@@ -124,6 +124,10 @@ Rust / Python / Node examples. They are grouped by family, mirroring the
- [Indicator-HistoricalVolatility.md](indicators/volatility/Indicator-HistoricalVolatility.md)
- [Indicator-BollingerBandwidth.md](indicators/volatility/Indicator-BollingerBandwidth.md)
- [Indicator-PercentB.md](indicators/volatility/Indicator-PercentB.md)
- [Indicator-SuperTrend.md](indicators/volatility/Indicator-SuperTrend.md)
- [Indicator-ChandelierExit.md](indicators/volatility/Indicator-ChandelierExit.md)
- [Indicator-ChandeKrollStop.md](indicators/volatility/Indicator-ChandeKrollStop.md)
- [Indicator-AtrTrailingStop.md](indicators/volatility/Indicator-AtrTrailingStop.md)
**Volume** — price moves weighted or confirmed by traded volume.
+9 -5
View File
@@ -1,6 +1,6 @@
# Indicators Overview
Wickra ships 54 indicators, organised in source under the four classical
Wickra ships 58 indicators, organised in source under the four classical
families — trend, momentum, volatility, volume — that map directly to the
directory structure of `crates/wickra-core/src/indicators/`. The same family
labels are used here, plus a second-level grouping that reflects how the
@@ -118,10 +118,10 @@ Centered on zero or driven by raw price differences; no fixed cap.
## Volatility
Volatility indicators sit in two functional groups: those that draw an
envelope around price, and those that report a scalar dispersion/range.
PSAR is a special case — a trailing-stop tracker rather than a width
measure — that lives in the volatility module by source convention.
Volatility indicators sit in three functional groups: those that draw an
envelope around price, those that report a scalar dispersion/range, and a
set of trailing stops — ATR-driven stop-loss trackers rather than width
measures — that live in the volatility module by source convention.
### Envelopes
@@ -148,6 +148,10 @@ measure — that lives in the volatility module by source convention.
| Indicator | One-liner | Input | Output | Range | Defaults | Warmup | Deep dive |
|-----------|-----------|-------|--------|-------|----------|--------|-----------|
| `Psar` | Wilder's Parabolic Stop-and-Reverse; per-bar stop level that flips sides on price crossing. | `Candle` | `f64` | unbounded (price scale) | `(af_start=0.02, af_step=0.02, af_max=0.20)` (Python) | `2` | [Indicator-Psar.md](indicators/volatility/Indicator-Psar.md) |
| `SuperTrend` | ATR-banded trailing stop that flips on a close through the band; reports the line and the trend direction. | `Candle` | `(value, direction)` | `value` price scale; `direction` `±1` | `(atr_period=10, multiplier=3.0)` (Python) | `atr_period` | [Indicator-SuperTrend.md](indicators/volatility/Indicator-SuperTrend.md) |
| `ChandelierExit` | `highest_high k·ATR` (long stop) and `lowest_low + k·ATR` (short stop). | `Candle` | `(long_stop, short_stop)` | unbounded (price scale) | `(period=22, multiplier=3.0)` (Python) | `period` | [Indicator-ChandelierExit.md](indicators/volatility/Indicator-ChandelierExit.md) |
| `ChandeKrollStop` | Two-stage ATR stop: an extreme-based stop, then smoothed over a shorter window. | `Candle` | `(stop_long, stop_short)` | unbounded (price scale) | `(atr_period=10, atr_multiplier=1.0, stop_period=9)` (Python) | `atr_period + stop_period 1` | [Indicator-ChandeKrollStop.md](indicators/volatility/Indicator-ChandeKrollStop.md) |
| `AtrTrailingStop` | A single line trailing the close by `k·ATR`, ratcheting toward the trend and flipping on a cross. | `Candle` | `f64` | unbounded (price scale) | `(atr_period=14, multiplier=3.0)` (Python) | `atr_period` | [Indicator-AtrTrailingStop.md](indicators/volatility/Indicator-AtrTrailingStop.md) |
## Volume
@@ -0,0 +1,170 @@
# AtrTrailingStop
> ATR Trailing Stop — a single stop level that trails price by a fixed ATR
> multiple, ratcheting toward the trend and flipping on a close through it.
## Quick reference
| Field | Value |
|-------|-------|
| Family | Volatility |
| Sub-category | Trailing stop |
| Input type | `Candle` (uses `high`, `low`, `close`) |
| Output type | `f64` |
| Output range | unbounded (price scale) |
| Default parameters | `atr_period = 14`, `multiplier = 3.0` (Python) |
| Warmup period | `atr_period` |
| Interpretation | One trailing stop line; price closing through it flips the trade. |
## Formula
```
loss = multiplier · ATR
stop_t = max(stop_{t1}, close loss) while price holds above the stop
= min(stop_{t1}, close + loss) while price holds below the stop
= close loss on a fresh break above the stop
= close + loss on a fresh break below the stop
```
This is the trailing stop popularised by the "UT Bot": a single line that sits
`multiplier · ATR` away from the close. While price holds on one side of the
stop the level only ratchets *toward* price — up in an uptrend, down in a
downtrend — and never away from it. When a close crosses the stop the level
snaps to the opposite side of the new close, flipping the trade. Unlike the
[`ChandelierExit`](Indicator-ChandelierExit.md), it hangs off the close
itself, not the window's extreme, and reports one line rather than two.
## Parameters
- `atr_period` — the ATR lookback (Python default `14`).
- `multiplier` — the ATR multiple the stop trails by (Python default `3.0`).
`AtrTrailingStop::classic()` returns the `(14, 3.0)` configuration.
## Inputs / Outputs
From `crates/wickra-core/src/indicators/atr_trailing_stop.rs`:
```rust
impl Indicator for AtrTrailingStop {
type Input = Candle;
type Output = f64;
// update(&mut self, input: Candle) -> Option<f64>
}
```
`AtrTrailingStop` is a **candle-input** indicator (it reads `high`, `low`,
`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 the matching `batch`.
## Warmup
`AtrTrailingStop::classic().warmup_period() == 14`. The first value lands once
the inner ATR is ready, on input index `atr_period 1`. That first bar seeds
the stop below price (a long).
## Edge cases
- **Seed bar.** The first emitted stop is `close loss` — the indicator
starts on the long side.
- **Ratchet.** While price holds above the stop it never moves down, and
while price holds below it never moves up
(`uptrend_stop_ratchets_up_and_stays_below_price` pins this).
- **Flat market.** Constant candles hold the stop at a fixed `close loss`.
- **Reset.** `ts.reset()` clears the ATR and the carried stop / close.
## Examples
### Rust
```rust
use wickra::{BatchExt, Candle, Indicator, AtrTrailingStop};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut ts = AtrTrailingStop::new(5, 3.0)?;
// Flat market: ATR = 2, loss = 3·2 = 6, stop = 10 - 6 = 4.
let candles: Vec<Candle> = (0..20)
.map(|i| Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, i).unwrap())
.collect();
let out = ts.batch(&candles);
println!("{:?}", out.last().unwrap());
Ok(())
}
```
Output:
```
Some(4.0)
```
On a flat market the seeded long stop holds at `close loss = 10 6 = 4`.
This matches the `reference_values_flat_market` test in
`crates/wickra-core/src/indicators/atr_trailing_stop.rs`.
### Python
```python
import numpy as np
import wickra as ta
ts = ta.AtrTrailingStop(5, 3.0)
n = 20
high = np.full(n, 11.0)
low = np.full(n, 9.0)
close = np.full(n, 10.0)
print(ts.batch(high, low, close)[-1])
```
Output:
```
4.0
```
### Node
```javascript
const ta = require('wickra');
const ts = new ta.AtrTrailingStop(5, 3.0);
const n = 20;
const high = Array(n).fill(11), low = Array(n).fill(9), close = Array(n).fill(10);
const out = ts.batch(high, low, close);
console.log(out[out.length - 1]);
```
Output:
```
4
```
## Interpretation
Read it as a stop-and-reverse line: while the stop sits below the close you
are long and it trails your profit up; the bar a close prints below the stop,
it flips above the new close and you are short. A larger `multiplier` gives
the trade more room — fewer flips, wider risk; a smaller one flips sooner.
## Common pitfalls
- **Expecting it off the window high.** It trails the *close*, so it can sit
closer to price than a [`ChandelierExit`](Indicator-ChandelierExit.md).
- **Feeding it scalar prices.** It needs the full `high`/`low`/`close` bar to
drive the ATR.
## References
The ATR Trailing Stop used by the well-known "UT Bot"; the four-branch ratchet
here matches the common Sylvain Vervoort formulation.
## See also
- [Indicator-SuperTrend.md](Indicator-SuperTrend.md) — an ATR trailing stop
with band ratcheting and an explicit direction flag.
- [Indicator-ChandelierExit.md](Indicator-ChandelierExit.md) — an ATR stop hung
off the window's extreme instead of the close.
- [Indicator-Atr.md](Indicator-Atr.md) — the volatility measure underneath.
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
@@ -0,0 +1,177 @@
# ChandeKrollStop
> Chande Kroll Stop — a two-stage ATR stop: an ATR stop off the recent
> extreme, then smoothed by taking the most extreme such stop over a
> shorter window.
## Quick reference
| Field | Value |
|-------|-------|
| Family | Volatility |
| Sub-category | Trailing stop |
| Input type | `Candle` (uses `high`, `low`, `close`) |
| Output type | `(stop_long, stop_short)` |
| Output range | unbounded (price scale) |
| Default parameters | `atr_period = 10`, `atr_multiplier = 1.0`, `stop_period = 9` (Python) |
| Warmup period | `atr_period + stop_period 1` |
| Interpretation | Smoothed long/short stop levels, less prone to single-bar whipsaw. |
## Formula
```
preliminary (window p = atr_period, x = atr_multiplier):
high_stop = highest_high(p) x · ATR(p)
low_stop = lowest_low(p) + x · ATR(p)
final (window q = stop_period):
stop_short = highest(high_stop, q)
stop_long = lowest(low_stop, q)
```
Tushar Chande and Stanley Kroll's stop runs in two stages. The first builds a
preliminary ATR stop off the recent extreme — the same idea as a
[`ChandelierExit`](Indicator-ChandelierExit.md). The second smooths it: rather
than use that preliminary stop directly, it takes the *most extreme*
preliminary stop seen over a shorter window `q`. That second pass keeps a
single unusually wide bar from yanking the stop around. The classic
configuration from *The New Technical Trader* is `ATR(10)`, multiplier `1.0`,
smoothing window `9`.
## Parameters
- `atr_period` — window for the preliminary ATR and the highest high / lowest
low (Python default `10`).
- `atr_multiplier` — how many ATRs the preliminary stop sits off the extreme
(default `1.0`).
- `stop_period` — the smoothing window `q` (default `9`).
`ChandeKrollStop::classic()` returns the `(10, 1.0, 9)` configuration.
## Inputs / Outputs
From `crates/wickra-core/src/indicators/chande_kroll_stop.rs`:
```rust
impl Indicator for ChandeKrollStop {
type Input = Candle;
type Output = ChandeKrollStopOutput; // { stop_long: f64, stop_short: f64 }
// update(&mut self, input: Candle) -> Option<ChandeKrollStopOutput>
}
```
`ChandeKrollStop` is a **candle-input** indicator (it reads `high`, `low`,
`close`). Python's streaming `update` returns a `(stop_long, stop_short)`
tuple; the batch helper returns an `(n, 2)` array with columns
`[stop_long, stop_short]`. Node's `update` returns `{ stopLong, stopShort }`
and `batch` a flat `[l0, s0, l1, s1, …]` array; WASM matches Node.
## Warmup
`ChandeKrollStop::classic().warmup_period() == 18` (`atr_period + stop_period
1`). The preliminary stop first appears on candle `atr_period`; the smoothing
window then needs `stop_period` of them.
## Edge cases
- **Two-stage warmup.** Nothing is emitted until both the preliminary window
and the smoothing window have filled.
- **Flat market.** Constant candles collapse both stages to fixed levels.
- **Reset.** `cks.reset()` clears the ATR and all four windows.
## Examples
### Rust
```rust
use wickra::{BatchExt, Candle, Indicator, ChandeKrollStop};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut cks = ChandeKrollStop::new(5, 1.0, 3)?;
// Flat market: ATR = 2, HH = 11, LL = 9.
let candles: Vec<Candle> = (0..20)
.map(|i| Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, i).unwrap())
.collect();
let out = cks.batch(&candles);
println!("{:?}", out.last().unwrap());
Ok(())
}
```
Output:
```
Some(ChandeKrollStopOutput { stop_long: 11.0, stop_short: 9.0 })
```
`high_stop = 11 1·2 = 9`, `low_stop = 9 + 1·2 = 11`; the smoothing pass over
constant values leaves `stop_short = 9` and `stop_long = 11`. This matches the
`reference_values_flat_market` test in
`crates/wickra-core/src/indicators/chande_kroll_stop.rs`.
### Python
```python
import numpy as np
import wickra as ta
cks = ta.ChandeKrollStop(5, 1.0, 3)
n = 20
high = np.full(n, 11.0)
low = np.full(n, 9.0)
close = np.full(n, 10.0)
print(cks.batch(high, low, close)[-1]) # [stop_long, stop_short]
```
Output:
```
[11. 9.]
```
### Node
```javascript
const ta = require('wickra');
const cks = new ta.ChandeKrollStop(5, 1.0, 3);
const n = 20;
const high = Array(n).fill(11), low = Array(n).fill(9), close = Array(n).fill(10);
const out = cks.batch(high, low, close);
console.log(out.slice(-2)); // [stop_long, stop_short] of the last bar
```
Output:
```
[ 11, 9 ]
```
## Interpretation
Use `stop_long` to trail a long position and `stop_short` to trail a short.
Compared with a one-stage [`ChandelierExit`](Indicator-ChandelierExit.md), the
extra smoothing window makes the Chande Kroll Stop steadier — it will not lurch
on a single wide-range bar — at the cost of reacting a little slower to a
genuine trend change.
## Common pitfalls
- **Forgetting the longer warmup.** Two stacked windows mean `atr_period +
stop_period 1` bars before the first value.
- **Confusing the labels.** `stop_short` is generally the lower level and
`stop_long` the higher — they bracket recent price, but each only applies to
its own side.
## References
Tushar Chande and Stanley Kroll's stop, from *The New Technical Trader* (1994);
the two-stage formulation here matches the common TradingView implementation.
## See also
- [Indicator-ChandelierExit.md](Indicator-ChandelierExit.md) — the one-stage
ATR stop this smooths.
- [Indicator-SuperTrend.md](Indicator-SuperTrend.md) — an ATR trailing stop
with explicit flip logic.
- [Indicator-Atr.md](Indicator-Atr.md) — the volatility measure underneath.
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
@@ -0,0 +1,166 @@
# ChandelierExit
> Chandelier Exit — an ATR trailing stop hung a fixed number of ATRs off
> the highest high (for longs) or the lowest low (for shorts) of a window.
## Quick reference
| Field | Value |
|-------|-------|
| Family | Volatility |
| Sub-category | Trailing stop |
| Input type | `Candle` (uses `high`, `low`, `close`) |
| Output type | `(long_stop, short_stop)` |
| Output range | unbounded (price scale) |
| Default parameters | `period = 22`, `multiplier = 3.0` (Python) |
| Warmup period | `period` |
| Interpretation | Long/short trailing-stop levels; a close past one exits the trade. |
## Formula
```
long_stop = highest_high(period) multiplier · ATR(period)
short_stop = lowest_low(period) + multiplier · ATR(period)
```
Chuck LeBeau's Chandelier Exit hangs the stop off the extreme of the lookback
window — like a chandelier off a ceiling — a fixed `multiplier · ATR` below the
highest high (for a long) or above the lowest low (for a short). Because the
extreme only moves favourably while a trend runs, the stop trails price up
(or down) and never loosens. A long is exited when price closes below
`long_stop`; a short when it closes above `short_stop`. The classic
configuration is a `22`-bar window with a `3.0` multiplier.
## Parameters
- `period` — the window for both the highest high / lowest low and the ATR
(Python default `22`).
- `multiplier` — how many ATRs the stop hangs off the extreme (default `3.0`).
`ChandelierExit::classic()` returns the `(22, 3.0)` configuration.
## Inputs / Outputs
From `crates/wickra-core/src/indicators/chandelier_exit.rs`:
```rust
impl Indicator for ChandelierExit {
type Input = Candle;
type Output = ChandelierExitOutput; // { long_stop: f64, short_stop: f64 }
// update(&mut self, input: Candle) -> Option<ChandelierExitOutput>
}
```
`ChandelierExit` is a **candle-input** indicator (it reads `high`, `low`,
`close`). Python's streaming `update` returns a `(long_stop, short_stop)`
tuple; the batch helper returns an `(n, 2)` array with columns
`[long_stop, short_stop]`. Node's `update` returns `{ longStop, shortStop }`
and `batch` a flat `[l0, s0, l1, s1, …]` array; WASM matches Node.
## Warmup
`ChandelierExit::classic().warmup_period() == 22`. The highest-high / lowest-low
window and the inner ATR become ready on the same candle — input index
`period 1`.
## Edge cases
- **Window bound.** `long_stop` never exceeds the window's highest high, and
`short_stop` never drops below its lowest low
(`long_stop_below_highest_short_stop_above_lowest` pins this).
- **Flat market.** Constant candles give constant `ATR` and equal extremes, so
both stops sit a fixed `multiplier · ATR` from the price.
- **Reset.** `ce.reset()` clears the ATR and both extreme windows.
## Examples
### Rust
```rust
use wickra::{BatchExt, Candle, Indicator, ChandelierExit};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut ce = ChandelierExit::new(5, 3.0)?;
// Flat market: ATR = 2, HH = 11, LL = 9.
let candles: Vec<Candle> = (0..20)
.map(|i| Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, i).unwrap())
.collect();
let out = ce.batch(&candles);
println!("{:?}", out.last().unwrap());
Ok(())
}
```
Output:
```
Some(ChandelierExitOutput { long_stop: 5.0, short_stop: 15.0 })
```
`long_stop = 11 3·2 = 5`, `short_stop = 9 + 3·2 = 15`. This matches the
`reference_values_flat_market` test in
`crates/wickra-core/src/indicators/chandelier_exit.rs`.
### Python
```python
import numpy as np
import wickra as ta
ce = ta.ChandelierExit(5, 3.0)
n = 20
high = np.full(n, 11.0)
low = np.full(n, 9.0)
close = np.full(n, 10.0)
print(ce.batch(high, low, close)[-1]) # [long_stop, short_stop]
```
Output:
```
[ 5. 15.]
```
### Node
```javascript
const ta = require('wickra');
const ce = new ta.ChandelierExit(5, 3.0);
const n = 20;
const high = Array(n).fill(11), low = Array(n).fill(9), close = Array(n).fill(10);
const out = ce.batch(high, low, close);
console.log(out.slice(-2)); // [long_stop, short_stop] of the last bar
```
Output:
```
[ 5, 15 ]
```
## Interpretation
While long, watch `long_stop`: it climbs as new highs print and never falls,
so a close beneath it is a disciplined exit. While short, `short_stop` is the
mirror. The `3.0` multiplier is wide enough to ride a trend through normal
pullbacks; tightening it exits sooner at the cost of more whipsaws.
## Common pitfalls
- **Using the wrong stop for the position.** `long_stop` only applies to
longs, `short_stop` only to shorts — they are not a channel.
- **Feeding it scalar prices.** It needs the full `high`/`low`/`close` bar.
## References
Chuck LeBeau's Chandelier Exit; the highest-high-minus-ATR formulation here
matches the standard definition.
## See also
- [Indicator-SuperTrend.md](Indicator-SuperTrend.md) — an ATR trailing stop
with explicit flip logic and a single line.
- [Indicator-ChandeKrollStop.md](Indicator-ChandeKrollStop.md) — a two-stage
ATR stop that smooths the preliminary level.
- [Indicator-Atr.md](Indicator-Atr.md) — the volatility measure underneath.
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
@@ -0,0 +1,174 @@
# SuperTrend
> SuperTrend — an ATR-banded trailing stop that flips sides when price
> closes through the band, reporting both the stop level and the trend
> direction.
## Quick reference
| Field | Value |
|-------|-------|
| Family | Volatility |
| Sub-category | Trailing stop |
| Input type | `Candle` (uses `high`, `low`, `close`) |
| Output type | `(value, direction)` |
| Output range | `value`: unbounded (price scale); `direction`: `1.0` or `+1.0` |
| Default parameters | `atr_period = 10`, `multiplier = 3.0` (Python) |
| Warmup period | `atr_period` |
| Interpretation | Trend-following stop; a direction flip marks a trend change. |
## Formula
```
hl2 = (high + low) / 2
basic_upper = hl2 + multiplier · ATR
basic_lower = hl2 multiplier · ATR
final_upper = basic_upper if basic_upper < prev_final_upper or prev_close > prev_final_upper
else prev_final_upper
final_lower = basic_lower if basic_lower > prev_final_lower or prev_close < prev_final_lower
else prev_final_lower
downtrend: stay down while close <= final_upper, else flip up
uptrend: stay up while close >= final_lower, else flip down
SuperTrend = final_lower in an uptrend, final_upper in a downtrend
```
The two final bands ratchet — the upper band only moves down, the lower band
only moves up — until price closes through the active one. That close flips
the trend and hands the trailing-stop role to the opposite band. The result is
a single line that sits below price in an uptrend and above it in a downtrend,
plus a `direction` flag (`+1.0` / `-1.0`) that names which regime you are in.
## Parameters
- `atr_period` — the ATR lookback (Python default `10`).
- `multiplier` — how many ATRs wide the bands sit (Python default `3.0`).
`SuperTrend::classic()` returns Wilder's `(10, 3.0)` configuration.
## Inputs / Outputs
From `crates/wickra-core/src/indicators/super_trend.rs`:
```rust
impl Indicator for SuperTrend {
type Input = Candle;
type Output = SuperTrendOutput; // { value: f64, direction: f64 }
// update(&mut self, input: Candle) -> Option<SuperTrendOutput>
}
```
`SuperTrend` is a **candle-input** indicator (it reads `high`, `low`, `close`).
Python's streaming `update` returns a `(value, direction)` tuple; the batch
helper returns an `(n, 2)` array with columns `[value, direction]`. Node's
`update` returns `{ value, direction }` and `batch` a flat `[v0, d0, v1, d1, …]`
array; WASM matches Node.
## Warmup
`SuperTrend::classic().warmup_period() == 10`. The first value lands once the
inner ATR is ready, on input index `atr_period 1`. The first ATR-ready bar
seeds the trend as up; the flip logic corrects it within a few bars if the
market is actually falling.
## Edge cases
- **Seed direction.** The first emitted bar is always `direction = +1.0`; a
genuine downtrend flips it within a handful of bars.
- **Flat market.** Constant candles give a constant ATR, so both bands and the
line are flat and the trend never flips.
- **Reset.** `st.reset()` clears the ATR and the carried band state.
## Examples
### Rust
```rust
use wickra::{BatchExt, Candle, Indicator, SuperTrend};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut st = SuperTrend::new(5, 3.0)?;
// Flat market: ATR = 2, hl2 = 10, lower band = 10 - 3·2 = 4.
let candles: Vec<Candle> = (0..20)
.map(|i| Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, i).unwrap())
.collect();
let out = st.batch(&candles);
println!("{:?}", out.last().unwrap());
Ok(())
}
```
Output:
```
Some(SuperTrendOutput { value: 4.0, direction: 1.0 })
```
On a flat market the seeded uptrend never flips and the line holds at the
lower band, `4.0`.
### Python
```python
import numpy as np
import wickra as ta
st = ta.SuperTrend(5, 3.0)
n = 20
high = np.full(n, 11.0)
low = np.full(n, 9.0)
close = np.full(n, 10.0)
print(st.batch(high, low, close)[-1]) # [value, direction]
```
Output:
```
[4. 1.]
```
### Node
```javascript
const ta = require('wickra');
const st = new ta.SuperTrend(5, 3.0);
const n = 20;
const high = Array(n).fill(11), low = Array(n).fill(9), close = Array(n).fill(10);
const out = st.batch(high, low, close);
console.log(out.slice(-2)); // [value, direction] of the last bar
```
Output:
```
[ 4, 1 ]
```
## Interpretation
`SuperTrend` is used as a stop-and-reverse system: stay long while
`direction == +1` and the line trails below price, flip to short the bar the
`direction` turns `-1` and the line jumps above price. A larger `multiplier`
widens the bands — fewer whipsaws, later flips; a smaller one flips sooner.
The line itself doubles as a concrete stop-loss level.
## Common pitfalls
- **Expecting an exact flip bar.** The seed bar is always an uptrend; on
genuinely falling data the flip lands a few bars in.
- **Reading `value` without `direction`.** The line means "support" in an
uptrend and "resistance" in a downtrend — `direction` tells you which.
## References
The SuperTrend trailing stop; the final-band ratchet formulation here matches
the widely used TradingView / Olivier Seban definition.
## See also
- [Indicator-Psar.md](Indicator-Psar.md) — Wilder's parabolic stop-and-reverse.
- [Indicator-AtrTrailingStop.md](Indicator-AtrTrailingStop.md) — a plain
ATR trailing stop without the band ratchet.
- [Indicator-Atr.md](Indicator-Atr.md) — the volatility measure underneath.
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.