Add performance for price reminder
This commit is contained in:
@@ -1,20 +1,75 @@
|
|||||||
using Xunit;
|
using Managing.Domain.Candles;
|
||||||
|
using Xunit;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Tests
|
namespace Managing.Application.Tests
|
||||||
{
|
{
|
||||||
public class CandleHelpersTests
|
public class CandleHelpersTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Shoud_Result_Correct_Range_Date()
|
public void GetDueTimeForTimeframe_FifteenMinutes_ShouldReturnCorrectTimeSpan()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedDate = DateTime.Now.AddMinutes(-15*5);
|
var timeframe = Timeframe.FifteenMinutes;
|
||||||
var currentCandleDate = DateTime.Now;
|
var testTime = new DateTime(2024, 1, 1, 16, 42, 36, DateTimeKind.Utc); // 16:42:36
|
||||||
var previousCandleDate = DateTime.Now.AddMinutes(-15);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
//var result = CandleHelpers.GetRangeDateFromTimeframe
|
var dueTime = CandleHelpers.GetDueTimeForTimeframe(timeframe, testTime);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
Assert.True(dueTime.TotalMinutes > 0, "Due time should be positive");
|
||||||
|
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
|
||||||
|
// Time difference should be approximately 2 minutes and 25 seconds
|
||||||
|
var expectedMinutes = 2;
|
||||||
|
var expectedSeconds = 25;
|
||||||
|
var expectedTotalSeconds = expectedMinutes * 60 + expectedSeconds;
|
||||||
|
|
||||||
|
Assert.True(Math.Abs(dueTime.TotalSeconds - expectedTotalSeconds) <= 1,
|
||||||
|
$"Expected approximately {expectedMinutes}m {expectedSeconds}s, but got {dueTime.TotalMinutes:F2}m {dueTime.Seconds}s");
|
||||||
|
|
||||||
|
// Verify the next execution time
|
||||||
|
var nextExecution = testTime.Add(dueTime);
|
||||||
|
Assert.Equal(16, nextExecution.Hour);
|
||||||
|
Assert.Equal(45, nextExecution.Minute);
|
||||||
|
Assert.Equal(1, nextExecution.Second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDueTimeForTimeframe_FiveMinutes_ShouldAlignToFiveMinuteBoundaries()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var timeframe = Timeframe.FiveMinutes;
|
||||||
|
var testTime = new DateTime(2024, 1, 1, 10, 23, 45, DateTimeKind.Utc); // 10:23:45
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var dueTime = CandleHelpers.GetDueTimeForTimeframe(timeframe, testTime);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var nextExecution = testTime.Add(dueTime);
|
||||||
|
// Should align to 10:25:01 (next 5-minute boundary + 1 second)
|
||||||
|
Assert.Equal(10, nextExecution.Hour);
|
||||||
|
Assert.Equal(25, nextExecution.Minute);
|
||||||
|
Assert.Equal(1, nextExecution.Second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDueTimeForTimeframe_OneHour_ShouldAlignToHourBoundaries()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var timeframe = Timeframe.OneHour;
|
||||||
|
var testTime = new DateTime(2024, 1, 1, 14, 30, 15, DateTimeKind.Utc); // 14:30:15
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var dueTime = CandleHelpers.GetDueTimeForTimeframe(timeframe, testTime);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var nextExecution = testTime.Add(dueTime);
|
||||||
|
// Should align to 15:00:01 (next hour boundary + 1 second)
|
||||||
|
Assert.Equal(15, nextExecution.Hour);
|
||||||
|
Assert.Equal(0, nextExecution.Minute);
|
||||||
|
Assert.Equal(1, nextExecution.Second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ public class PriceFetcher15MinGrain : Grain, IPriceFetcher15MinGrain, IRemindabl
|
|||||||
await this.RegisterOrUpdateReminder(
|
await this.RegisterOrUpdateReminder(
|
||||||
FetchPricesReminderName,
|
FetchPricesReminderName,
|
||||||
TimeSpan.FromSeconds(5),
|
TimeSpan.FromSeconds(5),
|
||||||
TimeSpan.FromMinutes(5));
|
TimeSpan.FromMinutes(7.5));
|
||||||
|
|
||||||
await base.OnActivateAsync(cancellationToken);
|
await base.OnActivateAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -184,12 +184,19 @@ public class PriceFetcher15MinGrain : Grain, IPriceFetcher15MinGrain, IRemindabl
|
|||||||
{
|
{
|
||||||
if (_timer != null) return;
|
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} seconds",
|
||||||
|
nameof(PriceFetcher15MinGrain), dueTime, now.Add(dueTime), period);
|
||||||
|
|
||||||
_timer = this.RegisterGrainTimer(
|
_timer = this.RegisterGrainTimer(
|
||||||
async _ => await FetchAndPublishPricesAsync(),
|
async _ => await FetchAndPublishPricesAsync(),
|
||||||
new GrainTimerCreationOptions
|
new GrainTimerCreationOptions
|
||||||
{
|
{
|
||||||
Period = TimeSpan.FromMinutes(15),
|
Period = period,
|
||||||
DueTime = TimeSpan.FromSeconds(1),
|
DueTime = dueTime,
|
||||||
KeepAlive = true
|
KeepAlive = true
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,7 +223,7 @@ public class PriceFetcher15MinGrain : Grain, IPriceFetcher15MinGrain, IRemindabl
|
|||||||
RegisterAndStartTimer();
|
RegisterAndStartTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,4 +7,4 @@ public class DateHelpers
|
|||||||
var dat_Time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
var dat_Time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||||
return dat_Time.AddSeconds(unixTimestamp);
|
return dat_Time.AddSeconds(unixTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0"/>
|
||||||
<PackageReference Include="Sentry" Version="5.5.1" />
|
<PackageReference Include="Sentry" Version="5.5.1"/>
|
||||||
<PackageReference Include="Sentry.AspNetCore" Version="5.5.1" />
|
<PackageReference Include="Sentry.AspNetCore" Version="5.5.1"/>
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Managing.Common">
|
||||||
|
<HintPath>..\Managing.Api\bin\Debug\net8.0\Managing.Common.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -96,9 +96,73 @@ public static class CandleHelpers
|
|||||||
return string.Join("-", exchange, ticker, timeframe);
|
return string.Join("-", exchange, ticker, timeframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (TradingExchanges exchange, Ticker ticker, Timeframe timeframe) ParseCandleStoreGrainKey(string grainKey)
|
public static (TradingExchanges exchange, Ticker ticker, Timeframe timeframe) ParseCandleStoreGrainKey(
|
||||||
|
string grainKey)
|
||||||
{
|
{
|
||||||
var components = grainKey.Split('-');
|
var components = grainKey.Split('-');
|
||||||
return (MiscExtensions.ParseEnum<TradingExchanges>(components[0]), MiscExtensions.ParseEnum<Ticker>(components[1]), MiscExtensions.ParseEnum<Timeframe>(components[2]));
|
return (MiscExtensions.ParseEnum<TradingExchanges>(components[0]),
|
||||||
|
MiscExtensions.ParseEnum<Ticker>(components[1]), MiscExtensions.ParseEnum<Timeframe>(components[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TimeSpan GetDueTimeForTimeframe(Timeframe timeframe, DateTime now)
|
||||||
|
{
|
||||||
|
var intervalMinutes = GetBaseIntervalInSeconds(timeframe) / 60;
|
||||||
|
|
||||||
|
// Calculate the next candle boundary
|
||||||
|
var nextBoundary = GetNextCandleBoundary(now, intervalMinutes);
|
||||||
|
|
||||||
|
// Add 1 second to ensure we're after the candle closes
|
||||||
|
var targetTime = nextBoundary.AddSeconds(1);
|
||||||
|
|
||||||
|
// Return the time difference
|
||||||
|
return targetTime - now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DateTime GetNextCandleBoundary(DateTime now, double intervalMinutes)
|
||||||
|
{
|
||||||
|
// For different timeframes, we need to align to different boundaries
|
||||||
|
if (intervalMinutes == 1) // OneMinute
|
||||||
|
{
|
||||||
|
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0).AddMinutes(1);
|
||||||
|
}
|
||||||
|
else if (intervalMinutes == 5) // FiveMinutes
|
||||||
|
{
|
||||||
|
var minute = (now.Minute / 5) * 5;
|
||||||
|
var boundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, minute, 0);
|
||||||
|
return boundary.AddMinutes(5);
|
||||||
|
}
|
||||||
|
else if (intervalMinutes == 15) // FifteenMinutes
|
||||||
|
{
|
||||||
|
var minute = (now.Minute / 15) * 15;
|
||||||
|
var boundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, minute, 0);
|
||||||
|
return boundary.AddMinutes(15);
|
||||||
|
}
|
||||||
|
else if (intervalMinutes == 30) // ThirtyMinutes
|
||||||
|
{
|
||||||
|
var minute = (now.Minute / 30) * 30;
|
||||||
|
var boundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, minute, 0);
|
||||||
|
return boundary.AddMinutes(30);
|
||||||
|
}
|
||||||
|
else if (intervalMinutes == 60) // OneHour
|
||||||
|
{
|
||||||
|
var boundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0);
|
||||||
|
return boundary.AddHours(1);
|
||||||
|
}
|
||||||
|
else if (intervalMinutes == 240) // FourHour
|
||||||
|
{
|
||||||
|
var hour = (now.Hour / 4) * 4;
|
||||||
|
var boundary = new DateTime(now.Year, now.Month, now.Day, hour, 0, 0);
|
||||||
|
return boundary.AddHours(4);
|
||||||
|
}
|
||||||
|
else if (intervalMinutes == 1440) // OneDay
|
||||||
|
{
|
||||||
|
var boundary = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
|
||||||
|
return boundary.AddDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to 5-minute intervals
|
||||||
|
var fallbackMinute = (now.Minute / 5) * 5;
|
||||||
|
var fallbackBoundary = new DateTime(now.Year, now.Month, now.Day, now.Hour, fallbackMinute, 0);
|
||||||
|
return fallbackBoundary.AddMinutes(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user