diff --git a/src/Managing.Application/Grains/PriceFetcherGrain.cs b/src/Managing.Application/Grains/PriceFetcherGrain.cs
index e237026e..e1dadaab 100644
--- a/src/Managing.Application/Grains/PriceFetcherGrain.cs
+++ b/src/Managing.Application/Grains/PriceFetcherGrain.cs
@@ -6,18 +6,16 @@ using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Microsoft.Extensions.Logging;
-using Orleans.Concurrency;
using Orleans.Streams;
using static Managing.Common.Enums;
namespace Managing.Application.Grains;
///
-/// StatelessWorker grain for fetching price data from external APIs and publishing to Orleans streams.
+/// Grain for fetching price data from external APIs and publishing to Orleans streams.
/// This grain runs periodically and processes all exchange/ticker combinations for a specific timeframe.
/// The timeframe is passed as the PrimaryKeyString to identify which timeframe this grain handles.
///
-[StatelessWorker]
public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
{
private readonly ILogger _logger;
@@ -26,7 +24,6 @@ public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
private readonly IGrainFactory _grainFactory;
private string FetchPricesReminderName => $"Fetch{TargetTimeframe}PricesReminder";
- private IDisposable _timer;
// Predefined lists of trading parameters to fetch
private static readonly TradingExchanges[] SupportedExchanges =
@@ -64,13 +61,18 @@ public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
_logger.LogInformation("{0} activated for timeframe {1}", nameof(PriceFetcherGrain), TargetTimeframe);
- // Register a reminder to enable timer if not existing
+ // Register a reminder aligned to timeframe boundaries
var intervalMinutes = GrainHelpers.GetIntervalMinutes(TargetTimeframe);
+ var now = DateTime.UtcNow;
+ var dueTime = CandleHelpers.GetDueTimeForTimeframe(TargetTimeframe, now);
await this.RegisterOrUpdateReminder(
FetchPricesReminderName,
- TimeSpan.FromSeconds(10),
+ dueTime,
TimeSpan.FromMinutes(intervalMinutes));
+ // Optional immediate kick-off to avoid waiting until next boundary
+ _ = FetchAndPublishPricesAsync();
+
await base.OnActivateAsync(cancellationToken);
}
@@ -79,7 +81,6 @@ public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
_logger.LogInformation("{0} deactivating for timeframe {1}. Reason: {Reason}",
nameof(PriceFetcherGrain), TargetTimeframe, reason.Description);
- StopAndDisposeTimer();
await base.OnDeactivateAsync(reason, cancellationToken);
}
@@ -190,53 +191,12 @@ public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
}
}
- ///
- /// Starts the Orleans timer for periodic price fetching
- ///
- private void RegisterAndStartTimer()
- {
- if (_timer != null) return;
-
- // Calculate the next execution time aligned to X-minute boundaries
- var now = DateTime.UtcNow;
- var dueTime = CandleHelpers.GetDueTimeForTimeframe(TargetTimeframe, now);
- var period = TimeSpan.FromSeconds(CandleHelpers.GetBaseIntervalInSeconds(TargetTimeframe));
- _logger.LogInformation("{0} next execution scheduled in {1} seconds and at {2:} UTC every {3}",
- string.Join("-", nameof(PriceFetcherGrain), TargetTimeframe), dueTime, now.Add(dueTime), period);
-
- _timer = this.RegisterGrainTimer(
- async _ => await FetchAndPublishPricesAsync(),
- new GrainTimerCreationOptions
- {
- Period = period,
- DueTime = dueTime,
- KeepAlive = true
- });
-
- _logger.LogInformation("{0} timer registered and started for timeframe {1}", nameof(PriceFetcherGrain),
- TargetTimeframe);
- }
-
- private void StopAndDisposeTimer()
- {
- if (_timer != null)
- {
- _timer?.Dispose();
- _timer = null;
- _logger.LogInformation("{0} timer stopped and disposed for timeframe {1}", nameof(PriceFetcherGrain),
- TargetTimeframe);
- }
- }
-
public Task ReceiveReminder(string reminderName, TickStatus status)
{
if (reminderName == FetchPricesReminderName)
{
- // Only enable timer if not existing anymore
- if (_timer == null)
- {
- RegisterAndStartTimer();
- }
+ // Trigger fetch on each reminder tick
+ return FetchAndPublishPricesAsync();
}
return Task.CompletedTask;