Fix grain price fetcher
This commit is contained in:
@@ -1,172 +0,0 @@
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// StatelessWorker grain for fetching 5-minute price data from external APIs and publishing to Orleans streams.
|
||||
/// This grain runs every 5 minutes and processes all exchange/ticker combinations for the 5-minute timeframe.
|
||||
/// </summary>
|
||||
[StatelessWorker]
|
||||
public class PriceFetcher5MinGrain : Grain, IPriceFetcher5MinGrain, IRemindable
|
||||
{
|
||||
private readonly ILogger<PriceFetcher5MinGrain> _logger;
|
||||
private readonly IExchangeService _exchangeService;
|
||||
private readonly ICandleRepository _candleRepository;
|
||||
private readonly IGrainFactory _grainFactory;
|
||||
|
||||
private const string FetchPricesReminderName = "FetchPricesReminder";
|
||||
|
||||
// Predefined lists of trading parameters to fetch
|
||||
private static readonly TradingExchanges[] SupportedExchanges =
|
||||
{
|
||||
TradingExchanges.GmxV2
|
||||
};
|
||||
|
||||
private static readonly Ticker[] SupportedTickers = Constants.GMX.Config.SupportedTickers;
|
||||
|
||||
private static readonly Timeframe TargetTimeframe = Timeframe.FiveMinutes;
|
||||
|
||||
public PriceFetcher5MinGrain(
|
||||
ILogger<PriceFetcher5MinGrain> logger,
|
||||
IExchangeService exchangeService,
|
||||
ICandleRepository candleRepository,
|
||||
IGrainFactory grainFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_exchangeService = exchangeService;
|
||||
_candleRepository = candleRepository;
|
||||
_grainFactory = grainFactory;
|
||||
}
|
||||
|
||||
public override async Task OnActivateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("PriceFetcher5MinGrain activated");
|
||||
|
||||
// Register a reminder to fetch prices every 5 minutes
|
||||
await this.RegisterOrUpdateReminder(
|
||||
FetchPricesReminderName,
|
||||
TimeSpan.FromMinutes(5),
|
||||
TimeSpan.FromMinutes(5));
|
||||
|
||||
await base.OnActivateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<bool> FetchAndPublishPricesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Starting 5-minute price fetch cycle");
|
||||
|
||||
var fetchTasks = new List<Task>();
|
||||
|
||||
// Create fetch tasks for all exchange/ticker combinations for 5-minute timeframe
|
||||
foreach (var exchange in SupportedExchanges)
|
||||
{
|
||||
foreach (var ticker in SupportedTickers)
|
||||
{
|
||||
fetchTasks.Add(FetchAndPublish(exchange, ticker, TargetTimeframe));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute all fetch operations in parallel
|
||||
await Task.WhenAll(fetchTasks);
|
||||
|
||||
_logger.LogInformation("Completed 5-minute price fetch cycle for {TotalCombinations} combinations",
|
||||
fetchTasks.Count);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during price fetch cycle");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FetchAndPublish(TradingExchanges exchange, Ticker ticker, Timeframe timeframe)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a dummy account for API calls (this may need to be adjusted based on your implementation)
|
||||
var account = new Account
|
||||
{
|
||||
Name = "PriceFetcher",
|
||||
Exchange = exchange,
|
||||
Type = AccountType.Watch
|
||||
};
|
||||
|
||||
// Get the last candle date from database
|
||||
var existingCandles = await _candleRepository.GetCandles(exchange, ticker, timeframe,
|
||||
DateTime.UtcNow.AddDays(-7), 1);
|
||||
|
||||
var startDate = existingCandles.Any()
|
||||
? existingCandles.Max(c => c.Date).AddMinutes(GetTimeframeMinutes(timeframe))
|
||||
: DateTime.UtcNow.AddDays(-1);
|
||||
|
||||
// Fetch new candles from external API
|
||||
var newCandles = await _exchangeService.GetCandles(account, ticker, startDate, timeframe, true);
|
||||
|
||||
if (newCandles?.Any() == true)
|
||||
{
|
||||
var streamProvider = this.GetStreamProvider("DefaultStreamProvider");
|
||||
var streamKey = $"{exchange}-{ticker}-{timeframe}";
|
||||
var stream = streamProvider.GetStream<Candle>(streamKey);
|
||||
|
||||
_logger.LogDebug("Fetched {CandleCount} new candles for {StreamKey}",
|
||||
newCandles.Count, streamKey);
|
||||
|
||||
// Process each new candle
|
||||
foreach (var candle in newCandles.OrderBy(c => c.Date))
|
||||
{
|
||||
// Ensure candle has correct metadata
|
||||
candle.Exchange = exchange;
|
||||
candle.Ticker = ticker;
|
||||
candle.Timeframe = timeframe;
|
||||
|
||||
// Save to database
|
||||
await _candleRepository.InsertCandle(candle);
|
||||
|
||||
// Publish to stream
|
||||
await stream.OnNextAsync(candle);
|
||||
|
||||
_logger.LogTrace("Published candle for {StreamKey} at {Date}",
|
||||
streamKey, candle.Date);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching prices for {Exchange}-{Ticker}-{Timeframe}",
|
||||
exchange, ticker, timeframe);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetTimeframeMinutes(Timeframe timeframe) => timeframe switch
|
||||
{
|
||||
Timeframe.OneMinute => 1,
|
||||
Timeframe.FiveMinutes => 5,
|
||||
Timeframe.FifteenMinutes => 15,
|
||||
Timeframe.ThirtyMinutes => 30,
|
||||
Timeframe.OneHour => 60,
|
||||
Timeframe.FourHour => 240,
|
||||
Timeframe.OneDay => 1440,
|
||||
_ => 1
|
||||
};
|
||||
|
||||
public async Task ReceiveReminder(string reminderName, TickStatus status)
|
||||
{
|
||||
if (reminderName == FetchPricesReminderName)
|
||||
{
|
||||
await FetchAndPublishPricesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,23 @@ namespace Managing.Application.Grains;
|
||||
|
||||
public class PriceFetcherInitializer : IHostedService
|
||||
{
|
||||
private readonly IClusterClient _clusterClient;
|
||||
private readonly IGrainFactory _grainFactory;
|
||||
|
||||
public PriceFetcherInitializer(IClusterClient clusterClient)
|
||||
public PriceFetcherInitializer(IClusterClient grainFactory)
|
||||
{
|
||||
_clusterClient = clusterClient;
|
||||
_grainFactory = grainFactory;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_clusterClient.GetGrain<IPriceFetcher5MinGrain>(0);
|
||||
return Task.CompletedTask;
|
||||
var fiveMinute = _grainFactory.GetGrain<IPriceFetcher15MinGrain>(0);
|
||||
Console.WriteLine("GrainId : {0}", fiveMinute.GetGrainId());
|
||||
|
||||
// Actually call a method on the grain to activate it
|
||||
// This will trigger OnActivateAsync and register the reminder
|
||||
await fiveMinute.FetchAndPublishPricesAsync();
|
||||
|
||||
Console.WriteLine("PriceFetcher5MinGrain activated and initial fetch completed");
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user