use chrono::{DateTime, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
pub mod strategies;
pub mod utils;
pub use strategies::mean_reversion::{
MeanReversionOscillatorStrategy, StatisticalArbitrageStrategy,
};
pub use strategies::momentum::{MomentumBreakoutStrategy, ScalpingStrategy};
pub use strategies::pattern::{ChartPatternStrategy, SupportResistanceStrategy};
pub use strategies::statistical::{RegressionStrategy, ZScoreStrategy};
pub use strategies::time_based::{SessionTransitionStrategy, TimeOfDayStrategy};
pub use strategies::volatility::{BollingerBandContractionStrategy, VolatilityBreakoutStrategy};
pub use strategies::volume::{RelativeVolumeStrategy, VolumeProfileStrategy};
#[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),
#[error("Data loading error: {0}")]
DataLoadError(String),
#[error("Parameter validation error: {0}")]
ParameterError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OhlcvData {
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
#[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,
}
impl fmt::Display for Signal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Signal::Buy => write!(f, "Buy"),
Signal::Sell => write!(f, "Sell"),
Signal::Hold => write!(f, "Hold"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SignalStrength {
StrongBuy = 2,
Buy = 1,
Neutral = 0,
Sell = -1,
StrongSell = -2,
}
impl From<SignalStrength> for Signal {
fn from(strength: SignalStrength) -> Self {
match strength {
SignalStrength::StrongBuy | SignalStrength::Buy => Signal::Buy,
SignalStrength::StrongSell | SignalStrength::Sell => Signal::Sell,
SignalStrength::Neutral => Signal::Hold,
}
}
}
pub trait IntradayStrategy {
fn name(&self) -> &str;
fn generate_signals(&self, data: &[MinuteOhlcv]) -> Result<Vec<Signal>, TradeError>;
fn calculate_performance(
&self,
data: &[MinuteOhlcv],
signals: &[Signal],
) -> Result<f64, TradeError>;
fn reset(&mut self) {}
}
#[derive(Debug, Clone)]
pub struct Trade {
pub entry_time: DateTime<Utc>,
pub exit_time: Option<DateTime<Utc>>,
pub entry_price: f64,
pub exit_price: Option<f64>,
pub size: f64,
pub is_long: bool,
pub pnl: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub total_return: f64,
pub annualized_return: f64,
pub sharpe_ratio: f64,
pub max_drawdown: f64,
pub win_rate: f64,
pub profit_factor: f64,
pub total_trades: usize,
}
pub fn create_test_data(length: usize) -> Vec<MinuteOhlcv> {
let mut data = Vec::with_capacity(length);
let base_price = 100.0;
for i in 0..length {
let timestamp = Utc
.ymd_opt(2023, 1, 1)
.unwrap()
.and_hms_opt(9, i as u32 % 60, 0)
.unwrap();
let close = base_price + (i as f64 * 0.01).sin() * 2.0;
let open = close - 0.1 + (i as f64 * 0.005).cos() * 0.2;
let high = close.max(open) + 0.1 + (i as f64 * 0.02).cos() * 0.1;
let low = close.min(open) - 0.1 + (i as f64 * 0.02).sin() * 0.1;
let volume = 1000.0 + (i as f64 * 0.1).cos() * 500.0;
data.push(MinuteOhlcv {
timestamp,
data: OhlcvData {
open,
high,
low,
close,
volume,
},
});
}
data
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn test_signal_conversion() {
assert_eq!(Signal::from(SignalStrength::StrongBuy), Signal::Buy);
assert_eq!(Signal::from(SignalStrength::Buy), Signal::Buy);
assert_eq!(Signal::from(SignalStrength::Neutral), Signal::Hold);
assert_eq!(Signal::from(SignalStrength::Sell), Signal::Sell);
assert_eq!(Signal::from(SignalStrength::StrongSell), Signal::Sell);
}
}