Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/src/indicators/efi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::indicators::ema::ema_aligned;
use crate::indicators::ema::ema;

pub fn efi(closes: &[Option<f64>], volumes: &[Option<f64>], period: usize) -> Vec<Option<f64>> {
let len = closes.len();
Expand All @@ -20,7 +20,7 @@ pub fn efi(closes: &[Option<f64>], volumes: &[Option<f64>], period: usize) -> Ve
if period == 1 {
efi = force;
} else {
efi = ema_aligned(&force, period);
efi = ema(&force, period);
}

efi
Expand Down
3 changes: 0 additions & 3 deletions core/src/indicators/ema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ pub fn ema(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> {

result
}

pub(crate) use ema as ema_aligned;

#[cfg(test)]
mod tests {
use super::*;
Expand Down
8 changes: 4 additions & 4 deletions core/src/indicators/eom.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::indicators::sma::sma_aligned;
use crate::indicators::sma::sma;

pub fn eom(
highs: &[Option<f64>],
Expand All @@ -9,7 +9,7 @@ pub fn eom(
scale: f64,
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
let eom_line = eom_line(highs, lows, volumes, period, scale);
let signal = sma_aligned(&eom_line, signal_period);
let signal = sma(&eom_line, signal_period);

(eom_line, signal)
}
Expand All @@ -23,7 +23,7 @@ pub fn eom_signal(
scale: f64,
) -> Vec<Option<f64>> {
let eom_line = eom_line(highs, lows, volumes, period, scale);
sma_aligned(&eom_line, signal_period)
sma(&eom_line, signal_period)
}

pub fn eom_line(
Expand Down Expand Up @@ -63,7 +63,7 @@ pub fn eom_line(
}
}

sma_aligned(&eom_values, period)
sma(&eom_values, period)
}

#[cfg(test)]
Expand Down
82 changes: 71 additions & 11 deletions core/src/indicators/macd.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::indicators::ema::{ema_aligned, ema_dense};
use crate::indicators::ema::ema;

pub fn macd(
data: &[f64],
data: &[Option<f64>],
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> (Vec<Option<f64>>, Vec<Option<f64>>, Vec<Option<f64>>) {
let macd_line = macd_line(data, fast_period, slow_period);
let signal_line = ema_aligned(&macd_line, signal_period);
let signal_line = ema(&macd_line, signal_period);
let histogram = macd_line
.iter()
.zip(signal_line.iter())
Expand All @@ -21,13 +21,13 @@ pub fn macd(
}

pub fn macd_histogram(
data: &[f64],
data: &[Option<f64>],
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> Vec<Option<f64>> {
let macd_line = macd_line(data, fast_period, slow_period);
let signal_line = ema_aligned(&macd_line, signal_period);
let signal_line = ema(&macd_line, signal_period);

macd_line
.iter()
Expand All @@ -40,24 +40,24 @@ pub fn macd_histogram(
}

pub fn macd_signal(
data: &[f64],
data: &[Option<f64>],
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> Vec<Option<f64>> {
let macd_line = macd_line(data, fast_period, slow_period);
ema_aligned(&macd_line, signal_period)
ema(&macd_line, signal_period)
}

pub fn macd_line(data: &[f64], fast_period: usize, slow_period: usize) -> Vec<Option<f64>> {
pub fn macd_line(data: &[Option<f64>], fast_period: usize, slow_period: usize) -> Vec<Option<f64>> {
let mut macd_line = vec![None; data.len()];

if data.len() < slow_period || fast_period >= slow_period {
return macd_line;
}

let fast_ema = ema_dense(data, fast_period);
let slow_ema = ema_dense(data, slow_period);
let fast_ema = ema(data, fast_period);
let slow_ema = ema(data, slow_period);

for i in (slow_period - 1)..data.len() {
if let (Some(fast), Some(slow)) = (fast_ema[i], slow_ema[i]) {
Expand All @@ -82,7 +82,10 @@ mod tests {

// When
for symbol in test_cases {
let input = testutils::load_data(&format!("../data/{}.json", symbol), "c");
let input = testutils::load_data(&format!("../data/{}.json", symbol), "c")
.into_iter()
.map(Some)
.collect::<Vec<_>>();
let (macd_line, signal_line, histogram) = macd(&input, 12, 26, 9);

let expected_macd = testutils::load_expected::<Option<f64>>(&format!(
Expand Down Expand Up @@ -119,4 +122,61 @@ mod tests {
);
}
}

#[test]
fn test_macd_signal_follows_base_ema_contract_across_gaps() {
let input = vec![
Some(1.0),
Some(2.0),
Some(3.0),
Some(4.0),
None,
Some(5.0),
Some(6.0),
Some(7.0),
];

let (line, signal, histogram) = macd(&input, 2, 3, 2);

assert_eq!(
line,
vec![
None,
None,
Some(0.5),
Some(0.5),
None,
Some(0.5),
Some(0.5),
Some(0.5),
]
);
assert_eq!(
signal,
vec![
None,
None,
None,
Some(0.5),
None,
Some(0.5),
Some(0.5),
Some(0.5),
]
);
assert_eq!(signal, ema(&line, 2));
assert_eq!(
histogram,
vec![
None,
None,
None,
Some(0.0),
None,
Some(0.0),
Some(0.0),
Some(0.0),
]
);
}
}
157 changes: 127 additions & 30 deletions core/src/indicators/massi.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,67 @@
use crate::indicators::ema::{ema_aligned, ema_dense};
use crate::indicators::ema::ema;
use crate::utils::rolling_sum_strict;

pub fn massi(
highs: &[f64],
lows: &[f64],
highs: &[Option<f64>],
lows: &[Option<f64>],
period_ema: usize,
period_sum: usize,
period_signal: usize,
) -> (Vec<Option<f64>>, Vec<Option<f64>>) {
let mass = massi_line(highs, lows, period_ema, period_sum);
let signal = ema_aligned(&mass, period_signal);
let signal = ema(&mass, period_signal);

(mass, signal)
}

pub fn massi_signal(
highs: &[f64],
lows: &[f64],
highs: &[Option<f64>],
lows: &[Option<f64>],
period_ema: usize,
period_sum: usize,
period_signal: usize,
) -> Vec<Option<f64>> {
let mass = massi_line(highs, lows, period_ema, period_sum);
ema_aligned(&mass, period_signal)
ema(&mass, period_signal)
}

pub fn massi_line(
highs: &[f64],
lows: &[f64],
highs: &[Option<f64>],
lows: &[Option<f64>],
period_ema: usize,
period_sum: usize,
) -> Vec<Option<f64>> {
let len = highs.len();
let mut mass = vec![None; len];

if len != lows.len() || len < 2 * (period_ema - 1) + (period_sum - 1) + 1 {
if len != lows.len()
|| period_ema == 0
|| period_sum == 0
|| len < 2 * (period_ema - 1) + (period_sum - 1) + 1
{
return mass;
}

let high_low_diffs: Vec<f64> = highs.iter().zip(lows.iter()).map(|(h, l)| h - l).collect();
let s_ema = ema_dense(&high_low_diffs, period_ema);
let offset: usize = period_ema - 1;
let d_ema = ema_aligned(&s_ema, period_ema);

let mut ema_ratio = Vec::with_capacity(len.saturating_sub(2 * offset));
for i in 0..len {
if let (Some(s), Some(d)) = (s_ema[i], d_ema[i]) {
ema_ratio.push(s / d);
}
}

let mut ratio_sum = 0.0;
for i in 0..ema_ratio.len() {
ratio_sum += ema_ratio[i];
if i >= period_sum - 1 {
mass[i + 2 * offset] = Some(ratio_sum);
ratio_sum -= ema_ratio[i - (period_sum - 1)];
}
}
let high_low_diffs = highs
.iter()
.zip(lows.iter())
.map(|(high, low)| match (high, low) {
(Some(high), Some(low)) => Some(high - low),
_ => None,
})
.collect::<Vec<_>>();
let s_ema = ema(&high_low_diffs, period_ema);
let d_ema = ema(&s_ema, period_ema);
let ema_ratio = s_ema
.iter()
.zip(d_ema.iter())
.map(|(&single, &double)| match (single, double) {
(Some(single), Some(double)) if double != 0.0 => Some(single / double),
_ => None,
})
.collect::<Vec<_>>();

mass = rolling_sum_strict(&ema_ratio, period_sum);
mass
}

Expand All @@ -77,6 +81,8 @@ mod tests {
for symbol in test_cases {
let highs = testutils::load_data(&format!("../data/{}.json", symbol), "h");
let lows = testutils::load_data(&format!("../data/{}.json", symbol), "l");
let highs = highs.into_iter().map(Some).collect::<Vec<_>>();
let lows = lows.into_iter().map(Some).collect::<Vec<_>>();

let (mass, signal) = massi(&highs, &lows, 9, 25, 9);

Expand Down Expand Up @@ -104,4 +110,95 @@ mod tests {
);
}
}

#[test]
fn test_massi_with_gap_invalidates_ratio_window() {
let highs = vec![
Some(5.0),
Some(6.0),
Some(7.0),
None,
Some(8.0),
Some(9.0),
Some(10.0),
];
let lows = vec![
Some(1.0),
Some(2.0),
Some(3.0),
None,
Some(4.0),
Some(5.0),
Some(6.0),
];

let line = massi_line(&highs, &lows, 2, 2);

assert_eq!(
line,
vec![None, None, None, None, None, Some(2.0), Some(2.0)]
);
}

#[test]
fn test_massi_signal_follows_base_ema_contract_across_gaps() {
let highs = vec![
Some(5.0),
Some(6.0),
Some(7.0),
Some(8.0),
Some(9.0),
None,
Some(10.0),
Some(11.0),
Some(12.0),
Some(13.0),
];
let lows = vec![
Some(1.0),
Some(2.0),
Some(3.0),
Some(4.0),
Some(5.0),
None,
Some(6.0),
Some(7.0),
Some(8.0),
Some(9.0),
];

let (line, signal) = massi(&highs, &lows, 2, 2, 2);

assert_eq!(
line,
vec![
None,
None,
None,
Some(2.0),
Some(2.0),
None,
None,
Some(2.0),
Some(2.0),
Some(2.0),
]
);
assert_eq!(
signal,
vec![
None,
None,
None,
None,
Some(2.0),
None,
None,
Some(2.0),
Some(2.0),
Some(2.0),
]
);
assert_eq!(signal, ema(&line, 2));
}
}
Loading