F6: add Aroon Oscillator, Vortex and Mass Index
Completes the F6 family (Trend strength) end to end: - Rust core: aroon_oscillator.rs (AroonUp - AroonDown, one-line trend gauge), vortex.rs (Vortex Indicator VI+/VI- with the VortexOutput struct), mass_index.rs (Dorsey's range-expansion sum of the EMA-of-range ratio). Each with a full Indicator impl, runnable doctest and reference / saturation / warmup / reset / batch==streaming tests. - Python: PyAroonOscillator / PyVortex / PyMassIndex PyO3 classes + module registration + .pyi stubs (defaults Aroon=14, Vortex=14, MassIndex=(9,25)). - Node: explicit AroonOscillatorNode, VortexNode (with VortexValue object) and MassIndexNode; index.d.ts and index.js updated. - WASM: WasmAroonOscillator, WasmVortex, WasmMassIndex. - Wiki: Indicator-AroonOscillator/Vortex/MassIndex.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 320 core tests, 25 data tests and 45 doctests green.
This commit is contained in:
@@ -107,6 +107,9 @@ Rust / Python / Node examples. They are grouped by family, mirroring the
|
||||
- [Indicator-Ppo.md](indicators/momentum/Indicator-Ppo.md)
|
||||
- [Indicator-Dpo.md](indicators/momentum/Indicator-Dpo.md)
|
||||
- [Indicator-Coppock.md](indicators/momentum/Indicator-Coppock.md)
|
||||
- [Indicator-AroonOscillator.md](indicators/momentum/Indicator-AroonOscillator.md)
|
||||
- [Indicator-Vortex.md](indicators/momentum/Indicator-Vortex.md)
|
||||
- [Indicator-MassIndex.md](indicators/momentum/Indicator-MassIndex.md)
|
||||
|
||||
**Volatility** — envelope width and per-bar dispersion measures.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Indicators Overview
|
||||
|
||||
Wickra ships 39 indicators, organised in source under the four classical
|
||||
Wickra ships 42 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
|
||||
@@ -112,6 +112,9 @@ Centered on zero or driven by raw price differences; no fixed cap.
|
||||
| Indicator | One-liner | Input | Output | Range | Defaults | Warmup | Deep dive |
|
||||
|-----------|-----------|-------|--------|-------|----------|--------|-----------|
|
||||
| `Adx` | Wilder's directional system: `+DI`, `−DI` (each `[0, 100]`) and `ADX` trend-strength index. | `Candle` | `(plus_di, minus_di, adx)` | each in `[0, 100]` | `period = 14` (Python) | `2·period` | [Indicator-Adx.md](indicators/momentum/Indicator-Adx.md) |
|
||||
| `AroonOscillator` | `AroonUp − AroonDown`; the two Aroon lines as one trend gauge. | `Candle` | `f64` | `[−100, 100]` | `period = 14` (Python) | `period + 1` | [Indicator-AroonOscillator.md](indicators/momentum/Indicator-AroonOscillator.md) |
|
||||
| `Vortex` | Vortex Indicator `VI+` / `VI−`; crossings mark trend onset. | `Candle` | `(plus, minus)` | each `>= 0` | `period = 14` (Python) | `period + 1` | [Indicator-Vortex.md](indicators/momentum/Indicator-Vortex.md) |
|
||||
| `MassIndex` | Dorsey's range-expansion sum of the EMA-of-range ratio. | `Candle` | `f64` | `> 0` (around `sum_period`) | `(ema_period=9, sum_period=25)` (Python) | `2·ema_period + sum_period − 2` | [Indicator-MassIndex.md](indicators/momentum/Indicator-MassIndex.md) |
|
||||
|
||||
## Volatility
|
||||
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# AroonOscillator
|
||||
|
||||
> Aroon Oscillator — the single-line difference `AroonUp − AroonDown`,
|
||||
> condensing the two Aroon lines into one trend gauge.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Family | Momentum (trend strength) |
|
||||
| Sub-category | Bounded oscillators |
|
||||
| Input type | `Candle` (uses `high`, `low`) |
|
||||
| Output type | `f64` |
|
||||
| Output range | `[−100, 100]` |
|
||||
| Default parameters | `period = 14` (Python) |
|
||||
| Warmup period | `period + 1` |
|
||||
| Interpretation | Positive = up-trend, negative = down-trend, near zero = range. |
|
||||
|
||||
## Formula
|
||||
|
||||
```
|
||||
AroonOscillator = AroonUp − AroonDown
|
||||
```
|
||||
|
||||
where [`Aroon`](Indicator-Aroon.md) reports two `[0, 100]` lines measuring
|
||||
how recently the window's highest high and lowest low occurred. Their
|
||||
difference lives in `[−100, 100]`: strongly positive means the most recent
|
||||
high is much fresher than the most recent low (an up-trend); strongly
|
||||
negative is the mirror image; near zero means neither extreme is recent.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Name | Type | Default | Valid range | Description |
|
||||
|----------|---------|---------------|-------------|-------------|
|
||||
| `period` | `usize` | `14` (Python) | `>= 1` | Aroon lookback window. `0` errors with `Error::PeriodZero`. |
|
||||
|
||||
The Python binding defaults `period` to `14`.
|
||||
|
||||
## Inputs / Outputs
|
||||
|
||||
From `crates/wickra-core/src/indicators/aroon_oscillator.rs`:
|
||||
|
||||
```rust
|
||||
impl Indicator for AroonOscillator {
|
||||
type Input = Candle;
|
||||
type Output = f64;
|
||||
// update(&mut self, input: Candle) -> Option<f64>
|
||||
}
|
||||
```
|
||||
|
||||
`AroonOscillator` is a **candle-input** indicator: it reads `high` and
|
||||
`low`. In Python the streaming `update` accepts a 6-tuple or a dict; the
|
||||
batch helper takes `high` and `low` numpy arrays. Node and WASM expose
|
||||
`update(high, low)` and `batch(high, low)`.
|
||||
|
||||
## Warmup
|
||||
|
||||
`AroonOscillator::new(period).warmup_period() == period + 1` — identical
|
||||
to the underlying `Aroon`, which needs a `period + 1`-bar window before
|
||||
the first reading.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Pure trend.** A series of fresh highs gives `AroonUp = 100`,
|
||||
`AroonDown = 0`, so the oscillator is `+100`; a series of fresh lows is
|
||||
`−100` (`pure_uptrend_yields_plus_100` /
|
||||
`pure_downtrend_yields_minus_100` pin this).
|
||||
- **Bounds.** The output is always within `[−100, 100]`
|
||||
(`output_stays_within_minus_100_and_100` pins this).
|
||||
- **Candle validation.** `Candle::new` rejects invalid bars before
|
||||
`update` ever sees them.
|
||||
- **Reset.** `osc.reset()` clears the underlying Aroon window.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
use wickra::{BatchExt, Candle, Indicator, AroonOscillator};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut osc = AroonOscillator::new(5)?;
|
||||
// 30 bars, each a fresh high.
|
||||
let candles: Vec<Candle> = (0..30)
|
||||
.map(|i| {
|
||||
let p = 100.0 + f64::from(i);
|
||||
Candle::new(p, p + 1.0, p - 1.0, p, 1.0, i64::from(i)).unwrap()
|
||||
})
|
||||
.collect();
|
||||
let out = osc.batch(&candles);
|
||||
println!("last = {:?}", out.last().unwrap());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
last = Some(100.0)
|
||||
```
|
||||
|
||||
Every bar is a fresh high and never a fresh low, so the oscillator pins at
|
||||
`+100`. This matches the `pure_uptrend_yields_plus_100` test in
|
||||
`crates/wickra-core/src/indicators/aroon_oscillator.rs`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
|
||||
osc = ta.AroonOscillator(14)
|
||||
high = np.arange(100.0, 140.0)
|
||||
low = high - 2.0
|
||||
print(osc.batch(high, low)[-1]) # steady uptrend -> 100
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
100.0
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
```javascript
|
||||
const ta = require('wickra');
|
||||
const osc = new ta.AroonOscillator(14);
|
||||
const high = Array.from({ length: 40 }, (_, i) => 100 + i);
|
||||
const low = high.map((h) => h - 2);
|
||||
console.log(osc.batch(high, low).at(-1)); // 100
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
`AroonOscillator` is a compact trend gauge. The two canonical reads are
|
||||
the zero-line cross (`AroonUp` overtaking `AroonDown` or vice versa — a
|
||||
trend change) and the magnitude (values pinned near `±100` confirm a
|
||||
strong, uninterrupted trend; values oscillating near zero confirm a
|
||||
range). Use it where the two-line `Aroon` is more detail than you need.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Feeding it scalar prices.** It needs `high`/`low`; it takes a
|
||||
`Candle`, not an `f64`.
|
||||
- **Expecting the `[0, 100]` Aroon scale.** The oscillator is signed and
|
||||
spans `[−100, 100]`.
|
||||
|
||||
## References
|
||||
|
||||
Tushar Chande's Aroon system (1995); the oscillator is the standard
|
||||
`AroonUp − AroonDown` difference.
|
||||
|
||||
## See also
|
||||
|
||||
- [Indicator-Aroon.md](Indicator-Aroon.md) — the two-line indicator this
|
||||
collapses.
|
||||
- [Indicator-Adx.md](Indicator-Adx.md) — another trend-strength gauge.
|
||||
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
|
||||
@@ -0,0 +1,173 @@
|
||||
# MassIndex
|
||||
|
||||
> Mass Index — Donald Dorsey's range-expansion indicator: it watches the
|
||||
> high–low range widen and contract to anticipate reversals.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Family | Momentum (trend strength) |
|
||||
| Sub-category | Range expansion |
|
||||
| Input type | `Candle` (uses `high`, `low`) |
|
||||
| Output type | `f64` |
|
||||
| Output range | `> 0`, oscillates around `sum_period` |
|
||||
| Default parameters | `(ema_period = 9, sum_period = 25)` (Python) |
|
||||
| Warmup period | `2·ema_period + sum_period − 2` |
|
||||
| Interpretation | A rise above `27` then fall below `26.5` flags a reversal. |
|
||||
|
||||
## Formula
|
||||
|
||||
```
|
||||
range_t = high_t − low_t
|
||||
single_t = EMA(range, ema_period)_t
|
||||
double_t = EMA(single, ema_period)_t
|
||||
ratio_t = single_t / double_t
|
||||
MassIndex = Σ ratio over sum_period
|
||||
```
|
||||
|
||||
The Mass Index ignores direction entirely — it tracks **volatility shape**.
|
||||
When the high–low range widens, the single EMA pulls ahead of the double
|
||||
EMA, the ratio climbs above `1`, and the windowed sum rises. Dorsey's
|
||||
"reversal bulge" is the classic pattern: the Mass Index rising above `27`
|
||||
and then falling back below `26.5` warns that a range expansion is about
|
||||
to resolve — often into a trend reversal.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Name | Type | Default | Valid range | Description |
|
||||
|--------------|---------|---------------|-------------|-------------|
|
||||
| `ema_period` | `usize` | `9` (Python) | `>= 1` | Period of both EMAs in the cascade. `0` errors with `Error::PeriodZero`. |
|
||||
| `sum_period` | `usize` | `25` (Python) | `>= 1` | Length of the summation window. |
|
||||
|
||||
The Python binding defaults the pair to `(9, 25)`. The `periods` property
|
||||
returns `(ema_period, sum_period)`.
|
||||
|
||||
## Inputs / Outputs
|
||||
|
||||
From `crates/wickra-core/src/indicators/mass_index.rs`:
|
||||
|
||||
```rust
|
||||
impl Indicator for MassIndex {
|
||||
type Input = Candle;
|
||||
type Output = f64;
|
||||
// update(&mut self, input: Candle) -> Option<f64>
|
||||
}
|
||||
```
|
||||
|
||||
`MassIndex` is a **candle-input** indicator: it reads `high` and `low`. In
|
||||
Python the streaming `update` accepts a 6-tuple or a dict; the batch
|
||||
helper takes `high` and `low` numpy arrays. Node and WASM expose
|
||||
`update(high, low)` and `batch(high, low)`.
|
||||
|
||||
## Warmup
|
||||
|
||||
`warmup_period() == 2·ema_period + sum_period − 2`. The first EMA seeds at
|
||||
input `ema_period`; the second EMA, stacked on it, seeds at
|
||||
`2·ema_period − 1`; the summation window then needs `sum_period` ratios.
|
||||
For the default `(9, 25)` that is `41` bars.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Constant range.** When every bar has the same high–low range, both
|
||||
EMAs converge to the same value, every ratio is `1`, and the Mass Index
|
||||
equals `sum_period` (`constant_range_sums_to_sum_period` pins this).
|
||||
- **Zero-range market.** A flat market (`high == low`) drives both EMAs to
|
||||
`0`; the `0 / 0` is guarded with the neutral ratio `1`, so the Mass
|
||||
Index again equals `sum_period`
|
||||
(`zero_range_market_sums_to_sum_period` pins this).
|
||||
- **Candle validation.** `Candle::new` rejects invalid bars upstream.
|
||||
- **Reset.** `mi.reset()` clears both EMAs, the window and the sum.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
use wickra::{BatchExt, Candle, Indicator, MassIndex};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut mi = MassIndex::new(3, 4)?;
|
||||
// Constant high-low range of 2.0; the Mass Index settles at sum_period.
|
||||
let candles: Vec<Candle> = (0..40)
|
||||
.map(|i| {
|
||||
let mid = 100.0 + f64::from(i);
|
||||
Candle::new(mid, mid + 1.0, mid - 1.0, mid, 1.0, i64::from(i)).unwrap()
|
||||
})
|
||||
.collect();
|
||||
let out = mi.batch(&candles);
|
||||
println!("warmup_period = {}", mi.warmup_period());
|
||||
println!("last = {:?}", out.last().unwrap());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
warmup_period = 8
|
||||
last = Some(4.0)
|
||||
```
|
||||
|
||||
A constant range makes every ratio `1`, so the sum equals `sum_period`
|
||||
(`4`). This matches the `constant_range_sums_to_sum_period` test in
|
||||
`crates/wickra-core/src/indicators/mass_index.rs`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
|
||||
mi = ta.MassIndex() # (ema_period=9, sum_period=25)
|
||||
mid = np.arange(100.0, 160.0)
|
||||
high = mid + 1.0
|
||||
low = mid - 1.0
|
||||
print(mi.batch(high, low)[-1]) # constant range -> 25
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
25.0
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
```javascript
|
||||
const ta = require('wickra');
|
||||
const mi = new ta.MassIndex(9, 25);
|
||||
const mid = Array.from({ length: 60 }, (_, i) => 100 + i);
|
||||
const high = mid.map((m) => m + 1);
|
||||
const low = mid.map((m) => m - 1);
|
||||
console.log(mi.batch(high, low).at(-1)); // 25
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
`MassIndex` is a *reversal-warning* tool, not a direction tool — it never
|
||||
tells you which way price will go, only that a turn is likely. The textbook
|
||||
use is the "reversal bulge" on the default `(9, 25)` settings: watch for
|
||||
the index to push above `27`, then act when it drops back under `26.5`,
|
||||
using a directional indicator (a moving average, ADX) to pick the side.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Expecting a direction.** The Mass Index is direction-blind; always
|
||||
pair it with a trend indicator.
|
||||
- **Feeding it scalar prices.** It needs `high`/`low`; it takes a
|
||||
`Candle`, not an `f64`.
|
||||
|
||||
## References
|
||||
|
||||
Donald Dorsey, "The Mass Index", *Technical Analysis of Stocks &
|
||||
Commodities* (1992). The double-EMA-of-range construction and the `(9,
|
||||
25)` defaults follow Dorsey's original.
|
||||
|
||||
## See also
|
||||
|
||||
- [Indicator-Atr.md](../volatility/Indicator-Atr.md) — directional-free
|
||||
volatility in price units.
|
||||
- [Indicator-BollingerBands.md](../volatility/Indicator-BollingerBands.md)
|
||||
— another range-expansion lens.
|
||||
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
|
||||
@@ -0,0 +1,160 @@
|
||||
# Vortex
|
||||
|
||||
> Vortex Indicator — a pair of oscillators (`VI+`, `VI−`) whose crossings
|
||||
> identify the start of a new trend.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Family | Momentum (trend strength) |
|
||||
| Sub-category | Directional |
|
||||
| Input type | `Candle` (uses `high`, `low`, `close`) |
|
||||
| Output type | `VortexOutput { plus, minus }` |
|
||||
| Output range | each line `>= 0`, typically around `1.0` |
|
||||
| Default parameters | `period = 14` (Python) |
|
||||
| Warmup period | `period + 1` |
|
||||
| Interpretation | `VI+` above `VI−` = up-trend; the cross marks the turn. |
|
||||
|
||||
## Formula
|
||||
|
||||
```
|
||||
VM+_t = |high_t − low_{t−1}| (positive vortex movement)
|
||||
VM−_t = |low_t − high_{t−1}| (negative vortex movement)
|
||||
TR_t = true range
|
||||
VI+ = Σ VM+ over period / Σ TR over period
|
||||
VI− = Σ VM− over period / Σ TR over period
|
||||
```
|
||||
|
||||
Each vortex movement measures how far this bar reached against the
|
||||
*opposite* extreme of the previous bar; dividing the running sums by the
|
||||
running true range normalises both lines to a comparable scale around
|
||||
`1.0`. `VI+` crossing above `VI−` signals a new up-trend; the reverse, a
|
||||
down-trend.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Name | Type | Default | Valid range | Description |
|
||||
|----------|---------|---------------|-------------|-------------|
|
||||
| `period` | `usize` | `14` (Python) | `>= 1` | Summation window. `0` errors with `Error::PeriodZero`. |
|
||||
|
||||
The Python binding defaults `period` to `14`.
|
||||
|
||||
## Inputs / Outputs
|
||||
|
||||
From `crates/wickra-core/src/indicators/vortex.rs`:
|
||||
|
||||
```rust
|
||||
pub struct VortexOutput { pub plus: f64, pub minus: f64 }
|
||||
|
||||
impl Indicator for Vortex {
|
||||
type Input = Candle;
|
||||
type Output = VortexOutput;
|
||||
}
|
||||
```
|
||||
|
||||
`Vortex` is a **candle-input** indicator reading `high`, `low` and
|
||||
`close`. The streaming `update` returns `VortexOutput` (Rust),
|
||||
`(plus, minus)` (Python), or `{ plus, minus }` (Node/WASM). The batch
|
||||
helper returns one row per input — a `(n, 2)` numpy array in Python, a
|
||||
flat `[plus, minus, …]` array of length `2·n` in Node/WASM, with `NaN`
|
||||
during warmup.
|
||||
|
||||
## Warmup
|
||||
|
||||
`Vortex::new(period).warmup_period() == period + 1`. The first VM/TR
|
||||
triple needs a previous bar, so it forms on bar 2; the summation window
|
||||
then needs `period` triples — the first output lands on input
|
||||
`period + 1`.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Flat market.** A window with zero total true range cannot be
|
||||
normalised; both lines are reported as `0.0`
|
||||
(`perfectly_flat_market_yields_zero` pins this).
|
||||
- **Non-negative.** Both `VI+` and `VI−` are sums of absolute values over
|
||||
a non-negative range, so neither is ever negative
|
||||
(`outputs_are_non_negative` pins this).
|
||||
- **Candle validation.** `Candle::new` rejects invalid bars upstream.
|
||||
- **Reset.** `vortex.reset()` clears the previous bar, the window and the
|
||||
three running sums.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
use wickra::{BatchExt, Candle, Indicator, Vortex};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let candles = [
|
||||
Candle::new(9.0, 10.0, 8.0, 9.0, 1.0, 0)?,
|
||||
Candle::new(10.0, 12.0, 9.0, 11.0, 1.0, 1)?,
|
||||
Candle::new(12.0, 13.0, 11.0, 12.0, 1.0, 2)?,
|
||||
];
|
||||
let mut v = Vortex::new(2)?;
|
||||
let out = v.batch(&candles);
|
||||
println!("{:?}", out[2]);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Some(VortexOutput { plus: 1.6, minus: 0.4 })
|
||||
```
|
||||
|
||||
Over the two formed bars `Σ VM+ = 8`, `Σ VM− = 2`, `Σ TR = 5`, giving
|
||||
`VI+ = 1.6` and `VI− = 0.4`. This matches the `reference_values` test in
|
||||
`crates/wickra-core/src/indicators/vortex.rs`.
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
|
||||
v = ta.Vortex(14)
|
||||
high = np.array([10.0, 12.0, 13.0])
|
||||
low = np.array([8.0, 9.0, 11.0])
|
||||
close = np.array([9.0, 11.0, 12.0])
|
||||
# v.batch(high, low, close) -> (3, 2) array of [plus, minus], NaN during warmup
|
||||
print(v.update((9.0, 10.0, 8.0, 9.0, 1.0, 0)))
|
||||
```
|
||||
|
||||
### Node
|
||||
|
||||
```javascript
|
||||
const ta = require('wickra');
|
||||
const v = new ta.Vortex(14);
|
||||
console.log(v.update(12, 9, 11)); // { plus, minus } or null during warmup
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
`Vortex` is a trend-onset detector. The signal is the **crossing**: when
|
||||
`VI+` rises above `VI−`, a new up-trend is starting; when `VI−` rises
|
||||
above `VI+`, a down-trend. The gap between the lines measures conviction —
|
||||
a wide, widening gap is a strong trend, converging lines warn of a stall.
|
||||
Unlike a lagging moving-average cross, the vortex movements react to the
|
||||
*reach* of each bar, so the cross tends to fire early.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- **Reading the lines in isolation.** A `VI+` of `1.1` means nothing on
|
||||
its own — what matters is its position relative to `VI−`.
|
||||
- **Feeding it scalar prices.** It needs `high`/`low`/`close`.
|
||||
|
||||
## References
|
||||
|
||||
Etienne Botes and Douglas Siepman, "The Vortex Indicator", *Technical
|
||||
Analysis of Stocks & Commodities* (2010). The `VM±` / true-range
|
||||
definition here follows their original.
|
||||
|
||||
## See also
|
||||
|
||||
- [Indicator-Adx.md](Indicator-Adx.md) — Wilder's directional system.
|
||||
- [Indicator-Atr.md](../volatility/Indicator-Atr.md) — the true range
|
||||
Vortex normalises against.
|
||||
- [Indicators-Overview.md](../../Indicators-Overview.md) — the full taxonomy.
|
||||
Reference in New Issue
Block a user