use crate::day_trade::TradeError;
pub struct SimpleMovingAverage {
period: usize,
prices: Vec<f64>,
}
impl SimpleMovingAverage {
pub fn new(period: usize) -> Result<Self, String> {
Ok(Self {
period,
prices: Vec::new(),
})
}
pub fn update(&mut self, price: f64) -> Result<(), String> {
self.prices.push(price);
if self.prices.len() > self.period * 2 {
self.prices.remove(0);
}
Ok(())
}
pub fn value(&self) -> Result<f64, String> {
if self.prices.len() < self.period {
return Err(format!("Not enough data points for SMA calculation"));
}
let sum: f64 = self.prices.iter().rev().take(self.period).sum();
Ok(sum / self.period as f64)
}
}
pub struct RelativeStrengthIndex {
period: usize,
prices: Vec<f64>,
gains: Vec<f64>,
losses: Vec<f64>,
}
impl RelativeStrengthIndex {
pub fn new(period: usize) -> Result<Self, String> {
Ok(Self {
period,
prices: Vec::new(),
gains: Vec::new(),
losses: Vec::new(),
})
}
pub fn update(&mut self, price: f64) -> Result<(), String> {
if !self.prices.is_empty() {
let prev_price = *self.prices.last().unwrap();
let change = price - prev_price;
if change > 0.0 {
self.gains.push(change);
self.losses.push(0.0);
} else {
self.gains.push(0.0);
self.losses.push(change.abs());
}
if self.gains.len() > self.period * 2 {
self.gains.remove(0);
self.losses.remove(0);
}
}
self.prices.push(price);
if self.prices.len() > self.period * 2 {
self.prices.remove(0);
}
Ok(())
}
pub fn value(&self) -> Result<f64, String> {
if self.prices.len() <= self.period || self.gains.len() < self.period {
return Err(format!("Not enough data points for RSI calculation"));
}
let avg_gain: f64 =
self.gains.iter().rev().take(self.period).sum::<f64>() / self.period as f64;
let avg_loss: f64 =
self.losses.iter().rev().take(self.period).sum::<f64>() / self.period as f64;
if avg_loss == 0.0 {
return Ok(100.0);
}
let rs = avg_gain / avg_loss;
let rsi = 100.0 - (100.0 / (1.0 + rs));
Ok(rsi)
}
}
pub struct Macd {
fast_period: usize,
slow_period: usize,
signal_period: usize,
prices: Vec<f64>,
macd_values: Vec<f64>,
}
impl Macd {
pub fn new(
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> Result<Self, String> {
Ok(Self {
fast_period,
slow_period,
signal_period,
prices: Vec::new(),
macd_values: Vec::new(),
})
}
pub fn update(&mut self, price: f64) -> Result<(), String> {
self.prices.push(price);
if self.prices.len() > self.slow_period {
let fast_ema = self.calculate_ema(&self.prices, self.fast_period)?;
let slow_ema = self.calculate_ema(&self.prices, self.slow_period)?;
let macd_value = fast_ema - slow_ema;
self.macd_values.push(macd_value);
}
if self.prices.len() > self.slow_period * 2 {
self.prices.remove(0);
}
if self.macd_values.len() > self.signal_period * 2 {
self.macd_values.remove(0);
}
Ok(())
}
fn calculate_ema(&self, prices: &[f64], period: usize) -> Result<f64, String> {
if prices.len() < period {
return Err(format!("Not enough data for EMA calculation"));
}
let multiplier = 2.0 / (period as f64 + 1.0);
let mut sum = 0.0;
for i in 0..period {
sum += prices[prices.len() - period + i];
}
let sma = sum / period as f64;
let mut ema = sma;
for i in prices.len() - period..prices.len() {
ema = (prices[i] - ema) * multiplier + ema;
}
Ok(ema)
}
pub fn macd_value(&self) -> Result<f64, String> {
if self.macd_values.len() < 1 {
return Err(format!("Not enough data for MACD calculation"));
}
Ok(*self.macd_values.last().unwrap())
}
pub fn signal_value(&self) -> Result<f64, String> {
if self.macd_values.len() < self.signal_period {
return Err(format!("Not enough data for signal line calculation"));
}
let multiplier = 2.0 / (self.signal_period as f64 + 1.0);
let mut sum = 0.0;
for i in 0..self.signal_period {
sum += self.macd_values[self.macd_values.len() - self.signal_period + i];
}
let sma = sum / self.signal_period as f64;
let mut ema = sma;
for i in self.macd_values.len() - self.signal_period..self.macd_values.len() {
ema = (self.macd_values[i] - ema) * multiplier + ema;
}
Ok(ema)
}
}
pub struct TimeSeriesPredictor {
horizon: usize,
embedding_dim: usize,
use_ma: bool,
data: Vec<f64>,
}
impl TimeSeriesPredictor {
pub fn new(horizon: usize, embedding_dim: usize, use_ma: bool) -> Result<Self, String> {
Ok(Self {
horizon,
embedding_dim,
use_ma,
data: Vec::new(),
})
}
pub fn forecast(&self, prices: &[f64]) -> Result<Vec<f64>, TradeError> {
if prices.len() < self.embedding_dim + self.horizon {
return Err(TradeError::InsufficientData(format!(
"Need at least {} data points for forecasting",
self.embedding_dim + self.horizon
)));
}
let mut result = Vec::with_capacity(self.horizon);
let trend = self.calculate_trend(prices);
let last_price = *prices.last().unwrap();
for i in 0..self.horizon {
result.push(last_price + trend * (i + 1) as f64);
}
Ok(result)
}
fn calculate_trend(&self, prices: &[f64]) -> f64 {
if prices.len() < 2 {
return 0.0;
}
let window_size = self.embedding_dim.min(prices.len() / 2);
let recent_prices = &prices[prices.len() - window_size..];
let first = recent_prices[0];
let last = recent_prices[recent_prices.len() - 1];
(last - first) / window_size as f64
}
}