From 7fd3d8050459c2043afa6d6f7d7f3b4b325484ed Mon Sep 17 00:00:00 2001 From: Griffiths Lott Date: Sat, 31 Dec 2022 14:38:36 -0500 Subject: [PATCH] Added MA and rsi indicators --- Cargo.toml | 2 +- src/data.rs | 3 -- src/data/options.rs | 2 +- src/data/quotes.rs | 7 +++ src/data/timeseries.rs | 38 +++++++++++--- src/indicators.rs | 2 + src/indicators/ma.rs | 42 ++++++++++++++++ src/indicators/rsi.rs | 45 +++++++++++++++++ src/lib.rs | 13 ++--- src/order.rs | 4 -- src/tests.rs | 112 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 243 insertions(+), 27 deletions(-) create mode 100644 src/indicators.rs create mode 100644 src/indicators/ma.rs create mode 100644 src/indicators/rsi.rs create mode 100644 src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index 792e136..761fb9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "RustyTrader" +name = "rusty_trader" version = "0.1.0" edition = "2021" diff --git a/src/data.rs b/src/data.rs index 3b2e988..a965794 100644 --- a/src/data.rs +++ b/src/data.rs @@ -13,9 +13,6 @@ pub trait Volume { fn volume(&self) -> Option; } -pub fn to_datetime(timestamp: i64) { - -} pub enum Period { Second, diff --git a/src/data/options.rs b/src/data/options.rs index 62acd7d..7de615e 100644 --- a/src/data/options.rs +++ b/src/data/options.rs @@ -30,7 +30,7 @@ pub enum OptionKind { Put } impl OptionKind { - fn from_str(option_kind: &str) -> Result{ + pub fn from_str(option_kind: &str) -> Result{ let call = Regex::new("(?i)call|(?i)c").expect("call regex failed:\noptions.rs:\t(?i)call|(?i)c"); let put = Regex::new("(?i)put|(?i)p").expect("put regex failed:\noptions.rs:\t(?i)put|(?i)p"); diff --git a/src/data/quotes.rs b/src/data/quotes.rs index 5ac4935..3171a63 100644 --- a/src/data/quotes.rs +++ b/src/data/quotes.rs @@ -22,6 +22,13 @@ pub trait QuoteData { fn exchange(&self) -> Option; // TO DO: Gives the datasource used } + +pub trait HOLC { + fn high(&self) -> f64; + fn open(&self) -> f64; + fn low(&self) -> f64; + fn close(&self) -> f64; +} ///Standard volume information /// pub trait VolumeData { diff --git a/src/data/timeseries.rs b/src/data/timeseries.rs index 8dba9e1..73ba2d6 100644 --- a/src/data/timeseries.rs +++ b/src/data/timeseries.rs @@ -1,8 +1,9 @@ use chrono::{DateTime, Utc}; use super::{Volume, TimeStamp, DynResult}; use polars::{prelude::DataFrame, df, prelude::{NamedFrom}}; +use super::quotes::HOLC; - +#[derive(Clone)] pub struct Candle { pub high: f64, pub open: f64, @@ -22,20 +23,31 @@ impl Candle { timestamp: data.timestamp(), } } + pub fn new(h: f64, o: f64, l: f64, c: f64, v: Option, t: DateTime) -> Self { + Candle { + high : h, + open : o, + low : l, + close: c, + volume : v, + timestamp : t + } + } } - -pub trait HOLC { - fn high(&self) -> f64; - fn open(&self) -> f64; - fn low(&self) -> f64; - fn close(&self) -> f64; +impl HOLC for Candle { + fn high(&self) -> f64 { self.high } + fn open(&self) -> f64 { self.open } + fn low(&self) -> f64 { self.low } + fn close(&self) -> f64 { self.close} } + + pub struct TimeSeries { candles: Vec } impl TimeSeries { - fn new(candles: Vec) -> Self { + pub fn new(candles: Vec) -> Self { TimeSeries { candles: candles } } fn from(data: Vec) -> Self @@ -47,6 +59,12 @@ impl TimeSeries { } TimeSeries::new(candles) } + pub fn into_vec(self) -> Vec { + return self.candles + } + pub fn as_vec(&self) -> Vec{ + return self.candles.clone() + } } pub fn create_timeseries_dataframe(timeseries_data: &Vec) -> DynResult { @@ -75,4 +93,8 @@ pub fn create_timeseries_dataframe(timeseries_data: &Vec) -> DynResult ×tamp )?) +} + +pub fn sample_timeseries() { + } \ No newline at end of file diff --git a/src/indicators.rs b/src/indicators.rs new file mode 100644 index 0000000..ea99ef9 --- /dev/null +++ b/src/indicators.rs @@ -0,0 +1,2 @@ +pub mod ma; +pub mod rsi; \ No newline at end of file diff --git a/src/indicators/ma.rs b/src/indicators/ma.rs new file mode 100644 index 0000000..34f7f22 --- /dev/null +++ b/src/indicators/ma.rs @@ -0,0 +1,42 @@ +use crate::data::quotes::HOLC; + + +pub fn simple_moving_avg (data: Vec, period: usize) -> Vec { + assert!(data.len() >= period, "Cannot create SMA! : Data is shorter than requested period."); + let mut ma_data = vec![]; + let n: f64 = period as f64; + // Get the initial average value + let mut avg = data[..period].iter().fold(0.0, |acc, d| acc + d.close()) / n; + ma_data.push(avg); + // calculate the rest of the averages going forward + for (i, d) in data[period..].iter().enumerate() { + // subtract the data that is no longer part of the period + // Not that i corresponds to the index of data 1 period away since we are starting + // iteraction 1 period in. This means it's perfect for getting the oldest data. + avg -= data[i].close()/n; + // add the new data to the average + avg += d.close() / n; + ma_data.push(avg); + } + ma_data +} + +fn cur_ema(closing_price: f64, prev_ema: f64, smoothing: f64) -> f64 { + (closing_price * smoothing) + (prev_ema * (1.0 - smoothing)) +} + +pub fn exponential_moving_average (data: Vec, period: usize) -> Vec { + assert!(data.len() >= period + 1, "EMA requires period + 1 # of datapoints!"); + let mut ema_data = vec![]; + let n: f64 = period as f64; + // Get the average closing price over the original period + let ma = data[..period].iter().fold(0.0, |acc, d| acc + d.close()) / n; + // Calculates our weighthing factor (using 2 as base) + let smoothing = 2.0 / (n + 1.0); + for (i, d) in data[period..].iter().enumerate() { + // Use the previously calced MA for the first iteration + if i == 0 {ema_data.push(cur_ema(d.close(), ma, smoothing)); continue;} + ema_data.push(cur_ema(d.close(), *ema_data.last().unwrap(), smoothing)); + } + ema_data +} \ No newline at end of file diff --git a/src/indicators/rsi.rs b/src/indicators/rsi.rs new file mode 100644 index 0000000..e20986c --- /dev/null +++ b/src/indicators/rsi.rs @@ -0,0 +1,45 @@ +use crate::data::quotes::HOLC; + +pub fn relative_strength_index(data: Vec, period: usize) -> Vec { + assert!(data.len() >= period + 2); + let mut rsi_data: Vec = vec![]; + let n = period as f64; + + let mut g_avg = 0.0; + let mut l_avg = 0.0; + + for (i, d) in data[1..period+1].iter().enumerate() { + let chg = d.close() - data[i].close(); + if chg >= 0.0 {g_avg += chg;} + else if chg <= 0.0 { l_avg += chg.abs();} + + if i == period -1 { + g_avg /=n; + l_avg /=n; + rsi_data.push(100.0 - (100.0/(1.0+((g_avg)/(l_avg))))); + } + } + // This is magic. Don't touch it. The periods just work. + + for (i, d) in data[period+1..].iter().enumerate() { + + let chg = d.close() - data[period + i ].close(); + + if chg > 0.0 { + g_avg = (g_avg * (n - 1.0) + chg) / n; + l_avg = (l_avg * (n - 1.0) + 0.0) / n; + } else if chg < 0.0 { + g_avg = (g_avg * (n - 1.0) + 0.0) / n; + l_avg = (l_avg * (n - 1.0) + chg.abs()) / n; + } + else { + g_avg = (g_avg * (n - 1.0) + 0.0) / n; + l_avg = (l_avg * (n - 1.0) + 0.0) / n; + } + + rsi_data.push(100.0 - (100.0/(1.0+((g_avg)/(l_avg))))); + + } + rsi_data + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e4e6dc7..ee54a46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,6 @@ -mod data; +pub mod data; +pub mod indicators; mod order; +mod tests; pub type DynResult = Result>; - - -#[cfg(test)] -mod tests { - - use super::*; - - -} diff --git a/src/order.rs b/src/order.rs index 14636db..5de3c6b 100644 --- a/src/order.rs +++ b/src/order.rs @@ -6,14 +6,10 @@ enum OrderDirection { Sell } -enum CancelCondition { - -} struct Order { asset: AssetIdentifier, transaction_price: f64, quantity: i32, - } \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..b48a4a1 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,112 @@ +#[cfg(test)] +mod tests { + use crate::data::timeseries::Candle; + use crate::indicators::ma::exponential_moving_average; + use crate::indicators::rsi::relative_strength_index; + use crate::indicators::{ma}; + use crate::data::quotes::{HOLC}; + use chrono::Utc; + + + #[test] + fn sma() { + let data = vec![ + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 8.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 11.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 8.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 14.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 1.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 7.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 12.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 5.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 1.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 10.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 5.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 2.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 8.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 11.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 14.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 12.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + ]; + println!("data.len() = {:?}", data.len()); + + let sma_d = ma::simple_moving_avg(data, 3); + println!("sma_d = {:?}", sma_d); + + } + #[test] + fn ema(){ + let data = vec![ + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 8.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 11.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 8.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 14.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 1.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 7.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 12.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 5.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 1.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 10.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 5.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 2.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 8.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 11.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 14.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 9.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 12.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 4.0, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 6.0, None,Utc::now()), + ]; + + let ema_d = exponential_moving_average(data, 3); + println!("ema_d = {:?}", ema_d); + } + + #[test] + fn rsi() { + let data = vec![ + Candle::new(0.0,0.0,0.0, 140.06, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 144.28, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 147.64, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 150.6, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 151.92, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 154.79, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 152.61, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 150.26, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 150.47, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 146.68, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 145.14, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 148.1, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 148.82, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 148.91, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 147.21, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 142.84, None,Utc::now()), + Candle::new(0.0,0.0,0.0, 145.48, None,Utc::now()), + + ]; + + let rsi_d = relative_strength_index(data, 14); + println!("rsi_d = {:?}", rsi_d); + } +} \ No newline at end of file