Skip to content

Commit

Permalink
add fractal adaptive ma (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
chungg authored Jan 16, 2025
1 parent cd0dfec commit de6e182
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
9 changes: 9 additions & 0 deletions benches/traquer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,15 @@ fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("ma-t3", |b| {
b.iter(|| black_box(smooth::t3(&stats.close, 6, None).collect::<Vec<f64>>()))
});
c.bench_function("ma-frama", |b| {
b.iter(|| {
black_box(
smooth::frama(&stats.close, 16)
.unwrap()
.collect::<Vec<f64>>(),
)
})
});

c.bench_function("correlation-pcc", |b| {
b.iter(|| black_box(correlation::pcc(&stats.open, &stats.close, 16).collect::<Vec<_>>()))
Expand Down
54 changes: 54 additions & 0 deletions src/smooth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,3 +921,57 @@ pub fn t3<T: ToPrimitive>(
.map(move |(e3, e4, e5, e6)| c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3),
)
}

/// Fractal Adaptive Moving Average
///
/// Based on argument that market prices are fractal, this algorithm considers fractal shapes as
/// self-similar because they tend to have the same roughness and sparseness regardless of the
/// magnification used to view them.
///
/// ## Sources
///
/// [[1]](https://www.mesasoftware.com/papers/FRAMA.pdf)
///
/// # Examples
///
/// ```
/// use traquer::smooth;
///
/// match smooth::frama(&[1.0,2.0,3.0,4.0,5.0,2.0,3.0,4.0,2.0,3.0,4.0,2.0,3.0,4.0], 3){
/// Ok(iter) => {
/// let ma = iter.collect::<Vec<f64>>();
/// },
/// Err(e) => println!("Error: {}", e),
/// }
/// ```
pub fn frama<T: ToPrimitive>(
data: &[T],
window: usize,
) -> Result<impl Iterator<Item = f64> + '_, &'static str> {
if window % 2 != 0 {
return Err("Window must be an even number");
}
let mid = window / 2;
Ok(iter::repeat(f64::NAN)
.take(window - 1)
.chain(data.windows(window).scan(0., move |state, w| {
let (hh1, ll1, hh2, ll2) = w.iter().take(mid).zip(w.iter().rev().take(mid)).fold(
(f64::MIN, f64::MAX, f64::MIN, f64::MAX),
|(hh1, ll1, hh2, ll2), (x1, x2)| {
(
hh1.max(x1.to_f64().unwrap()),
ll1.min(x1.to_f64().unwrap()),
hh2.max(x2.to_f64().unwrap()),
ll2.min(x2.to_f64().unwrap()),
)
},
);
let n1 = (hh1 - ll1) / mid as f64;
let n2 = (hh2 - ll2) / mid as f64;
let n3 = (hh1.max(hh2) - ll1.min(ll2)) / window as f64;
let d = ((n1 + n2).ln() - n3.ln()) / 2_f64.ln();
let alpha = (-4.6 * (d - 1.)).exp().clamp(0.01, 1.);
*state = alpha * w.last().unwrap().to_f64().unwrap() + (1. - alpha) * *state;
Some(*state)
})))
}
41 changes: 41 additions & 0 deletions tests/ma_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1618,3 +1618,44 @@ fn test_t3() {
result[12..]
);
}

#[test]
fn test_frama() {
let stats = common::test_data();
assert!(smooth::frama(&stats.close, 3).is_err());

let result = smooth::frama(&stats.close, 8).unwrap().collect::<Vec<_>>();
assert_eq!(stats.close.len(), result.len());
assert_eq!(
vec![
11.735817449990835,
35.08640357543414,
38.21403178926078,
41.68248606395869,
42.28922749250002,
42.23389147160019,
43.956450640092505,
42.27000045776367,
40.0,
40.17812608700349,
39.32672748964177,
41.720001220703125,
41.5875219933315,
41.640739963598065,
41.7792881667438,
42.163913483292994,
42.45000076293945,
45.43000030517578,
45.97753225132534,
45.2928023379573,
45.880001068115234,
46.458327365816054,
46.483063104549856,
47.6326315798041,
49.400001525878906,
50.180656289232715,
49.29499816894531
],
result[8 - 1..]
);
}

0 comments on commit de6e182

Please sign in to comment.