Reduce API call when fetching new candles

This commit is contained in:
2025-09-17 17:45:41 +07:00
parent 900405b3de
commit 841bb20800
3 changed files with 85 additions and 12 deletions

View File

@@ -71,5 +71,56 @@ namespace Managing.Application.Tests
Assert.Equal(0, nextExecution.Minute); Assert.Equal(0, nextExecution.Minute);
Assert.Equal(1, nextExecution.Second); Assert.Equal(1, nextExecution.Second);
} }
[Fact]
public void GetNextExpectedCandleTime_FifteenMinutes_ShouldReturnCorrectBoundary()
{
// Arrange
var timeframe = Timeframe.FifteenMinutes;
var testTime = new DateTime(2024, 1, 1, 16, 42, 36, DateTimeKind.Utc); // 16:42:36
// Act
var nextCandleTime = CandleHelpers.GetNextExpectedCandleTime(timeframe, testTime);
// Assert
// Next 15-minute boundary should be 16:45:00
Assert.Equal(16, nextCandleTime.Hour);
Assert.Equal(44, nextCandleTime.Minute);
Assert.Equal(59, nextCandleTime.Second);
}
[Fact]
public void GetNextExpectedCandleTime_FiveMinutes_ShouldAlignToFiveMinuteBoundaries()
{
// Arrange
var timeframe = Timeframe.FiveMinutes;
var testTime = new DateTime(2024, 1, 1, 10, 23, 45, DateTimeKind.Utc); // 10:23:45
// Act
var nextCandleTime = CandleHelpers.GetNextExpectedCandleTime(timeframe, testTime);
// Assert
// Next 5-minute boundary should be 10:25:00
Assert.Equal(10, nextCandleTime.Hour);
Assert.Equal(24, nextCandleTime.Minute);
Assert.Equal(59, nextCandleTime.Second);
}
[Fact]
public void GetNextExpectedCandleTime_OneHour_ShouldAlignToHourBoundaries()
{
// Arrange
var timeframe = Timeframe.OneHour;
var testTime = new DateTime(2024, 1, 1, 14, 30, 15, DateTimeKind.Utc); // 14:30:15
// Act
var nextCandleTime = CandleHelpers.GetNextExpectedCandleTime(timeframe, testTime);
// Assert
// Next hour boundary should be 15:00:00
Assert.Equal(14, nextCandleTime.Hour);
Assert.Equal(59, nextCandleTime.Minute);
Assert.Equal(59, nextCandleTime.Second);
}
} }
} }

View File

@@ -70,9 +70,6 @@ public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
dueTime, dueTime,
TimeSpan.FromMinutes(intervalMinutes)); TimeSpan.FromMinutes(intervalMinutes));
// Optional immediate kick-off to avoid waiting until next boundary
_ = FetchAndPublishPricesAsync();
await base.OnActivateAsync(cancellationToken); await base.OnActivateAsync(cancellationToken);
} }
@@ -134,6 +131,15 @@ public class PriceFetcherGrain : Grain, IPriceFetcherGrain, IRemindable
var isFirstCall = !existingCandles.Any(); var isFirstCall = !existingCandles.Any();
// Check if the next expected candle is available yet
var nextExpectedCandleTime = CandleHelpers.GetNextExpectedCandleTime(timeframe);
if (nextExpectedCandleTime > DateTime.UtcNow)
{
_logger.LogDebug("Next candle for {Exchange}-{Ticker}-{Timeframe} not available yet. Expected at {NextCandleTime}, current time: {CurrentTime}",
exchange, ticker, timeframe, nextExpectedCandleTime, DateTime.UtcNow);
return; // Skip this fetch as the new candle won't be available
}
var startDate = !isFirstCall var startDate = !isFirstCall
? existingCandles.Last().Date ? existingCandles.Last().Date
: new DateTime(2017, 1, 1); : new DateTime(2017, 1, 1);

View File

@@ -118,6 +118,22 @@ public static class CandleHelpers
return targetTime - now; return targetTime - now;
} }
/// <summary>
/// Gets the next expected candle time for the given timeframe.
/// This is useful to determine if a new candle should be available yet.
/// </summary>
/// <param name="timeframe">The timeframe to calculate for</param>
/// <param name="now">The current time (defaults to DateTime.UtcNow)</param>
/// <returns>The next expected candle time</returns>
public static DateTime GetNextExpectedCandleTime(Timeframe timeframe, DateTime? now = null)
{
var currentTime = now ?? DateTime.UtcNow;
var intervalMinutes = GetBaseIntervalInSeconds(timeframe) / 60;
// Calculate the next candle boundary
return GetNextCandleBoundary(currentTime, intervalMinutes).AddSeconds(-1);
}
private static DateTime GetNextCandleBoundary(DateTime now, double intervalMinutes) private static DateTime GetNextCandleBoundary(DateTime now, double intervalMinutes)
{ {
// For different timeframes, we need to align to different boundaries // For different timeframes, we need to align to different boundaries