ParallelLLC/algorithmic_trading
2732
1import pandas as pd2import numpy as np3from datetime import datetime, timedelta4import logging5from typing import Dict, List, Optional6 7logger = logging.getLogger(__name__)8 9class SyntheticDataGenerator:10 """11 Generates synthetic market data for testing and development purposes.12 Creates realistic price movements with volatility, trends, and market noise.13 """14 15 def __init__(self, config: Dict):16 self.config = config17 self.base_price = config.get('synthetic_data', {}).get('base_price', 100.0)18 self.volatility = config.get('synthetic_data', {}).get('volatility', 0.02)19 self.trend = config.get('synthetic_data', {}).get('trend', 0.001)20 self.noise_level = config.get('synthetic_data', {}).get('noise_level', 0.005)21 22 logger.info(f"Initialized SyntheticDataGenerator with base_price={self.base_price}, "23 f"volatility={self.volatility}, trend={self.trend}")24 25 def generate_ohlcv_data(self, 26 symbol: str = 'AAPL',27 start_date: str = '2024-01-01',28 end_date: str = '2024-12-31',29 frequency: str = '1min') -> pd.DataFrame:30 """31 Generate synthetic OHLCV (Open, High, Low, Close, Volume) data.32 33 Args:34 symbol: Stock symbol35 start_date: Start date in YYYY-MM-DD format36 end_date: End date in YYYY-MM-DD format37 frequency: Data frequency ('1min', '5min', '1H', '1D')38 39 Returns:40 DataFrame with OHLCV data41 """42 logger.info(f"Generating synthetic OHLCV data for {symbol} from {start_date} to {end_date}")43 44 # Create datetime range45 start_dt = pd.to_datetime(start_date)46 end_dt = pd.to_datetime(end_date)47 48 # Generate timestamps based on frequency49 if frequency == '1min' or frequency == '1m':50 timestamps = pd.date_range(start=start_dt, end=end_dt, freq='1min')51 elif frequency == '5min' or frequency == '5m':52 timestamps = pd.date_range(start=start_dt, end=end_dt, freq='5min')53 elif frequency == '1H' or frequency == '1h':54 timestamps = pd.date_range(start=start_dt, end=end_dt, freq='1h')55 elif frequency == '1D' or frequency == '1d':56 timestamps = pd.date_range(start=start_dt, end=end_dt, freq='1D')57 else:58 raise ValueError(f"Unsupported frequency: {frequency}")59 60 # Generate price data61 prices = self._generate_price_series(len(timestamps))62 63 # Generate OHLCV data64 data = []65 current_price = self.base_price66 67 for i, timestamp in enumerate(timestamps):68 # Add trend and noise69 trend_component = self.trend * i70 noise = np.random.normal(0, self.noise_level)71 72 # Generate OHLC from current price73 open_price = current_price * (1 + noise)74 close_price = open_price * (1 + np.random.normal(0, self.volatility))75 76 # Generate high and low77 price_range = abs(close_price - open_price) * np.random.uniform(1.5, 3.0)78 high_price = max(open_price, close_price) + price_range * np.random.uniform(0, 0.5)79 low_price = min(open_price, close_price) - price_range * np.random.uniform(0, 0.5)80 81 # Generate volume (correlated with price movement)82 volume = np.random.randint(1000, 100000) * (1 + abs(close_price - open_price) / open_price)83 84 data.append({85 'timestamp': timestamp,86 'symbol': symbol,87 'open': round(open_price, 2),88 'high': round(high_price, 2),89 'low': round(low_price, 2),90 'close': round(close_price, 2),91 'volume': int(volume)92 })93 94 current_price = close_price95 96 df = pd.DataFrame(data)97 logger.info(f"Generated {len(df)} data points for {symbol}")98 return df99 100 def generate_tick_data(self, 101 symbol: str = 'AAPL',102 duration_minutes: int = 60,103 tick_interval_ms: int = 1000) -> pd.DataFrame:104 """105 Generate high-frequency tick data for testing.106 107 Args:108 symbol: Stock symbol109 duration_minutes: Duration in minutes110 tick_interval_ms: Interval between ticks in milliseconds111 112 Returns:113 DataFrame with tick data114 """115 logger.info(f"Generating tick data for {symbol} for {duration_minutes} minutes")116 117 num_ticks = (duration_minutes * 60 * 1000) // tick_interval_ms118 timestamps = pd.date_range(119 start=datetime.now(),120 periods=num_ticks,121 freq=f'{tick_interval_ms}ms'122 )123 124 # Generate price series with more noise for tick data125 base_prices = self._generate_price_series(num_ticks, volatility=self.volatility * 2)126 127 data = []128 for i, (timestamp, base_price) in enumerate(zip(timestamps, base_prices)):129 # Add micro-movements130 tick_price = base_price * (1 + np.random.normal(0, self.noise_level * 0.5))131 132 data.append({133 'timestamp': timestamp,134 'symbol': symbol,135 'price': round(tick_price, 4),136 'volume': np.random.randint(1, 100)137 })138 139 df = pd.DataFrame(data)140 logger.info(f"Generated {len(df)} tick data points for {symbol}")141 return df142 143 def _generate_price_series(self, length: int, volatility: Optional[float] = None) -> np.ndarray:144 """145 Generate a realistic price series using geometric Brownian motion.146 147 Args:148 length: Number of price points149 volatility: Price volatility (if None, uses self.volatility)150 151 Returns:152 Array of prices153 """154 if volatility is None:155 volatility = self.volatility156 157 # Geometric Brownian motion parameters158 mu = self.trend # drift159 sigma = volatility # volatility160 161 # Generate random walks162 dt = 1.0 / length163 t = np.linspace(0, 1, length)164 165 # Brownian motion166 dW = np.random.normal(0, np.sqrt(dt), length)167 W = np.cumsum(dW)168 169 # Geometric Brownian motion170 S = self.base_price * np.exp((mu - 0.5 * sigma**2) * t + sigma * W)171 172 return S173 174 def save_to_csv(self, df: pd.DataFrame, filepath: str) -> None:175 """176 Save generated data to CSV file.177 178 Args:179 df: DataFrame to save180 filepath: Path to save the CSV file181 """182 df.to_csv(filepath, index=False)183 logger.info(f"Saved synthetic data to {filepath}")184 185 def generate_market_scenarios(self, scenario_type: str = 'normal') -> pd.DataFrame:186 """187 Generate data for different market scenarios.188 189 Args:190 scenario_type: Type of scenario ('normal', 'volatile', 'trending', 'crash')191 192 Returns:193 DataFrame with scenario-specific data194 """195 logger.info(f"Generating {scenario_type} market scenario")196 197 if scenario_type == 'normal':198 return self.generate_ohlcv_data()199 elif scenario_type == 'volatile':200 # High volatility scenario201 self.volatility *= 3202 data = self.generate_ohlcv_data()203 self.volatility /= 3 # Reset204 return data205 elif scenario_type == 'trending':206 # Strong upward trend207 self.trend *= 5208 data = self.generate_ohlcv_data()209 self.trend /= 5 # Reset210 return data211 elif scenario_type == 'crash':212 # Market crash scenario213 original_volatility = self.volatility214 original_trend = self.trend215 216 self.volatility *= 5217 self.trend = -0.01 # Strong downward trend218 219 try:220 data = self.generate_ohlcv_data()221 finally:222 # Reset parameters223 self.volatility = original_volatility224 self.trend = original_trend225 226 return data227 else:228 raise ValueError(f"Unknown scenario type: {scenario_type}") 229 230 def generate_data(self) -> pd.DataFrame:231 """232 Generate synthetic OHLCV data using config defaults.233 Returns:234 DataFrame with OHLCV data235 """236 symbol = self.config.get('trading', {}).get('symbol', 'AAPL')237 start_date = self.config.get('synthetic_data', {}).get('start_date', '2024-01-01')238 end_date = self.config.get('synthetic_data', {}).get('end_date', '2024-12-31')239 frequency = self.config.get('synthetic_data', {}).get('frequency', '1min')240 return self.generate_ohlcv_data(symbol=symbol, start_date=start_date, end_date=end_date, frequency=frequency) 