using TradingBot.Models; namespace TradingBot.Services; public class SimulatedMarketDataService : IMarketDataService { private readonly Dictionary _assets = new(); private readonly Random _random = new(); private readonly Timer _updateTimer; private readonly object _lock = new(); public event Action? OnPriceUpdated; public SimulatedMarketDataService() { InitializeAssets(); _updateTimer = new Timer(UpdatePrices, null, TimeSpan.Zero, TimeSpan.FromSeconds(2)); } private void InitializeAssets() { var assets = new[] { new { Symbol = "BTC", Name = "Bitcoin", BasePrice = 45000m, Volatility = 0.02m, TrendBias = 0.0002m }, new { Symbol = "ETH", Name = "Ethereum", BasePrice = 2500m, Volatility = 0.025m, TrendBias = 0.0003m }, new { Symbol = "BNB", Name = "Binance Coin", BasePrice = 350m, Volatility = 0.03m, TrendBias = 0.0001m }, new { Symbol = "SOL", Name = "Solana", BasePrice = 100m, Volatility = 0.035m, TrendBias = 0.0004m }, new { Symbol = "ADA", Name = "Cardano", BasePrice = 0.45m, Volatility = 0.028m, TrendBias = 0.0002m }, new { Symbol = "XRP", Name = "Ripple", BasePrice = 0.65m, Volatility = 0.032m, TrendBias = 0.0001m }, new { Symbol = "DOT", Name = "Polkadot", BasePrice = 6.5m, Volatility = 0.03m, TrendBias = 0.0003m }, new { Symbol = "AVAX", Name = "Avalanche", BasePrice = 35m, Volatility = 0.038m, TrendBias = 0.0005m }, new { Symbol = "MATIC", Name = "Polygon", BasePrice = 0.85m, Volatility = 0.033m, TrendBias = 0.0002m }, new { Symbol = "LINK", Name = "Chainlink", BasePrice = 15m, Volatility = 0.029m, TrendBias = 0.0003m }, new { Symbol = "UNI", Name = "Uniswap", BasePrice = 6.5m, Volatility = 0.031m, TrendBias = 0.0001m }, new { Symbol = "ATOM", Name = "Cosmos", BasePrice = 10m, Volatility = 0.03m, TrendBias = 0.0004m }, new { Symbol = "LTC", Name = "Litecoin", BasePrice = 75m, Volatility = 0.025m, TrendBias = 0.0001m }, new { Symbol = "ALGO", Name = "Algorand", BasePrice = 0.25m, Volatility = 0.032m, TrendBias = 0.0003m }, new { Symbol = "VET", Name = "VeChain", BasePrice = 0.03m, Volatility = 0.035m, TrendBias = 0.0002m } }; foreach (var asset in assets) { _assets[asset.Symbol] = new SimulatedAsset { Symbol = asset.Symbol, Name = asset.Name, CurrentPrice = asset.BasePrice, BasePrice = asset.BasePrice, Volatility = asset.Volatility, TrendBias = asset.TrendBias, LastUpdate = DateTime.UtcNow }; } } private void UpdatePrices(object? state) { lock (_lock) { var now = DateTime.UtcNow; foreach (var asset in _assets.Values) { // Calculate time-based factors var timeSinceStart = (now - asset.LastUpdate).TotalSeconds; // Generate random walk with trend var randomChange = (_random.NextDouble() - 0.5) * 2 * (double)asset.Volatility; var trendComponent = (double)asset.TrendBias; // Add market cycles (sine wave for realistic market behavior) var cycleComponent = Math.Sin((double)asset.PriceUpdateCount / 100.0) * 0.001; // Combine all factors var totalChange = randomChange + trendComponent + cycleComponent; // Update price var newPrice = asset.CurrentPrice * (1 + (decimal)totalChange); // Keep price within reasonable bounds (50% to 200% of base price) newPrice = Math.Max(asset.BasePrice * 0.5m, Math.Min(asset.BasePrice * 2.0m, newPrice)); // Calculate change and volume var priceChange = newPrice - asset.CurrentPrice; var changePercentage = asset.CurrentPrice > 0 ? (priceChange / asset.CurrentPrice) * 100 : 0; // Simulate volume based on volatility and price change var baseVolume = asset.BasePrice * 1000000m; var volumeVariation = (decimal)(_random.NextDouble() * 0.5 + 0.75); // 75% to 125% var volumeFromVolatility = Math.Abs(changePercentage) * 100000m; asset.CurrentPrice = newPrice; asset.Change24h = changePercentage; asset.Volume24h = (baseVolume + volumeFromVolatility) * volumeVariation; asset.LastUpdate = now; asset.PriceUpdateCount++; // Add to history asset.PriceHistory.Add(new MarketPrice { Symbol = asset.Symbol, Price = newPrice, Change24h = changePercentage, Volume24h = asset.Volume24h, Timestamp = now }); // Keep history limited to last 500 points if (asset.PriceHistory.Count > 500) { asset.PriceHistory.RemoveAt(0); } } OnPriceUpdated?.Invoke(); } } public Task> GetMarketPricesAsync(List symbols) { lock (_lock) { var prices = new List(); foreach (var symbol in symbols) { if (_assets.TryGetValue(symbol, out var asset)) { prices.Add(new MarketPrice { Symbol = asset.Symbol, Price = asset.CurrentPrice, Change24h = asset.Change24h, Volume24h = asset.Volume24h, Timestamp = asset.LastUpdate }); } } return Task.FromResult(prices); } } public Task GetPriceAsync(string symbol) { lock (_lock) { if (_assets.TryGetValue(symbol, out var asset)) { return Task.FromResult(new MarketPrice { Symbol = asset.Symbol, Price = asset.CurrentPrice, Change24h = asset.Change24h, Volume24h = asset.Volume24h, Timestamp = asset.LastUpdate }); } return Task.FromResult(null); } } public List GetPriceHistory(string symbol, int count = 100) { lock (_lock) { if (_assets.TryGetValue(symbol, out var asset)) { return asset.PriceHistory .Skip(Math.Max(0, asset.PriceHistory.Count - count)) .ToList(); } return new List(); } } public List GetAvailableSymbols() { lock (_lock) { return _assets.Keys.OrderBy(s => s).ToList(); } } public Dictionary GetAssetNames() { lock (_lock) { return _assets.ToDictionary(a => a.Key, a => a.Value.Name); } } private class SimulatedAsset { public string Symbol { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public decimal CurrentPrice { get; set; } public decimal BasePrice { get; set; } public decimal Change24h { get; set; } public decimal Volume24h { get; set; } public decimal Volatility { get; set; } public decimal TrendBias { get; set; } public DateTime LastUpdate { get; set; } public int PriceUpdateCount { get; set; } public List PriceHistory { get; set; } = new(); } }