Skip to content

Commit

Permalink
Added ability to check if or to EMAs in comparison to SMA.
Browse files Browse the repository at this point in the history
Renamed project from sigta-rs to tatk-rs.
Added MACD indicator.
  • Loading branch information
Ohkthx committed Aug 11, 2023
1 parent 06aa8ea commit 8c83b03
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 22 deletions.
13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[package]
name = "sigta"
name = "tatk"
license = "MIT"
version = "0.1.1"
edition = "2021"
description = "Signal and Technical Analysis"
description = "Technical Analysis Toolkit"
readme = "README.md"
homepage = "https://github.com/Ohkthx/sigta-rs"
repository = "https://github.com/Ohkthx/sigta-rs"
homepage = "https://github.com/Ohkthx/tatk-rs"
repository = "https://github.com/Ohkthx/tatk-rs"
keywords = ["trading", "signal", "technical", "analysis", "ta"]
include = ["*/**/***.rs"]

Expand All @@ -30,6 +30,11 @@ name = "double_exponential_moving_average"
path = "examples/double_exponential_moving_average.rs"
required-features = ["test-data"]

[[example]]
name = "moving_average_convergence_divergence"
path = "examples/moving_average_convergence_divergence.rs"
required-features = ["test-data"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ tatk = { git = "https://github.com/ohkthx/tatk-rs" }
- `cargo run --example exponential_moving_average --all-features`
- **Double Exponential Moving Average (DEMA)**: [double_exponential_moving_average.rs](https://github.com/Ohkthx/tatk-rs/tree/main/examples/double_exponential_moving_average.rs)
- `cargo run --example double_exponential_moving_average --all-features`
- **Moving Average Convergence Divergence (MACD)**: [moving_average_convergence_divergence.rs](https://github.com/Ohkthx/tatk-rs/tree/main/examples/moving_average_convergence_divergence.rs)
- `cargo run --example moving_average_convergence_divergence --all-features`

## Tips Appreciated!

Expand Down
4 changes: 2 additions & 2 deletions examples/double_exponential_moving_average.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Demonstrates how to initialize and use a DEMA.
use sigta::indicators::DEMA;
use sigta::test_data::TEST_DATA;
use tatk::indicators::DEMA;
use tatk::test_data::TEST_DATA;

fn main() {
let period: usize = 10;
Expand Down
4 changes: 2 additions & 2 deletions examples/exponential_moving_average.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Demonstrates how to initialize and use a EMA.
use sigta::indicators::EMA;
use sigta::test_data::TEST_DATA;
use tatk::indicators::EMA;
use tatk::test_data::TEST_DATA;

fn main() {
let period: usize = 10;
Expand Down
23 changes: 23 additions & 0 deletions examples/moving_average_convergence_divergence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Demonstrates how to initialize and use a MACD.
use tatk::indicators::MACD;
use tatk::test_data::TEST_DATA;

// const TEST_DATA: &[f64] = &[
// 11.13, 11.3, 11.59, 11.71, 11.8, 11.8, 12.07, 12.14, 12.04, 12.02, 12.34, 12.61, 12.59, 12.66,
// 12.82, 12.93, 12.79, 12.21,
// ];

fn main() {
let period: usize = 10;

println!("Data: {:?}", TEST_DATA);
println!("Period: {}", period);

let mut macd = match MACD::new(12, 26, 9, TEST_DATA) {
Ok(value) => value,
Err(error) => panic!("{}", error),
};

println!("\nMACD: {}, signal: {}", macd.value(), macd.signal_value());
println!("Adding 107.00. New MACD: {}", macd.next(107.000000));
}
4 changes: 2 additions & 2 deletions examples/simple_moving_average.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Demonstrates how to initialize and use a SMA.
use sigta::indicators::SMA;
use sigta::test_data::TEST_DATA;
use tatk::indicators::SMA;
use tatk::test_data::TEST_DATA;

fn main() {
let period: usize = 10;
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub enum TAError {
InvalidArray,
/// Indexes provided are not valid.
InvalidIndex(usize, usize),
/// Line length is not valid.
InvalidLine(String),
}

impl fmt::Display for TAError {
Expand All @@ -26,6 +28,9 @@ impl fmt::Display for TAError {
start_idx, end_idx
)
}
TAError::InvalidLine(line) => {
write!(f, "invalid line, {} is too small", line)
}
}
}
}
22 changes: 22 additions & 0 deletions src/indicators/ema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct EMA {
period: usize,
/// Current value for the EMA.
value: f64,
/// SMA for the same period
sma: SMA,
}

impl EMA {
Expand All @@ -52,9 +54,15 @@ impl EMA {
last_ema = Self::calculate(period, &last_ema, value);
}

let sma: SMA = match SMA::new(period, data) {
Ok(v) => v,
Err(error) => return Err(error),
};

Ok(Self {
period,
value: last_ema,
sma,
})
}

Expand All @@ -68,12 +76,26 @@ impl EMA {
self.value
}

/// Returns true if the EMA is above an SMA of the same period.
pub fn is_above(&self) -> bool {
self.value > self.sma.value()
}

/// Returns true if the EMA is below an SMA of the same period.
pub fn is_below(&self) -> bool {
self.value < self.sma.value()
}

/// Supply an additional value to recalculate a new EMA.
///
/// # Arguments
///
/// * `value` - New value to add to period.
pub fn next(&mut self, value: f64) -> f64 {
// Progress the SMA by a value.
self.sma.next(value);

// Get the next EMA value.
self.value = Self::calculate(self.period, &self.value, &value);
self.value
}
Expand Down
150 changes: 150 additions & 0 deletions src/indicators/macd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! Moving Average Convergence and Divergence (MACD)
//!
//! # Formula
//!
//! MACD = SHORT_EMA - LONG_EMA
//!
//! MACD = x - y
//!
//! where:
//!
//! * `x` = Short EMA of period `n`
//! * `y` = Long EMA of period `n`
use super::EMA;
use crate::error::TAError;

/// Moving Average Convergence and Divergence (MACD)
///
/// # Formula
///
/// MACD = SHORT_EMA - LONG_EMA
///
/// MACD = x - y
///
/// where:
///
/// * `x` = Short EMA of period `n`
/// * `y` = Long EMA of period `n`
#[derive(Debug)]
pub struct MACD {
/// MACD's current value.
value: f64,
/// Short EMA
ema_short: EMA,
/// Long EMA
ema_long: EMA,
/// Signal Line, EMA of MACD values.
ema_signal: EMA,
/// If the MACD crossed the signal.
crossed: bool,
}

impl MACD {
/// Creates a new MACD with the supplied period and initial data. Often the short line is
/// period of 12, long is a period of 26, and signal is period of 9.
///
/// # Arguments
///
/// * `short` - Period of the short EMA.
/// * `long` - Period of the long EMA.
/// * `signal` - Period of the signal EMA.
/// * `data` - Array of values to create the MACD from.
pub fn new(short: usize, long: usize, signal: usize, data: &[f64]) -> Result<Self, TAError> {
if short > long {
return Err(TAError::InvalidLine("long".to_string()));
} else if data.len() < short {
return Err(TAError::InvalidLine("data".to_string()));
} else if data.len() < signal {
return Err(TAError::InvalidLine("data".to_string()));
}

// Build short EMA up to the long.
let mut ema_short = match EMA::new(short, &data[..long]) {
Ok(value) => value,
Err(error) => return Err(error),
};

// Build long EMA.
let mut ema_long = match EMA::new(long, &data[..long]) {
Ok(value) => value,
Err(error) => return Err(error),
};

// Add the first value.
let mut signals: Vec<f64> = vec![ema_short.value() - ema_long.value()];

// Process the remainder of the data, building a signal line.
for n in long..data.len() {
let short_value = ema_short.next(data[n].clone());
let long_value = ema_long.next(data[n].clone());

signals.push(short_value - long_value);
}

// Build signal EMA of MACDs.
let ema_signal = match EMA::new(signal, &signals) {
Ok(value) => value,
Err(error) => return Err(error),
};

Ok(Self {
value: ema_short.value() - ema_long.value(),
ema_short,
ema_long,
ema_signal,
crossed: false,
})
}

/// Current and most recent value calculated.
pub fn value(&self) -> f64 {
self.value
}

/// Current and most recent signal value calculated.
pub fn signal_value(&self) -> f64 {
self.ema_signal.value()
}

/// Check if the value crossed the signal.
pub fn crossed(&self) -> bool {
self.crossed
}

/// Returns true if the value is above the signal.
pub fn is_above(&self) -> bool {
self.value > self.signal_value()
}

/// Returns true if the value is below the signal.
pub fn is_below(&self) -> bool {
self.value < self.signal_value()
}

/// Supply an additional value to recalculate a new MACD.
///
/// # Arguments
///
/// * `value` - New value to add to period.
pub fn next(&mut self, value: f64) -> f64 {
let was_below: bool = self.is_below();

let short_value = self.ema_short.next(value);
let long_value = self.ema_long.next(value);

// Calculate the new MACD and signal.
self.value = short_value - long_value;
self.ema_signal.next(self.value);

// Update if it crossed the signal or not.
if was_below && self.is_below() {
self.crossed = false;
} else if !was_below && self.is_above() {
self.crossed = false;
} else {
self.crossed = true;
}

self.value
}
}
2 changes: 2 additions & 0 deletions src/indicators/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Indicators generated from samples used for signals.
mod dema;
mod ema;
mod macd;
mod sma;

pub use dema::DEMA;
pub use ema::EMA;
pub use macd::MACD;
pub use sma::SMA;
Loading

0 comments on commit 8c83b03

Please sign in to comment.