F13c: restructure the indicator catalogue into eight families
The original taxonomy was four classical families plus a statistics group, with the F1-F12 expansion slotted in as sub-categories. This regroups the whole 71-indicator catalogue into eight top-level families, each with at least five members: Moving Averages (12), Momentum Oscillators (13), Trend & Directional (9), Price Oscillators (5), Volatility & Bands (12), Trailing Stops (5), Volume (9), Price Statistics (7). - Wiki: docs/wiki/indicators/ reorganised into eight family folders; all 71 indicator pages moved with `git mv`. Every internal cross-link is normalised to `../<family>/Indicator-X.md`, each page's `Family` field is set to its new family, and two pre-existing `../Indicator-Chaining.md` links (should have been `../../`) are corrected. A link check confirms every relative wiki link resolves. - Indicators-Overview.md fully rewritten around the eight families; Home.md indicator reference and the README family table follow suit. - Warmup-Periods.md gains the eight F13 indicators; CHANGELOG records the 46-indicator expansion (25 -> 71) and the eight-family taxonomy. - Tests: Node indicators.test.js and Python test_new_indicators.py cover all eight new indicators (Node 91/91, Python 117/117 green). cargo fmt + clippy (core/wickra/data/wasm/node) clean; 508 core tests, 25 data tests and 74 doctests green.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# Donchian Channels
|
||||
|
||||
> The unsmoothed price-extreme envelope: highest high and lowest low over a
|
||||
> rolling window, with the mid-band defined as their average. Breakouts of
|
||||
> the Donchian channel are the foundation of the Turtle trading rules.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Item | Value |
|
||||
|---------------------|--------------------------------------------------------------------|
|
||||
| Family | Volatility & Bands |
|
||||
| Input type | `Candle` (uses `high` and `low`) |
|
||||
| Output type | `DonchianOutput { upper: f64, middle: f64, lower: f64 }` |
|
||||
| Output range | unbounded; `lower ≤ middle ≤ upper` |
|
||||
| Default parameters | `period = 20` |
|
||||
| Warmup period | `period` (20 for defaults) |
|
||||
| Interpretation | breakout boundary; channel touches are tradable events |
|
||||
|
||||
## Formula
|
||||
|
||||
For a lookback of `period` candles:
|
||||
|
||||
```
|
||||
upper_t = max( high_t, high_{t-1}, …, high_{t-period+1} )
|
||||
lower_t = min( low_t, low_{t-1}, …, low_{t-period+1} )
|
||||
middle_t = (upper_t + lower_t) / 2
|
||||
```
|
||||
|
||||
`crates/wickra-core/src/indicators/donchian.rs:58-72` computes both
|
||||
extrema by folding over the in-window candles each tick; this is O(n)
|
||||
per update in the period size and O(1) in the data length.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Name | Type | Default | Constraint | Source |
|
||||
|----------|---------|---------|------------|-----------------------------------------|
|
||||
| `period` | `usize` | `20` | `> 0` | `Donchian::new` (`donchian.rs:30`) |
|
||||
|
||||
Python default from `#[pyo3(signature = (period=20))]` in
|
||||
`bindings/python/src/lib.rs`. `period == 0` returns `Error::PeriodZero`.
|
||||
|
||||
## Inputs / Outputs
|
||||
|
||||
```rust
|
||||
impl Indicator for Donchian {
|
||||
type Input = Candle;
|
||||
type Output = DonchianOutput;
|
||||
fn update(&mut self, candle: Candle) -> Option<DonchianOutput>;
|
||||
}
|
||||
|
||||
pub struct DonchianOutput { pub upper: f64, pub middle: f64, pub lower: f64 }
|
||||
```
|
||||
|
||||
- **Python streaming.** Returns `(upper, middle, lower)` tuple or `None`.
|
||||
- **Python batch.** `Donchian.batch(high, low)` returns a 2-D
|
||||
`np.ndarray` of shape `(n, 3)` with columns `[upper, middle, lower]`;
|
||||
warmup rows are `NaN` across all three columns. (`close` is not
|
||||
required.)
|
||||
- **Node streaming.** Not exposed — the Node binding ships only the
|
||||
`batch` form for `Donchian`.
|
||||
- **Node batch.** `donchian.batch(high, low)` returns a flat
|
||||
`Array<number>` of length `n * 3` interleaved per row:
|
||||
`[u0, m0, l0, u1, m1, l1, …]`.
|
||||
|
||||
## Warmup
|
||||
|
||||
`warmup_period() == period`. The first `period - 1` candles return
|
||||
`None`; the `period`-th candle emits the first envelope. Verified for
|
||||
`period = 3`: the first non-`None` output is at index `2` (the 3rd
|
||||
candle).
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Flat market (HH == LL).** When every candle in the window has
|
||||
identical highs and identical lows, `upper == lower` (and therefore
|
||||
`middle == upper == lower`). The pinned test
|
||||
`flat_market_yields_equal_bands` covers this.
|
||||
- **Single extreme candle.** A lone wick at the edge of the window sets
|
||||
the boundary until it scrolls out. Donchian therefore reacts in
|
||||
step-functions, not smoothly — a new all-time high inside the window
|
||||
immediately moves the upper band; a single bar later, that high
|
||||
remains the boundary unless an even higher print occurs.
|
||||
- **NaN / infinity.** `Candle::new` rejects non-finite OHLC values
|
||||
before they can reach Donchian.
|
||||
- **Reset.** `reset()` clears the candle window; the configured
|
||||
`period` is preserved.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
use wickra::{BatchExt, Candle, Donchian, Indicator};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let candles = vec![
|
||||
Candle::new(10.0, 11.0, 9.0, 10.5, 1.0, 0)?,
|
||||
Candle::new(10.5, 12.0, 10.0, 11.5, 1.0, 0)?,
|
||||
Candle::new(11.5, 13.0, 11.0, 12.5, 1.0, 0)?,
|
||||
Candle::new(12.5, 14.0, 12.0, 13.5, 1.0, 0)?,
|
||||
Candle::new(13.5, 15.0, 13.0, 14.5, 1.0, 0)?,
|
||||
];
|
||||
let mut d = Donchian::new(3)?;
|
||||
for (i, v) in d.batch(&candles).into_iter().enumerate() {
|
||||
println!("i={i} -> {:?}", v);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
i=0 -> None
|
||||
i=1 -> None
|
||||
i=2 -> Some(DonchianOutput { upper: 13.0, middle: 11.0, lower: 9.0 })
|
||||
i=3 -> Some(DonchianOutput { upper: 14.0, middle: 12.0, lower: 10.0 })
|
||||
i=4 -> Some(DonchianOutput { upper: 15.0, middle: 13.0, lower: 11.0 })
|
||||
```
|
||||
|
||||
At `i = 2` the window contains highs `[11, 12, 13]` and lows `[9, 10, 11]`,
|
||||
so `upper = 13`, `lower = 9`, `middle = 11`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
|
||||
d = ta.Donchian(3)
|
||||
h = np.array([11.0, 12.0, 13.0, 14.0, 15.0])
|
||||
l = np.array([ 9.0, 10.0, 11.0, 12.0, 13.0])
|
||||
print(d.batch(h, l))
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
[[nan nan nan]
|
||||
[nan nan nan]
|
||||
[13. 11. 9.]
|
||||
[14. 12. 10.]
|
||||
[15. 13. 11.]]
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
```js
|
||||
const w = require('wickra');
|
||||
|
||||
const d = new w.Donchian(3);
|
||||
const flat = d.batch(
|
||||
[11, 12, 13, 14, 15],
|
||||
[ 9, 10, 11, 12, 13],
|
||||
);
|
||||
console.log('length:', flat.length);
|
||||
console.log('row 2 [upper, middle, lower]:', flat.slice(6, 9));
|
||||
console.log('row 4 [upper, middle, lower]:', flat.slice(12, 15));
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
length: 15
|
||||
row 2 [upper, middle, lower]: [ 13, 11, 9 ]
|
||||
row 4 [upper, middle, lower]: [ 15, 13, 11 ]
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
- **Breakouts.** The original Turtle Trading rules (Dennis / Eckhardt,
|
||||
early 1980s) buy on a 20-day Donchian upper-band breach and sell on
|
||||
a 10-day lower-band breach. The modern descendant is the "channel
|
||||
breakout" family of trend-following systems.
|
||||
- **Mean reversion.** A small minority of systems take the bands as
|
||||
fade levels; this works on range-bound assets and fails dramatically
|
||||
in trends — the inverse of breakout systems.
|
||||
- **Volatility proxy.** Channel width `upper - lower` is a simple
|
||||
volatility proxy that requires no smoothing and no parameter tuning
|
||||
beyond the lookback length.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Stale extreme.** A single shock high from `period` candles ago
|
||||
keeps the upper band elevated even when current prices have fallen
|
||||
back to normal. Watch for the "channel drop" event when that high
|
||||
scrolls out of the window — the upper band will step down sharply
|
||||
in a single bar.
|
||||
- **No close required.** Donchian only uses high/low. Feeding it a
|
||||
close-only series (with high = low = close) collapses it into an
|
||||
envelope of close extremes, which is a much noisier signal than
|
||||
the canonical high/low form. The Python `batch` accepts only
|
||||
`(high, low)` for exactly this reason.
|
||||
- **Flat range collapse.** On a truly flat instrument the channel
|
||||
collapses to a line (`upper == middle == lower`); downstream code
|
||||
that divides by `upper - lower` (e.g. computing channel position)
|
||||
must handle this division-by-zero case explicitly.
|
||||
|
||||
## References
|
||||
|
||||
- Richard Donchian published the 4-week channel rule in the early
|
||||
1960s as part of his broader trend-following work.
|
||||
- Curtis Faith, *Way of the Turtle*, McGraw-Hill, 2007, documents
|
||||
the 20/10-day Donchian variant that defined the Turtle program.
|
||||
|
||||
## See also
|
||||
|
||||
- [Bollinger Bands](../volatility-bands/Indicator-BollingerBands.md) — envelope shaped by
|
||||
stddev rather than rolling extrema.
|
||||
- [Keltner Channels](../volatility-bands/Indicator-Keltner.md) — envelope shaped by ATR
|
||||
around an EMA centerline.
|
||||
- [PSAR](../trailing-stops/Indicator-Psar.md) — alternative trailing-stop construction
|
||||
for breakout systems.
|
||||
Reference in New Issue
Block a user