#![allow(unused)]
use rand::SeedableRng;
use rand::rngs::StdRng;
use rust_decimal::Decimal;
use rust_decimal::prelude::FromPrimitive;
use crate::algorithms::RandomWalkGenerator;
use crate::config::GeneratorConfig;
use crate::types::{OHLC, Tick};
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(feature = "regimes")]
use crate::regimes::{RegimeDetector, RegimeState, RegimeConfig, RegimeTracker, VolatilityRegimeDetector, RegimeController, RegimeSchedule, ScheduleInfo};
pub struct MarketDataGenerator {
rng: StdRng,
config: GeneratorConfig,
price_generator: RandomWalkGenerator,
current_timestamp: i64,
#[cfg(feature = "regimes")]
regime_detector: Option<Box<dyn RegimeDetector>>,
#[cfg(feature = "regimes")]
regime_tracker: Option<RegimeTracker>,
#[cfg(feature = "regimes")]
data_buffer: Vec<OHLC>,
#[cfg(feature = "regimes")]
regime_controller: Option<RegimeController>,
}
impl MarketDataGenerator {
pub fn new() -> Self {
Self::with_config(GeneratorConfig::default()).expect("Default config should be valid")
}
pub fn with_config(config: GeneratorConfig) -> Result<Self, String> {
config.validate()
.map_err(|e| format!("Invalid configuration: {e}"))?;
let rng = match config.seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::from_entropy(),
};
let price_generator = RandomWalkGenerator::new(config.clone())?;
let current_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| format!("Failed to get system time: {e}"))?
.as_millis() as i64;
Ok(Self {
rng,
config: config.clone(),
price_generator,
current_timestamp,
#[cfg(feature = "regimes")]
regime_detector: None,
#[cfg(feature = "regimes")]
regime_tracker: None,
#[cfg(feature = "regimes")]
data_buffer: Vec::with_capacity(100),
#[cfg(feature = "regimes")]
regime_controller: None,
})
}
pub fn generate_ohlc(&mut self) -> OHLC {
#[cfg(feature = "regimes")]
self.update_regime_controller();
let (open, high, low, close) = self.price_generator.generate_ohlc(&mut self.rng, 10);
let volume = self.price_generator.generate_volume(&mut self.rng);
let timestamp = self.current_timestamp;
self.current_timestamp += self.config.time_interval.millis() as i64;
let candle = OHLC::new(open, high, low, close, volume, timestamp);
#[cfg(feature = "regimes")]
self.update_regime_detection(&candle);
candle
}
#[deprecated(since = "0.2.0", note = "Use generate_ohlc() instead")]
pub fn generate_candle(&mut self) -> OHLC {
self.generate_ohlc()
}
pub fn generate_series(&mut self, count: usize) -> Vec<OHLC> {
let mut candles = Vec::with_capacity(count);
for _ in 0..count {
candles.push(self.generate_ohlc());
}
candles
}
pub fn generate_tick(&mut self) -> Tick {
let price = self.price_generator.next_price(&mut self.rng);
let volume = self.price_generator.generate_volume(&mut self.rng);
let timestamp = self.current_timestamp;
self.current_timestamp += 1000;
let spread = Decimal::from_f64(0.001).unwrap(); let half_spread = price * spread / Decimal::from(2);
Tick::with_spread(
price,
volume,
timestamp,
price - half_spread,
price + half_spread,
)
}
pub fn generate_ticks(&mut self, count: usize) -> Vec<Tick> {
let mut ticks = Vec::with_capacity(count);
for _ in 0..count {
ticks.push(self.generate_tick());
}
ticks
}
pub fn reset(&mut self) {
self.price_generator.reset();
self.current_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
if let Some(seed) = self.config.seed {
self.rng = StdRng::seed_from_u64(seed);
}
}
pub fn set_timestamp(&mut self, timestamp: i64) {
self.current_timestamp = timestamp;
}
pub fn config(&self) -> &GeneratorConfig {
&self.config
}
pub fn set_config(&mut self, config: GeneratorConfig) -> Result<(), String> {
config.validate()
.map_err(|e| format!("Invalid configuration: {e}"))?;
self.config = config.clone();
self.price_generator = RandomWalkGenerator::new(config)?;
if let Some(seed) = self.config.seed {
self.rng = StdRng::seed_from_u64(seed);
}
Ok(())
}
#[cfg(feature = "csv_export")]
pub fn generate_to_csv_ohlc<P: AsRef<std::path::Path>>(
&mut self,
count: usize,
path: P,
) -> Result<(), Box<dyn std::error::Error>> {
let data = self.generate_series(count);
crate::export::to_csv_ohlc(&data, path)?;
Ok(())
}
#[cfg(feature = "csv_export")]
pub fn generate_to_csv_ticks<P: AsRef<std::path::Path>>(
&mut self,
count: usize,
path: P,
) -> Result<(), Box<dyn std::error::Error>> {
let data = self.generate_ticks(count);
crate::export::to_csv_ticks(&data, path)?;
Ok(())
}
#[cfg(feature = "csv_export")]
pub fn stream_generate_to_csv_ohlc<P: AsRef<std::path::Path>>(
&mut self,
count: usize,
path: P,
) -> Result<usize, Box<dyn std::error::Error>> {
use crate::export::csv::CsvExporter;
let exporter = CsvExporter::default();
let iter = (0..count).map(|_| self.generate_ohlc());
Ok(exporter.stream_ohlc(iter, path)?)
}
#[cfg(feature = "csv_export")]
pub fn stream_generate_to_csv_ticks<P: AsRef<std::path::Path>>(
&mut self,
count: usize,
path: P,
) -> Result<usize, Box<dyn std::error::Error>> {
use crate::export::csv::CsvExporter;
let exporter = CsvExporter::default();
let iter = (0..count).map(|_| self.generate_tick());
Ok(exporter.stream_ticks(iter, path)?)
}
}
#[cfg(feature = "regimes")]
impl MarketDataGenerator {
pub fn enable_volatility_regime_detection(&mut self, window_size: usize) {
self.regime_detector = Some(Box::new(VolatilityRegimeDetector::new(window_size)));
self.regime_tracker = Some(RegimeTracker::new(1000));
}
pub fn disable_regime_detection(&mut self) {
self.regime_detector = None;
self.regime_tracker = None;
self.data_buffer.clear();
}
pub fn current_regime(&self) -> Option<&RegimeState> {
self.regime_tracker.as_ref()?.current()
}
pub fn regime_analytics(&self) -> Option<RegimeAnalytics> {
let tracker = self.regime_tracker.as_ref()?;
Some(RegimeAnalytics {
current_regime: tracker.current().cloned(),
transitions: tracker.transitions,
average_duration: tracker.average_duration(),
regime_distribution: tracker.regime_distribution(),
})
}
fn update_regime_detection(&mut self, candle: &OHLC) {
self.data_buffer.push(candle.clone());
while self.data_buffer.len() > 200 {
self.data_buffer.remove(0);
}
if let Some(ref mut detector) = self.regime_detector {
let config = RegimeConfig::default();
if let Some(state) = detector.update(candle, &config) {
if let Some(ref mut tracker) = self.regime_tracker {
tracker.record(state);
}
}
}
}
pub fn generate_regime_aware_series(&mut self, count: usize, _regime_config: RegimeConfig) -> Vec<RegimeOHLC> {
let mut series = Vec::with_capacity(count);
for _ in 0..count {
let candle = self.generate_ohlc();
let current_regime = self.current_regime().cloned();
series.push(RegimeOHLC {
ohlc: candle,
regime_state: current_regime,
});
}
series
}
pub fn generate_controlled_regime_series(&mut self, count: usize) -> Vec<ControlledRegimeOHLC> {
let mut series = Vec::with_capacity(count);
for _ in 0..count {
let schedule_info = self.regime_control_info();
let candle = self.generate_ohlc();
series.push(ControlledRegimeOHLC {
ohlc: candle,
schedule_info: schedule_info.clone(),
});
}
series
}
pub fn data_buffer(&self) -> &[OHLC] {
&self.data_buffer
}
pub fn detect_regime_on_buffer(&mut self) -> Option<RegimeState> {
if let Some(ref mut detector) = self.regime_detector {
let config = RegimeConfig::default();
detector.detect(&self.data_buffer, &config)
} else {
None
}
}
pub fn enable_regime_control(&mut self, schedule: RegimeSchedule) {
let base_config = self.config.clone();
self.regime_controller = Some(RegimeController::new(schedule, base_config));
}
pub fn disable_regime_control(&mut self) {
self.regime_controller = None;
}
pub fn regime_control_info(&self) -> Option<ScheduleInfo> {
self.regime_controller.as_ref().map(|c| c.schedule_info())
}
pub fn force_regime(&mut self, regime: crate::regimes::MarketRegime, duration: usize, transition_duration: Option<usize>) {
if let Some(ref mut controller) = self.regime_controller {
controller.force_regime(regime, duration, transition_duration);
}
}
pub fn add_regime_segment(&mut self, segment: crate::regimes::RegimeSegment) {
if let Some(ref mut controller) = self.regime_controller {
controller.add_segment(segment);
}
}
pub fn reset_regime_schedule(&mut self) {
if let Some(ref mut controller) = self.regime_controller {
controller.reset();
}
}
fn update_regime_controller(&mut self) {
if let Some(ref mut controller) = self.regime_controller {
controller.advance();
let new_config = controller.current_config().clone();
if let Ok(new_price_generator) = RandomWalkGenerator::new(new_config.clone()) {
self.config = new_config;
self.price_generator = new_price_generator;
}
}
}
}
#[cfg(feature = "regimes")]
#[derive(Debug, Clone)]
pub struct RegimeOHLC {
pub ohlc: OHLC,
pub regime_state: Option<RegimeState>,
}
#[cfg(feature = "regimes")]
#[derive(Debug, Clone)]
pub struct ControlledRegimeOHLC {
pub ohlc: OHLC,
pub schedule_info: Option<ScheduleInfo>,
}
#[cfg(feature = "regimes")]
#[derive(Debug, Clone)]
pub struct RegimeAnalytics {
pub current_regime: Option<RegimeState>,
pub transitions: usize,
pub average_duration: Decimal,
pub regime_distribution: (Decimal, Decimal, Decimal),
}
impl Default for MarketDataGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TrendDirection;
use crate::config::ConfigBuilder;
#[test]
fn test_generator_creation() {
let generator = MarketDataGenerator::new();
assert_eq!(generator.config().starting_price, Decimal::from_f64(100.0).unwrap());
}
#[test]
fn test_generator_with_config() {
let config = ConfigBuilder::new()
.starting_price_f64(50.0)
.volatility_f64(0.03)
.seed(42)
.build()
.unwrap();
let generator = MarketDataGenerator::with_config(config);
assert!(generator.is_ok());
}
#[test]
fn test_candle_generation() {
let config = ConfigBuilder::new()
.seed(42)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let candle = generator.generate_ohlc();
assert!(candle.is_valid());
assert!(candle.volume.value() > 0);
}
#[test]
fn test_series_generation() {
let config = ConfigBuilder::new()
.seed(42)
.num_points(10)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let series = generator.generate_series(10);
assert_eq!(series.len(), 10);
for i in 1..series.len() {
assert!(series[i].timestamp > series[i-1].timestamp);
}
}
#[test]
fn test_tick_generation() {
let mut generator = MarketDataGenerator::new();
let tick = generator.generate_tick();
assert!(tick.price > Decimal::ZERO);
assert!(tick.volume.value() > 0);
assert!(tick.bid.is_some());
assert!(tick.ask.is_some());
if let (Some(bid), Some(ask)) = (tick.bid, tick.ask) {
assert!(ask > bid); }
}
#[test]
fn test_deterministic_generation() {
let config = ConfigBuilder::new()
.seed(42)
.build()
.unwrap();
let mut gen1 = MarketDataGenerator::with_config(config.clone()).unwrap();
let mut gen2 = MarketDataGenerator::with_config(config).unwrap();
let candles1 = gen1.generate_series(5);
let candles2 = gen2.generate_series(5);
for (c1, c2) in candles1.iter().zip(candles2.iter()) {
assert_eq!(c1.open, c2.open);
assert_eq!(c1.close, c2.close);
}
}
#[test]
fn test_reset() {
let config = ConfigBuilder::new()
.seed(42)
.starting_price_f64(100.0)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let candle1 = generator.generate_ohlc();
generator.reset();
let candle2 = generator.generate_ohlc();
assert_eq!(candle1.open, candle2.open);
}
#[cfg(feature = "regimes")]
#[test]
fn test_regime_control_basic() {
use crate::regimes::{RegimeSchedule, RegimeSegment, MarketRegime};
use crate::config::TrendDirection;
let config = ConfigBuilder::new()
.seed(42)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let segments = vec![
RegimeSegment::new(MarketRegime::Bull, 5),
RegimeSegment::new(MarketRegime::Bear, 5),
];
let schedule = RegimeSchedule::new(segments);
generator.enable_regime_control(schedule);
let bull_candles = generator.generate_series(5);
let info = generator.regime_control_info().unwrap();
assert_eq!(info.current_regime, Some(MarketRegime::Bear));
let bear_candles = generator.generate_series(5);
let info = generator.regime_control_info().unwrap();
assert!(info.is_complete);
assert_eq!(bull_candles.len(), 5);
assert_eq!(bear_candles.len(), 5);
for candle in &bull_candles {
assert!(candle.is_valid());
}
for candle in &bear_candles {
assert!(candle.is_valid());
}
}
#[cfg(feature = "regimes")]
#[test]
fn test_regime_control_parameter_changes() {
use crate::regimes::{RegimeSchedule, RegimeSegment, MarketRegime};
let config = ConfigBuilder::new()
.seed(42)
.trend_f64(TrendDirection::Sideways, 0.001) .build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let segments = vec![
RegimeSegment::new(MarketRegime::Bull, 3),
RegimeSegment::new(MarketRegime::Sideways, 3),
];
let schedule = RegimeSchedule::new(segments);
generator.enable_regime_control(schedule);
let config1 = generator.config().clone();
generator.generate_ohlc();
generator.generate_ohlc();
assert_eq!(generator.config().trend_direction, TrendDirection::Bullish);
generator.generate_ohlc();
let config2 = generator.config().clone();
assert_eq!(config2.trend_direction, TrendDirection::Sideways);
assert_ne!(config1.trend_strength, config2.trend_strength); }
#[cfg(feature = "regimes")]
#[test]
fn test_regime_control_force_regime() {
use crate::regimes::{RegimeSchedule, RegimeSegment, MarketRegime};
let config = ConfigBuilder::new()
.seed(42)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let segments = vec![
RegimeSegment::new(MarketRegime::Bull, 10),
];
let schedule = RegimeSchedule::new(segments);
generator.enable_regime_control(schedule);
let info = generator.regime_control_info().unwrap();
assert_eq!(info.current_regime, Some(MarketRegime::Bull));
generator.force_regime(MarketRegime::Bear, 5, None);
let info = generator.regime_control_info().unwrap();
assert_eq!(info.current_regime, Some(MarketRegime::Bear));
}
#[cfg(feature = "regimes")]
#[test]
fn test_controlled_regime_series() {
use crate::regimes::{RegimeSchedule, RegimeSegment, MarketRegime};
let config = ConfigBuilder::new()
.seed(42)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let segments = vec![
RegimeSegment::new(MarketRegime::Bull, 3),
RegimeSegment::new(MarketRegime::Bear, 2),
];
let schedule = RegimeSchedule::new(segments);
generator.enable_regime_control(schedule);
let series = generator.generate_controlled_regime_series(5);
assert_eq!(series.len(), 5);
for i in 0..3 {
if let Some(ref info) = series[i].schedule_info {
assert_eq!(info.current_regime, Some(MarketRegime::Bull));
}
}
for i in 3..5 {
if let Some(ref info) = series[i].schedule_info {
assert_eq!(info.current_regime, Some(MarketRegime::Bear));
}
}
}
#[cfg(feature = "regimes")]
#[test]
fn test_regime_schedule_reset() {
use crate::regimes::{RegimeSchedule, RegimeSegment, MarketRegime};
let config = ConfigBuilder::new()
.seed(42)
.build()
.unwrap();
let mut generator = MarketDataGenerator::with_config(config).unwrap();
let segments = vec![
RegimeSegment::new(MarketRegime::Bull, 2),
RegimeSegment::new(MarketRegime::Bear, 2),
];
let schedule = RegimeSchedule::new(segments);
generator.enable_regime_control(schedule);
generator.generate_series(4);
let info = generator.regime_control_info().unwrap();
assert!(info.is_complete);
generator.reset_regime_schedule();
let info = generator.regime_control_info().unwrap();
assert!(!info.is_complete);
assert_eq!(info.current_regime, Some(MarketRegime::Bull)); }
}