Reduce API call when fetching new candles
This commit is contained in:
@@ -19,16 +19,16 @@ namespace Managing.Application.Tests
|
|||||||
// Assert
|
// Assert
|
||||||
Assert.True(dueTime.TotalMinutes > 0, "Due time should be positive");
|
Assert.True(dueTime.TotalMinutes > 0, "Due time should be positive");
|
||||||
Assert.True(dueTime.TotalMinutes < 15, "Due time should be less than the timeframe interval");
|
Assert.True(dueTime.TotalMinutes < 15, "Due time should be less than the timeframe interval");
|
||||||
|
|
||||||
// Next 15-minute boundary is 16:45:00, so execution should be at 16:45:01
|
// Next 15-minute boundary is 16:45:00, so execution should be at 16:45:01
|
||||||
// Time difference should be approximately 2 minutes and 25 seconds
|
// Time difference should be approximately 2 minutes and 25 seconds
|
||||||
var expectedMinutes = 2;
|
var expectedMinutes = 2;
|
||||||
var expectedSeconds = 25;
|
var expectedSeconds = 25;
|
||||||
var expectedTotalSeconds = expectedMinutes * 60 + expectedSeconds;
|
var expectedTotalSeconds = expectedMinutes * 60 + expectedSeconds;
|
||||||
|
|
||||||
Assert.True(Math.Abs(dueTime.TotalSeconds - expectedTotalSeconds) <= 1,
|
Assert.True(Math.Abs(dueTime.TotalSeconds - expectedTotalSeconds) <= 1,
|
||||||
$"Expected approximately {expectedMinutes}m {expectedSeconds}s, but got {dueTime.TotalMinutes:F2}m {dueTime.Seconds}s");
|
$"Expected approximately {expectedMinutes}m {expectedSeconds}s, but got {dueTime.TotalMinutes:F2}m {dueTime.Seconds}s");
|
||||||
|
|
||||||
// Verify the next execution time
|
// Verify the next execution time
|
||||||
var nextExecution = testTime.Add(dueTime);
|
var nextExecution = testTime.Add(dueTime);
|
||||||
Assert.Equal(16, nextExecution.Hour);
|
Assert.Equal(16, nextExecution.Hour);
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -107,17 +107,33 @@ public static class CandleHelpers
|
|||||||
public static TimeSpan GetDueTimeForTimeframe(Timeframe timeframe, DateTime now)
|
public static TimeSpan GetDueTimeForTimeframe(Timeframe timeframe, DateTime now)
|
||||||
{
|
{
|
||||||
var intervalMinutes = GetBaseIntervalInSeconds(timeframe) / 60;
|
var intervalMinutes = GetBaseIntervalInSeconds(timeframe) / 60;
|
||||||
|
|
||||||
// Calculate the next candle boundary
|
// Calculate the next candle boundary
|
||||||
var nextBoundary = GetNextCandleBoundary(now, intervalMinutes);
|
var nextBoundary = GetNextCandleBoundary(now, intervalMinutes);
|
||||||
|
|
||||||
// Add 1 second to ensure we're after the candle closes
|
// Add 1 second to ensure we're after the candle closes
|
||||||
var targetTime = nextBoundary.AddSeconds(1);
|
var targetTime = nextBoundary.AddSeconds(1);
|
||||||
|
|
||||||
// Return the time difference
|
// Return the time difference
|
||||||
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
|
||||||
@@ -159,7 +175,7 @@ public static class CandleHelpers
|
|||||||
var boundary = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
|
var boundary = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
|
||||||
return boundary.AddDays(1);
|
return boundary.AddDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to 5-minute intervals
|
// Fallback to 5-minute intervals
|
||||||
var fallbackMinute = (now.Minute / 5) * 5;
|
var fallbackMinute = (now.Minute / 5) * 5;
|
||||||
var fallbackBoundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, fallbackMinute, 0);
|
var fallbackBoundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, fallbackMinute, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user