Files
wickra/docs/wiki/indicators/momentum/Indicator-Mfi.md
T
kingchenc 278b6afaa4 E4: commit the documentation sources
The 33 Markdown files under docs/wiki/ were never tracked. Commit them
into the repository so the documentation is versioned alongside the
code: 8 top-level pages plus 25 per-indicator deep dives under
indicators/{momentum,trend,volatility,volume}/.

The pages are kept in-repo (not pushed to a flat GitHub Wiki), so the
relative indicators/<family>/... links in Home.md resolve correctly
when rendered on GitHub.
2026-05-22 16:18:04 +02:00

205 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MFI
> Money Flow Index — a volume-weighted RSI built on typical price times
> volume.
## Quick reference
| Field | Value |
|-------|-------|
| Family | Momentum |
| Sub-category | bounded oscillator (volume-driven) |
| Input type | `Candle` (volume needed) |
| Output type | `f64` |
| Output range | `[0, 100]` |
| Default parameters | `period = 14` (Python) |
| Warmup period | `period` (14 for `period = 14`) |
| Interpretation | overbought above 80, oversold below 20 |
## Formula
For each new candle:
```
TP_t = (high_t + low_t + close_t) / 3 (typical price)
MF_t = TP_t · volume_t (money flow)
positive MF = MF_t if TP_t > TP_{t-1}, else 0
negative MF = MF_t if TP_t < TP_{t-1}, else 0
(both zero when TP_t == TP_{t-1})
```
Maintain rolling sums of positive and negative money flow over the last
`period` bars. Then:
```
MR_t = positive_sum / negative_sum
MFI_t = 100 100 / (1 + MR_t)
```
The implementation guards both special cases: when both rolling sums are
zero, MFI returns `50` (neutral); when only `negative_sum == 0`, MFI
returns `100`; otherwise the standard formula.
## Parameters
| Name | Type | Default (Python) | Valid range | Description |
|------|------|------------------|-------------|-------------|
| `period` | `usize` | `14` | `>= 1` | Rolling window length for the positive/negative money-flow sums. |
`Mfi::new(0)` returns `Error::PeriodZero`.
## Inputs / Outputs
From `impl Indicator for Mfi`:
```rust
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64>;
```
Volume is consumed via `candle.volume` — it is not optional. Calling
the indicator with a zero-volume candle is legal (every money flow on
that bar is zero), but mass zero-volume bars will dilute the sums.
Python's `MFI.batch(high, low, close, volume)` returns a 1-D `float64`
`np.ndarray` (warmup → `NaN`). Node's `MFI.batch(high, low, close,
volume)` returns a flat `number[]` (warmup → `NaN`); only `batch` is
exposed on the Node binding.
## Warmup
`warmup_period()` returns `period`. The first candle has no previous
`TP` to compare against, so its money flow is classified as neither
positive nor negative — it sits in the window as a `0 / 0` slot but
still counts toward filling the window. The first `Some` is therefore
emitted at the `period`-th `update`, exactly when the rolling positive
and negative sums first contain `period 1` real comparisons.
## Edge cases
- **Pure uptrend.** Every `TP_t > TP_{t-1}`, so `negative_sum == 0` and
the implementation returns `100` directly (test
`pure_uptrend_yields_high_mfi`). Pure downtrend mirrors at `0` (test
`pure_downtrend_yields_low_mfi`).
- **Flat input (all `TP` equal).** Both sums stay at zero; the
implementation returns `50` (the same neutral convention as RSI on
flat input).
- **Zero-volume candle.** Money flow on that bar is zero. The window
still advances; the indicator just gets one less data point of
influence.
- **Reset.** `reset()` clears `prev_tp`, both rolling windows, and both
sums.
## Examples
### Rust
```rust
use wickra::{BatchExt, Candle, Indicator, Mfi};
let candles: Vec<Candle> = (1..=20)
.map(|i| Candle::new(i as f64, i as f64, i as f64, i as f64, 100.0, 0).unwrap())
.collect();
let mut mfi = Mfi::new(14)?;
let out = mfi.batch(&candles);
println!("row 13 = {}", out[13].unwrap());
println!("row 19 = {}", out[19].unwrap());
# Ok::<(), wickra::Error>(())
```
Verified output:
```
row 13 = 100
row 19 = 100
```
### Python
```python
import numpy as np
import wickra as ta
n = 20
i = np.arange(1, n + 1, dtype=float)
high = low = close = i
volume = np.full(n, 100.0)
mfi = ta.MFI(14)
out = mfi.batch(high, low, close, volume)
print('warmup:', mfi.warmup_period())
print('row 13:', out[13])
print('row 19:', out[19])
```
Verified output:
```
warmup: 14
row 13: 100.0
row 19: 100.0
```
### Node
```javascript
const wickra = require('wickra');
const n = 20;
const high = [], low = [], close = [], vol = [];
for (let i = 1; i <= n; i++) {
high.push(i); low.push(i); close.push(i); vol.push(100);
}
const m = new wickra.MFI(14);
const out = m.batch(high, low, close, vol);
console.log('row 13:', out[13]);
console.log('row 19:', out[19]);
```
Verified output:
```
row 13: 100
row 19: 100
```
## Interpretation
- **Overbought / oversold.** The conventional MFI thresholds are
`80 / 20` — tighter than RSI's `70 / 30` because the volume weighting
amplifies sustained one-way moves.
- **Divergence.** MFI divergences are read like RSI divergences: a new
price high without a confirming MFI high is bearish, and vice versa.
Because volume is in the mix, MFI divergences are often interpreted
as "the move is happening on weak participation" — i.e. structurally
more meaningful than a pure-price divergence.
- **Compare with OBV.** OBV (the unsmoothed cumulative volume) tells
you accumulated participation; MFI tells you participation pressure
over a fixed horizon. The two often diverge interestingly near
trend exhaustion.
## Common pitfalls
- **MFI requires volume.** Unlike RSI (close only) or Stochastic
(high/low/close), MFI's per-bar money flow is `TP × volume`. Passing
a candle stream with `volume == 0` throughout will collapse MFI to
`50` regardless of price action. Validate your data source before
reaching for MFI.
- **Same flat-input convention as RSI.** A perfectly flat window yields
`50` (not `NaN`, not "no value"). Treat the value as informational
only until the underlying TP series starts moving.
## References
- Gene Quong and Avrum Soudack, "Volume-Weighted RSI: Money Flow",
*Technical Analysis of Stocks & Commodities*, March 1989 — the
original publication of the MFI as a volume-weighted RSI variant.
## See also
- [Indicator: Rsi](Indicator-Rsi.md) — the price-only ancestor.
- [Indicator: Adx](Indicator-Adx.md) — directional/trend strength to
pair with MFI's overbought/oversold reading.
- [Warmup Periods](../../Warmup-Periods.md) — bare `period` (no off-by-one).