diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index ccdc8f30..954434f0 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -123,7 +123,34 @@ public class TradingBotBase : ITradingBot { var grainKey = CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe); var grain = grainFactory.GetGrain(grainKey); - LastCandle = await grain.GetLastCandle(); + + try + { + // Add a small delay to ensure grain is fully activated + await Task.Delay(100); + LastCandle = await grain.GetLastCandle(); + } + catch (InvalidOperationException ex) when (ex.Message.Contains("invalid activation")) + { + Logger.LogWarning("Grain activation failed for {GrainKey}, retrying in 1 second...", grainKey); + + // Wait a bit longer and retry once + await Task.Delay(1000); + try + { + LastCandle = await grain.GetLastCandle(); + } + catch (Exception retryEx) + { + Logger.LogError(retryEx, "Failed to load last candle for {GrainKey} after retry", grainKey); + LastCandle = null; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error loading last candle for {GrainKey}", grainKey); + LastCandle = null; + } }); } diff --git a/src/Managing.Application/Grains/CandleStoreGrain.cs b/src/Managing.Application/Grains/CandleStoreGrain.cs index 17c6f4cd..cee90dc6 100644 --- a/src/Managing.Application/Grains/CandleStoreGrain.cs +++ b/src/Managing.Application/Grains/CandleStoreGrain.cs @@ -34,22 +34,38 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver public override async Task OnActivateAsync(CancellationToken cancellationToken) { - var grainKey = this.GetPrimaryKeyString(); - _logger.LogInformation("CandleStoreGrain activated for key: {GrainKey}", grainKey); - - // Parse the grain key to extract exchange, ticker, and timeframe - var (exchange, ticker, timeframe) = CandleHelpers.ParseCandleStoreGrainKey(grainKey); - - // Initialize state if empty - if (_state.State.Candles == null || _state.State.Candles.Count == 0) + try { - await LoadInitialCandlesAsync(exchange, ticker, timeframe); + var grainKey = this.GetPrimaryKeyString(); + _logger.LogInformation("CandleStoreGrain activated for key: {GrainKey}", grainKey); + + // Parse the grain key to extract exchange, ticker, and timeframe + var (exchange, ticker, timeframe) = CandleHelpers.ParseCandleStoreGrainKey(grainKey); + + // Initialize state if empty + if (_state.State.Candles == null || _state.State.Candles.Count == 0) + { + await LoadInitialCandlesAsync(exchange, ticker, timeframe); + } + + // Subscribe to the price stream + await SubscribeToPriceStreamAsync(grainKey); + + await base.OnActivateAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during CandleStoreGrain activation for key: {GrainKey}", this.GetPrimaryKeyString()); + + // Ensure state is initialized even if there's an error + if (_state.State.Candles == null) + { + _state.State.Candles = new List(); + await _state.WriteStateAsync(); + } + + throw; // Re-throw to let Orleans handle the activation failure } - - // Subscribe to the price stream - await SubscribeToPriceStreamAsync(grainKey); - - await base.OnActivateAsync(cancellationToken); } public override async Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) @@ -68,7 +84,14 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver { try { - return Task.FromResult(_state.State.Candles?.ToList() ?? new List()); + // Ensure state is initialized + if (_state.State.Candles == null) + { + _logger.LogWarning("State not initialized for grain {GrainKey}, returning empty list", this.GetPrimaryKeyString()); + return Task.FromResult(new List()); + } + + return Task.FromResult(_state.State.Candles.ToList()); } catch (Exception ex) { @@ -135,14 +158,17 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver _logger.LogInformation("Loading initial candles for {Exchange}-{Ticker}-{Timeframe}", exchange, ticker, timeframe); + // Ensure state is initialized first + if (_state.State.Candles == null) + { + _state.State.Candles = new List(); + } + // Load the last 500 candles from the database var endDate = DateTime.UtcNow; - var startDate = - CandleHelpers - .GetBotPreloadSinceFromTimeframe(timeframe); // Look back 30 days to ensure we get enough data + var startDate = CandleHelpers.GetBotPreloadSinceFromTimeframe(timeframe); - var candles = - await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, endDate, MaxCandleCount); + var candles = await _candleRepository.GetCandles(exchange, ticker, timeframe, startDate, endDate, MaxCandleCount); if (candles?.Any() == true) { @@ -195,7 +221,22 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver public Task GetLastCandle() { - return Task.FromResult(_state.State.Candles?.LastOrDefault()); + try + { + // Ensure state is initialized + if (_state.State.Candles == null || _state.State.Candles.Count == 0) + { + _logger.LogDebug("No candles available for grain {GrainKey}", this.GetPrimaryKeyString()); + return Task.FromResult(null); + } + + return Task.FromResult(_state.State.Candles.LastOrDefault()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error retrieving last candle for grain {GrainKey}", this.GetPrimaryKeyString()); + return Task.FromResult(null); + } } } diff --git a/src/Managing.Application/Grains/PriceFetcher15MinGrain.cs b/src/Managing.Application/Grains/PriceFetcher15MinGrain.cs index 52c321d0..957b9be9 100644 --- a/src/Managing.Application/Grains/PriceFetcher15MinGrain.cs +++ b/src/Managing.Application/Grains/PriceFetcher15MinGrain.cs @@ -106,12 +106,12 @@ public class PriceFetcher15MinGrain : Grain, IPriceFetcher15MinGrain, IRemindabl // Get the last candle date from database var existingCandles = await _candleRepository.GetCandles(exchange, ticker, timeframe, - DateTime.UtcNow.AddDays(-30), 1); + DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(1), 20); var isFirstCall = !existingCandles.Any(); var startDate = !isFirstCall - ? existingCandles.Max(c => c.Date).AddMinutes(GetTimeframeMinutes(timeframe)) + ? existingCandles.Last().Date : new DateTime(2017, 1, 1); // Fetch new candles from external API @@ -125,7 +125,9 @@ public class PriceFetcher15MinGrain : Grain, IPriceFetcher15MinGrain, IRemindabl newCandles.Count, streamKey); // Process all new candles - var processedCandles = newCandles.OrderBy(c => c.Date).ToList(); + var processedCandles = newCandles.OrderBy(c => c.Date) + .Where(c => c.Date <= DateTime.UtcNow) // Avoid duplicates + .ToList(); foreach (var candle in processedCandles) {