use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
use std::hash::Hash;
use thiserror::Error;
mod strategies;
pub mod mock_indicators;
pub mod utils;
pub use strategies::{
AdaptiveMovingAverageStrategy, BollingerBandsStrategy, BreakoutStrategy, CompositeStrategy,
DualTimeframeStrategy, ForecastingStrategy, GridTradingStrategy, MACrossover, MacdStrategy,
MeanReversionStrategy, RsiStrategy, VolumeBasedStrategy, VwapStrategy,
};
#[derive(Error, Debug)]
pub enum TradeError {
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Insufficient data for strategy: {0}")]
InsufficientData(String),
#[error("Strategy calculation error: {0}")]
CalculationError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OhlcvData {
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DailyOhlcv {
pub date: NaiveDate,
pub data: OhlcvData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MinuteOhlcv {
pub timestamp: DateTime<Utc>,
pub data: OhlcvData,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Signal {
Buy,
Sell,
Hold,
}
pub trait TradingStrategy {
fn generate_signals(&self, data: &[DailyOhlcv]) -> Result<Vec<Signal>, TradeError>;
fn calculate_performance(
&self,
data: &[DailyOhlcv],
signals: &[Signal],
) -> Result<f64, TradeError>;
}
pub trait IntradayTradingStrategy {
fn generate_signals(&self, data: &[MinuteOhlcv]) -> Result<Vec<Signal>, TradeError>;
fn calculate_performance(
&self,
data: &[MinuteOhlcv],
signals: &[Signal],
) -> Result<f64, TradeError>;
}
pub trait RealtimeTradingStrategy {
fn update(
&mut self,
timestamp: DateTime<Utc>,
open: f64,
high: f64,
low: f64,
close: f64,
volume: f64,
) -> Result<(), String>;
fn generate_signal(&self) -> Result<i8, String>;
fn name(&self) -> &str;
fn reset(&mut self);
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
fn create_test_data() -> Vec<DailyOhlcv> {
vec![
DailyOhlcv {
date: NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
data: OhlcvData {
open: 100.0,
high: 105.0,
low: 99.0,
close: 102.0,
volume: 1000,
},
},
DailyOhlcv {
date: NaiveDate::from_ymd_opt(2023, 1, 2).unwrap(),
data: OhlcvData {
open: 102.0,
high: 106.0,
low: 101.0,
close: 105.0,
volume: 1200,
},
},
]
}
#[test]
fn test_ohlcv_data_creation() {
let data = create_test_data();
assert_eq!(data.len(), 2);
assert_eq!(data[0].data.close, 102.0);
}
#[test]
fn test_generate_test_data() {
let data = utils::generate_test_data(50, 100.0, 0.05);
assert_eq!(data.len(), 50);
assert!(data[0].data.open == 100.0);
for i in 1..data.len() {
assert!(data[i].date > data[i - 1].date);
}
}
}