use crate::day_trade::{DailyOhlcv, OhlcvData, Signal, TradeError};
pub fn calculate_basic_performance(
data: &[DailyOhlcv],
signals: &[Signal],
initial_cash: f64,
) -> Result<f64, TradeError> {
if data.len() != signals.len() {
return Err(TradeError::InvalidData(
"Data and signals arrays must be the same length".to_string(),
));
}
if data.len() <= 1 {
return Err(TradeError::InsufficientData(
"Need at least 2 data points to calculate performance".to_string(),
));
}
let mut cash = initial_cash;
let mut shares = 0.0;
for i in 1..data.len() {
match signals[i - 1] {
Signal::Buy => {
shares = cash / data[i].data.open;
cash = 0.0;
}
Signal::Sell => {
cash += shares * data[i].data.open;
shares = 0.0;
}
Signal::Hold => {} }
}
let final_value = cash + shares * data.last().unwrap().data.close;
let performance = (final_value / initial_cash - 1.0) * 100.0;
Ok(performance)
}
pub fn generate_test_data(
num_points: usize,
starting_price: f64,
volatility: f64,
) -> Vec<DailyOhlcv> {
use chrono::NaiveDate;
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
let mut data = Vec::with_capacity(num_points);
let mut current_price = starting_price;
let base_date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
for i in 0..num_points {
let price_change = current_price * volatility * (rng.gen::<f64>() - 0.5);
let open = current_price;
let close = open + price_change;
let high = open.max(close) + rng.gen::<f64>() * volatility * open * 0.5;
let low = open.min(close) - rng.gen::<f64>() * volatility * open * 0.5;
let volume = rng.gen_range(1000..10000);
let date = base_date
.checked_add_days(chrono::Days::new(i as u64))
.unwrap();
data.push(DailyOhlcv {
date,
data: OhlcvData {
open,
high,
low,
close,
volume,
},
});
current_price = close;
}
data
}
pub fn validate_period(period: usize, min_value: usize) -> Result<(), String> {
if period < min_value {
return Err(format!("Period must be at least {}", min_value));
}
Ok(())
}
pub fn validate_positive(value: f64, name: &str) -> Result<(), String> {
if value <= 0.0 {
return Err(format!("{} must be positive", name));
}
Ok(())
}
pub fn validate_range(value: f64, min: f64, max: f64, name: &str) -> Result<(), String> {
if value < min || value > max {
return Err(format!("{} must be between {} and {}", name, min, max));
}
Ok(())
}
pub mod data_generation {
use crate::day_trade::{DailyOhlcv, OhlcvData};
use chrono::NaiveDate;
use rand::Rng;
pub fn generate_daily_data(
days: usize,
starting_price: f64,
volatility: f64,
trend: f64,
) -> Vec<DailyOhlcv> {
let mut data = Vec::with_capacity(days);
let mut current_price = starting_price;
use rand::thread_rng;
let mut rng = thread_rng();
for i in 0..days {
let price_change = current_price * volatility * (rng.gen::<f64>() - 0.5);
current_price = current_price * (1.0 + trend) + price_change;
let open = if i == 0 {
starting_price
} else {
current_price * (1.0 + (rng.gen::<f64>() - 0.5) * 0.01)
};
let close = current_price;
let high = open.max(close) + rng.gen::<f64>() * volatility * open * 0.5;
let low = open.min(close) - rng.gen::<f64>() * volatility * open * 0.5;
let volume = rng.gen_range(1000..10000);
let date = NaiveDate::from_ymd_opt(2023, 1, i as u32 % 28 + 1).unwrap();
data.push(DailyOhlcv {
date,
data: OhlcvData {
open,
high,
low,
close,
volume,
},
});
}
data
}
}