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.
206 lines
6.3 KiB
Markdown
206 lines
6.3 KiB
Markdown
# Aroon
|
||
|
||
> Tushar Chande's Aroon indicator — tracks the bars-since-highest-high
|
||
> and bars-since-lowest-low inside a `period + 1`-bar window, reported
|
||
> as percentages.
|
||
|
||
## Quick reference
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| Family | Trend & Directional |
|
||
| Input type | `Candle` |
|
||
| Output type | `AroonOutput { up, down }` |
|
||
| Output range | `up, down ∈ [0, 100]` |
|
||
| Default parameters | `period = 14` (Python) |
|
||
| Warmup period | `period + 1` (15 for `period = 14`) |
|
||
| Interpretation | `up > 70 && down < 30` strong uptrend (mirror for downtrend); crossovers as turn signals |
|
||
|
||
## Formula
|
||
|
||
Scan the rolling `period + 1`-bar window for the position of the highest
|
||
high and the position of the lowest low (with `0 = oldest`,
|
||
`period = newest`):
|
||
|
||
```
|
||
hh_idx_t = argmax_{i in 0..period} high_{t-period+i}
|
||
ll_idx_t = argmin_{i in 0..period} low_{t-period+i}
|
||
|
||
up_t = 100 · hh_idx_t / period
|
||
down_t = 100 · ll_idx_t / period
|
||
```
|
||
|
||
When the highest high lands on the most-recent bar (`hh_idx == period`),
|
||
`up == 100`; when it lands on the oldest bar in the window, `up == 0`.
|
||
The same holds for `down`.
|
||
|
||
In Wickra's implementation the scan uses `>=` / `<=`, so ties go to the
|
||
*latest* matching bar — which is why a perfectly flat window produces
|
||
`up = down = 100` rather than `0` (the latest bar is always tied with
|
||
the oldest).
|
||
|
||
## Parameters
|
||
|
||
| Name | Type | Default (Python) | Valid range | Description |
|
||
|------|------|------------------|-------------|-------------|
|
||
| `period` | `usize` | `14` | `>= 1` | Lookback length. The internal window holds `period + 1` candles. |
|
||
|
||
`Aroon::new(0)` returns `Error::PeriodZero`.
|
||
|
||
## Inputs / Outputs
|
||
|
||
From `impl Indicator for Aroon`:
|
||
|
||
```rust
|
||
type Input = Candle;
|
||
type Output = AroonOutput;
|
||
fn update(&mut self, candle: Candle) -> Option<AroonOutput>;
|
||
```
|
||
|
||
`AroonOutput`:
|
||
|
||
| Field | Description |
|
||
|-------|-------------|
|
||
| `up` | `100 · bars_since_oldest_HH / period`, in `[0, 100]`. High = recent new high. |
|
||
| `down` | `100 · bars_since_oldest_LL / period`, in `[0, 100]`. High = recent new low. |
|
||
|
||
Python's `Aroon.batch(high, low)` returns a `(n, 2)` `float64` array
|
||
with columns `[up, down]`; warmup rows are `[NaN, NaN]`. Streaming
|
||
`update(candle)` returns a `(up, down)` tuple or `None`.
|
||
|
||
Node's `Aroon.batch(high, low)` returns a flat `number[]` of length
|
||
`n * 2`, interleaved `[up_0, down_0, up_1, down_1, …]`. Only `batch`
|
||
is exposed on the Node binding.
|
||
|
||
## Warmup
|
||
|
||
`warmup_period()` returns `period + 1`. Aroon scans `period + 1` bars
|
||
to find "bars since highest high" (which ranges over `0..period`), so
|
||
the indicator is not ready until exactly `period + 1` candles have
|
||
arrived. This is the same off-by-one as RSI and ROC, but for a
|
||
window-position reason rather than a diff reason.
|
||
|
||
## Edge cases
|
||
|
||
- **Pure uptrend.** Every new candle is a new high — `hh_idx` is always
|
||
the latest position, `up == 100`. The lowest low is the oldest
|
||
candle in the window, `down == 0`. Tests `pure_uptrend_aroon_up_100`
|
||
pin this.
|
||
- **Constant input.** Every candle's high is equal to every other
|
||
candle's high. The `>=` tiebreak in the scan means the most-recent
|
||
candle always wins both the HH and LL positions — both `up` and
|
||
`down` end up at `100`. (Be careful: this is *not* a neutral
|
||
reading; it is an artefact of the tiebreak rule.)
|
||
- **Reset.** `reset()` clears the candle buffer; the next `period + 1`
|
||
updates return `None`.
|
||
|
||
## Examples
|
||
|
||
### Rust
|
||
|
||
```rust
|
||
use wickra::{Aroon, BatchExt, Candle, Indicator};
|
||
|
||
let candles: Vec<Candle> = (1..=15)
|
||
.map(|i| Candle::new(i as f64, i as f64 + 1.0, i as f64 - 1.0, i as f64, 1.0, 0).unwrap())
|
||
.collect();
|
||
let mut aroon = Aroon::new(14)?;
|
||
let out = aroon.batch(&candles);
|
||
let v = out[14].unwrap();
|
||
println!("uptrend row 14 up={} down={}", v.up, v.down);
|
||
# Ok::<(), wickra::Error>(())
|
||
```
|
||
|
||
Verified output:
|
||
|
||
```
|
||
uptrend row 14 up=100 down=0
|
||
```
|
||
|
||
### Python
|
||
|
||
```python
|
||
import numpy as np
|
||
import wickra as ta
|
||
|
||
i = np.arange(1, 16, dtype=float)
|
||
high = i + 1.0
|
||
low = i - 1.0
|
||
aroon = ta.Aroon(14)
|
||
out = aroon.batch(high, low)
|
||
print('warmup:', aroon.warmup_period())
|
||
print('shape :', out.shape)
|
||
print('row 14:', out[14])
|
||
```
|
||
|
||
Verified output:
|
||
|
||
```
|
||
warmup: 15
|
||
shape : (15, 2)
|
||
row 14: [100. 0.]
|
||
```
|
||
|
||
### Node
|
||
|
||
```javascript
|
||
const wickra = require('wickra');
|
||
|
||
const high = [], low = [];
|
||
for (let i = 1; i <= 15; i++) {
|
||
high.push(i + 1);
|
||
low.push(i - 1);
|
||
}
|
||
const a = new wickra.Aroon(14);
|
||
const out = a.batch(high, low);
|
||
console.log('len :', out.length);
|
||
console.log('row 14:', { up: out[14 * 2], down: out[14 * 2 + 1] });
|
||
```
|
||
|
||
Verified output:
|
||
|
||
```
|
||
len : 30
|
||
row 14: { up: 100, down: 0 }
|
||
```
|
||
|
||
## Interpretation
|
||
|
||
- **Strong trend bands.** `up > 70` with `down < 30` indicates a strong
|
||
uptrend (new highs are recent, new lows are old); mirror for a
|
||
downtrend.
|
||
- **Crossover.** `up` crossing above `down` is a bullish trend-shift
|
||
signal; the mirror is bearish. Crossovers near `50/50` are weak
|
||
(the window has no clear leader); crossovers from `0`/`100` extremes
|
||
are strong.
|
||
- **Consolidation.** Both lines wandering near `50` means neither
|
||
recent highs nor recent lows are dominating — typical of a
|
||
range-bound market.
|
||
|
||
## Common pitfalls
|
||
|
||
- **Constant input gives `up == down == 100`, not `0` or `50`.** The
|
||
`>=` / `<=` tiebreak in the scan rewards the most-recent candle.
|
||
Treat constant or near-constant windows as a degenerate case; a
|
||
reading of `(100, 100)` is *not* a strong trend in both directions
|
||
— it is "no information".
|
||
- **`period + 1` warmup, not `period`.** Same off-by-one trap as RSI:
|
||
the indicator looks at a window of size `period + 1` so that
|
||
`bars_since_high` can range from `0` to `period`. Indexing your
|
||
output array as if it were ready at the `period`-th input gives you
|
||
one `NaN` / `None` row at the start you didn't expect.
|
||
|
||
## References
|
||
|
||
- Tushar Chande, "A New Tool for Technical Traders: The Aroon
|
||
Indicator", *Technical Analysis of Stocks & Commodities*, September
|
||
1995 — the original publication.
|
||
|
||
## See also
|
||
|
||
- [Indicator: Adx](../trend-directional/Indicator-Adx.md) — alternative trend-strength
|
||
measure with explicit `+DI` / `−DI` direction.
|
||
- [Indicator: Stochastic](../momentum-oscillators/Indicator-Stochastic.md) — also range-window
|
||
based, but reports close position rather than extremum age.
|
||
- [Warmup Periods](../../Warmup-Periods.md) — the `period + 1` family.
|