A1: MFI emits first value on the (period+1)-th candle
The first candle now only seeds the previous typical price instead of pushing a fabricated (0,0) money-flow pair into the window, matching the TA-Lib / pandas-ta convention. warmup_period() returns period + 1 and the dead prev_tp.is_none() guard is removed. Adds a first-emission test and a hand-computed reference-value test (MFI(2) = 1200/23).
This commit is contained in:
@@ -50,18 +50,23 @@ impl Indicator for Mfi {
|
|||||||
|
|
||||||
fn update(&mut self, candle: Candle) -> Option<f64> {
|
fn update(&mut self, candle: Candle) -> Option<f64> {
|
||||||
let tp = candle.typical_price();
|
let tp = candle.typical_price();
|
||||||
|
|
||||||
|
// The very first candle only establishes the previous typical price.
|
||||||
|
// It carries no money-flow direction, so it is not pushed into the
|
||||||
|
// window. This matches TA-Lib / pandas-ta, which need `period + 1`
|
||||||
|
// candles before the first MFI value.
|
||||||
|
let Some(prev) = self.prev_tp else {
|
||||||
|
self.prev_tp = Some(tp);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
let mf = tp * candle.volume;
|
let mf = tp * candle.volume;
|
||||||
let (pos_flow, neg_flow) = match self.prev_tp {
|
let (pos_flow, neg_flow) = if tp > prev {
|
||||||
None => (0.0, 0.0),
|
(mf, 0.0)
|
||||||
Some(prev) => {
|
} else if tp < prev {
|
||||||
if tp > prev {
|
(0.0, mf)
|
||||||
(mf, 0.0)
|
} else {
|
||||||
} else if tp < prev {
|
(0.0, 0.0)
|
||||||
(0.0, mf)
|
|
||||||
} else {
|
|
||||||
(0.0, 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.pos_window.len() == self.period {
|
if self.pos_window.len() == self.period {
|
||||||
@@ -75,12 +80,11 @@ impl Indicator for Mfi {
|
|||||||
|
|
||||||
self.prev_tp = Some(tp);
|
self.prev_tp = Some(tp);
|
||||||
|
|
||||||
// Need period+1 candles total (the first one only gives prev_tp).
|
if self.pos_window.len() < self.period {
|
||||||
if self.prev_tp.is_none() || self.pos_window.len() < self.period {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Need at least one comparison-based flow inside the window, otherwise we
|
// A fully flat window (every typical price equal) has zero flow on
|
||||||
// are still on the very first candle.
|
// both sides; by convention MFI is then 50.
|
||||||
if self.pos_sum == 0.0 && self.neg_sum == 0.0 {
|
if self.pos_sum == 0.0 && self.neg_sum == 0.0 {
|
||||||
return Some(50.0);
|
return Some(50.0);
|
||||||
}
|
}
|
||||||
@@ -100,7 +104,9 @@ impl Indicator for Mfi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn warmup_period(&self) -> usize {
|
fn warmup_period(&self) -> usize {
|
||||||
self.period
|
// One seed candle establishes the first previous typical price, then
|
||||||
|
// `period` flow comparisons fill the window.
|
||||||
|
self.period + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ready(&self) -> bool {
|
fn is_ready(&self) -> bool {
|
||||||
@@ -163,4 +169,32 @@ mod tests {
|
|||||||
fn rejects_zero_period() {
|
fn rejects_zero_period() {
|
||||||
assert!(Mfi::new(0).is_err());
|
assert!(Mfi::new(0).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_value_emitted_on_period_plus_one_candle() {
|
||||||
|
// The seed candle plus `period` flow comparisons -> first MFI on the
|
||||||
|
// (period + 1)-th candle (index `period`).
|
||||||
|
let candles: Vec<Candle> = (1..=20).map(|i| c(f64::from(i), 100.0)).collect();
|
||||||
|
let mut mfi = Mfi::new(5).unwrap();
|
||||||
|
let out = mfi.batch(&candles);
|
||||||
|
for (i, v) in out.iter().enumerate().take(5) {
|
||||||
|
assert!(v.is_none(), "candle index {i} must be None during warmup");
|
||||||
|
}
|
||||||
|
assert!(out[5].is_some(), "first MFI value lands at index period (5)");
|
||||||
|
assert_eq!(mfi.warmup_period(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn known_value_period_2() {
|
||||||
|
// Three candles, MFI(2). Candle 1 (tp=10) only seeds the previous TP.
|
||||||
|
// Candle 2 (tp=12 > 10): positive money flow 12 * 100 = 1200.
|
||||||
|
// Candle 3 (tp=11 < 12): negative money flow 11 * 100 = 1100.
|
||||||
|
// money ratio = 1200 / 1100; MFI = 100 - 100 / (1 + 1200/1100) = 1200/23.
|
||||||
|
let candles = vec![c(10.0, 100.0), c(12.0, 100.0), c(11.0, 100.0)];
|
||||||
|
let mut mfi = Mfi::new(2).unwrap();
|
||||||
|
let out = mfi.batch(&candles);
|
||||||
|
assert!(out[0].is_none());
|
||||||
|
assert!(out[1].is_none());
|
||||||
|
assert_relative_eq!(out[2].unwrap(), 1200.0 / 23.0, epsilon = 1e-9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user