import random import time from datetime import datetime, timedelta from typing import List, Optional from app.schemas.schemas import PriceData class GoldPriceSimulator: """ Simulates realistic gold price movements without external API calls. Uses Geometric Brownian Motion for realistic price action. """ def __init__(self, initial_price: float = 2650.0): """ Initialize the simulator with a starting price. Args: initial_price: Starting gold price in USD per oz (default ~current market price) """ self.base_price = initial_price self.current_price = initial_price self.volatility = 0.0008 # Daily volatility (0.08%) self.drift = 0.00001 # Slight upward drift self.last_update = time.time() # For trend simulation self.trend_direction = 1 # 1 for up, -1 for down self.trend_strength = 0.0001 self.trend_duration = 0 self.max_trend_duration = 100 # Max ticks before trend change def _calculate_price_change(self) -> float: """Calculate the next price change using Geometric Brownian Motion.""" # Random walk component random_shock = random.gauss(0, 1) * self.volatility # Trend component (changes periodically) self.trend_duration += 1 if self.trend_duration > self.max_trend_duration: # Change trend direction self.trend_direction = random.choice([1, -1]) self.trend_strength = random.uniform(0.00005, 0.0002) self.trend_duration = 0 self.max_trend_duration = random.randint(50, 200) trend_component = self.trend_direction * self.trend_strength # Mean reversion (pulls price back toward base) mean_reversion = (self.base_price - self.current_price) * 0.00001 # Combine components total_change = self.drift + random_shock + trend_component + mean_reversion return self.current_price * total_change def get_current_price(self) -> float: """Get the current simulated gold price.""" # Update price based on time elapsed current_time = time.time() time_elapsed = current_time - self.last_update # Update price (simulating continuous price movement) if time_elapsed > 0: # Multiple small updates for smoother price action updates = max(1, int(time_elapsed)) for _ in range(min(updates, 10)): # Cap at 10 updates to avoid huge jumps price_change = self._calculate_price_change() self.current_price += price_change # Keep price within reasonable bounds (±20% from base) self.current_price = max( self.base_price * 0.8, min(self.base_price * 1.2, self.current_price) ) self.last_update = current_time return round(self.current_price, 2) def get_live_candle(self, interval: str = "1min") -> PriceData: """ Generate a live price candle for the current interval. Args: interval: Time interval (1min, 5min, 15min, 30min, 60min) Returns: PriceData object with OHLC values """ current_price = self.get_current_price() # Map intervals to seconds interval_map = { "1min": 60, "5min": 5 * 60, "15min": 15 * 60, "30min": 30 * 60, "60min": 60 * 60, } interval_seconds = interval_map.get(interval, 60) current_time = int(time.time()) # Round up to next interval boundary to ensure newest timestamp timestamp = ((current_time // interval_seconds) + 1) * interval_seconds # Generate OHLC with small realistic variance variance = current_price * 0.0005 # 0.05% variance open_price = current_price + random.uniform(-variance, variance) close_price = current_price + random.uniform(-variance, variance) high_price = max(open_price, close_price) + random.uniform(0, variance) low_price = min(open_price, close_price) - random.uniform(0, variance) return PriceData( time=timestamp, open=round(open_price, 2), high=round(high_price, 2), low=round(low_price, 2), close=round(close_price, 2), ) def generate_historical_data( self, interval: str = "daily", points: int = 100 ) -> List[PriceData]: """ Generate historical price data using the simulator. Args: interval: Time interval (daily, 1min, 5min, etc.) points: Number of data points to generate Returns: List of PriceData objects in chronological order """ # Map intervals to seconds interval_map = { "daily": 24 * 60 * 60, "1min": 60, "5min": 5 * 60, "15min": 15 * 60, "30min": 30 * 60, "60min": 60 * 60, } interval_seconds = interval_map.get(interval, 24 * 60 * 60) # Start from past and work forward end_time = int(time.time()) start_time = end_time - (interval_seconds * points) price_data = [] current_sim_price = self.base_price for i in range(points): timestamp = start_time + (interval_seconds * i) # Simulate price evolution price_change = random.gauss(0, 1) * self.volatility * current_sim_price trend = random.uniform(-0.0001, 0.0001) * current_sim_price current_sim_price += price_change + trend # Keep within bounds current_sim_price = max( self.base_price * 0.85, min(self.base_price * 1.15, current_sim_price) ) # Generate OHLC for this candle candle_variance = current_sim_price * 0.002 # 0.2% intra-candle variance open_price = current_sim_price + random.uniform(-candle_variance/2, candle_variance/2) close_price = current_sim_price + random.uniform(-candle_variance/2, candle_variance/2) high_price = max(open_price, close_price) + random.uniform(0, candle_variance) low_price = min(open_price, close_price) - random.uniform(0, candle_variance) price_data.append( PriceData( time=timestamp, open=round(open_price, 2), high=round(high_price, 2), low=round(low_price, 2), close=round(close_price, 2), ) ) # Set current price to the last closing price for continuity if price_data: self.current_price = price_data[-1].close self.last_update = time.time() return price_data # Global simulator instance (maintains state across requests) gold_simulator = GoldPriceSimulator(initial_price=2650.0)