Add yield for orleans + 1min to 2h timeout for grain message + more exception send to sentry
This commit is contained in:
@@ -82,6 +82,10 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
|
|||||||
var initialBalance = config.BotTradingBalance;
|
var initialBalance = config.BotTradingBalance;
|
||||||
|
|
||||||
var fixedCandles = new HashSet<Candle>();
|
var fixedCandles = new HashSet<Candle>();
|
||||||
|
var lastYieldTime = DateTime.UtcNow;
|
||||||
|
const int yieldIntervalMs = 5000; // Yield control every 5 seconds to prevent timeout
|
||||||
|
const int candlesPerBatch = 100; // Process in batches to allow Orleans to check for cancellation
|
||||||
|
|
||||||
// Process all candles following the exact pattern from GetBacktestingResult
|
// Process all candles following the exact pattern from GetBacktestingResult
|
||||||
foreach (var candle in candles)
|
foreach (var candle in candles)
|
||||||
{
|
{
|
||||||
@@ -94,6 +98,16 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
|
|||||||
|
|
||||||
currentCandle++;
|
currentCandle++;
|
||||||
|
|
||||||
|
// Yield control periodically to prevent Orleans from thinking the grain is stuck
|
||||||
|
// This helps prevent timeout issues during long-running backtests
|
||||||
|
var timeSinceLastYield = (DateTime.UtcNow - lastYieldTime).TotalMilliseconds;
|
||||||
|
if (timeSinceLastYield >= yieldIntervalMs || currentCandle % candlesPerBatch == 0)
|
||||||
|
{
|
||||||
|
// Yield control back to Orleans scheduler
|
||||||
|
await Task.Yield();
|
||||||
|
lastYieldTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
// Log progress every 10%
|
// Log progress every 10%
|
||||||
var currentPercentage = (currentCandle * 100) / totalCandles;
|
var currentPercentage = (currentCandle * 100) / totalCandles;
|
||||||
if (currentPercentage >= lastLoggedPercentage + 10)
|
if (currentPercentage >= lastLoggedPercentage + 10)
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error processing bundle backtest request {RequestId}", bundleRequest.RequestId);
|
_logger.LogError(ex, "Error processing bundle backtest request {RequestId}", bundleRequest.RequestId);
|
||||||
|
SentrySdk.CaptureException(ex);
|
||||||
await HandleBundleRequestError(bundleRequest, backtester, ex);
|
await HandleBundleRequestError(bundleRequest, backtester, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,19 +134,24 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates individual backtest requests from variant configuration
|
/// Generates individual backtest requests from variant configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<List<RunBacktestRequest>> GenerateBacktestRequestsFromVariants(BundleBacktestRequest bundleRequest)
|
private async Task<List<RunBacktestRequest>> GenerateBacktestRequestsFromVariants(
|
||||||
|
BundleBacktestRequest bundleRequest)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Deserialize the variant configurations
|
// Deserialize the variant configurations
|
||||||
var universalConfig = JsonSerializer.Deserialize<BundleBacktestUniversalConfig>(bundleRequest.UniversalConfigJson);
|
var universalConfig =
|
||||||
|
JsonSerializer.Deserialize<BundleBacktestUniversalConfig>(bundleRequest.UniversalConfigJson);
|
||||||
var dateTimeRanges = JsonSerializer.Deserialize<List<DateTimeRange>>(bundleRequest.DateTimeRangesJson);
|
var dateTimeRanges = JsonSerializer.Deserialize<List<DateTimeRange>>(bundleRequest.DateTimeRangesJson);
|
||||||
var moneyManagementVariants = JsonSerializer.Deserialize<List<MoneyManagementVariant>>(bundleRequest.MoneyManagementVariantsJson);
|
var moneyManagementVariants =
|
||||||
|
JsonSerializer.Deserialize<List<MoneyManagementVariant>>(bundleRequest.MoneyManagementVariantsJson);
|
||||||
var tickerVariants = JsonSerializer.Deserialize<List<Ticker>>(bundleRequest.TickerVariantsJson);
|
var tickerVariants = JsonSerializer.Deserialize<List<Ticker>>(bundleRequest.TickerVariantsJson);
|
||||||
|
|
||||||
if (universalConfig == null || dateTimeRanges == null || moneyManagementVariants == null || tickerVariants == null)
|
if (universalConfig == null || dateTimeRanges == null || moneyManagementVariants == null ||
|
||||||
|
tickerVariants == null)
|
||||||
{
|
{
|
||||||
_logger.LogError("Failed to deserialize variant configurations for bundle request {RequestId}", bundleRequest.RequestId);
|
_logger.LogError("Failed to deserialize variant configurations for bundle request {RequestId}",
|
||||||
|
bundleRequest.RequestId);
|
||||||
return new List<RunBacktestRequest>();
|
return new List<RunBacktestRequest>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,13 +160,15 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
_scopeFactory,
|
_scopeFactory,
|
||||||
async service =>
|
async service =>
|
||||||
{
|
{
|
||||||
var accounts = await service.GetAccountsByUserAsync(bundleRequest.User, hideSecrets: true, getBalance: false);
|
var accounts =
|
||||||
|
await service.GetAccountsByUserAsync(bundleRequest.User, hideSecrets: true, getBalance: false);
|
||||||
return accounts.FirstOrDefault();
|
return accounts.FirstOrDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (firstAccount == null)
|
if (firstAccount == null)
|
||||||
{
|
{
|
||||||
_logger.LogError("No accounts found for user {UserId} in bundle request {RequestId}", bundleRequest.User.Id, bundleRequest.RequestId);
|
_logger.LogError("No accounts found for user {UserId} in bundle request {RequestId}",
|
||||||
|
bundleRequest.User.Id, bundleRequest.RequestId);
|
||||||
return new List<RunBacktestRequest>();
|
return new List<RunBacktestRequest>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +187,8 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
Timeframe = universalConfig.Timeframe,
|
Timeframe = universalConfig.Timeframe,
|
||||||
IsForWatchingOnly = universalConfig.IsForWatchingOnly,
|
IsForWatchingOnly = universalConfig.IsForWatchingOnly,
|
||||||
BotTradingBalance = universalConfig.BotTradingBalance,
|
BotTradingBalance = universalConfig.BotTradingBalance,
|
||||||
Name = $"{universalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}",
|
Name =
|
||||||
|
$"{universalConfig.BotName}_{ticker}_{dateRange.StartDate:yyyyMMdd}_{dateRange.EndDate:yyyyMMdd}",
|
||||||
FlipPosition = universalConfig.FlipPosition,
|
FlipPosition = universalConfig.FlipPosition,
|
||||||
CooldownPeriod = universalConfig.CooldownPeriod,
|
CooldownPeriod = universalConfig.CooldownPeriod,
|
||||||
MaxLossStreak = universalConfig.MaxLossStreak,
|
MaxLossStreak = universalConfig.MaxLossStreak,
|
||||||
@@ -204,15 +213,16 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
WatchOnly = universalConfig.WatchOnly,
|
WatchOnly = universalConfig.WatchOnly,
|
||||||
Save = universalConfig.Save,
|
Save = universalConfig.Save,
|
||||||
WithCandles = false, // Bundle backtests never return candles
|
WithCandles = false, // Bundle backtests never return candles
|
||||||
MoneyManagement = mmVariant.MoneyManagement != null ?
|
MoneyManagement = mmVariant.MoneyManagement != null
|
||||||
new MoneyManagement
|
? new MoneyManagement
|
||||||
{
|
{
|
||||||
Name = mmVariant.MoneyManagement.Name,
|
Name = mmVariant.MoneyManagement.Name,
|
||||||
Timeframe = mmVariant.MoneyManagement.Timeframe,
|
Timeframe = mmVariant.MoneyManagement.Timeframe,
|
||||||
StopLoss = mmVariant.MoneyManagement.StopLoss,
|
StopLoss = mmVariant.MoneyManagement.StopLoss,
|
||||||
TakeProfit = mmVariant.MoneyManagement.TakeProfit,
|
TakeProfit = mmVariant.MoneyManagement.TakeProfit,
|
||||||
Leverage = mmVariant.MoneyManagement.Leverage
|
Leverage = mmVariant.MoneyManagement.Leverage
|
||||||
} : null
|
}
|
||||||
|
: null
|
||||||
};
|
};
|
||||||
|
|
||||||
backtestRequests.Add(backtestRequest);
|
backtestRequests.Add(backtestRequest);
|
||||||
@@ -224,7 +234,8 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error generating backtest requests from variants for bundle request {RequestId}", bundleRequest.RequestId);
|
_logger.LogError(ex, "Error generating backtest requests from variants for bundle request {RequestId}",
|
||||||
|
bundleRequest.RequestId);
|
||||||
return new List<RunBacktestRequest>();
|
return new List<RunBacktestRequest>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,6 +278,7 @@ public class BundleBacktestGrain : Grain, IBundleBacktestGrain, IRemindable
|
|||||||
index + 1, bundleRequest.RequestId);
|
index + 1, bundleRequest.RequestId);
|
||||||
bundleRequest.FailedBacktests++;
|
bundleRequest.FailedBacktests++;
|
||||||
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
await backtester.UpdateBundleBacktestRequestAsync(bundleRequest);
|
||||||
|
SentrySdk.CaptureException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,8 @@ public static class ApiBootstrap
|
|||||||
.Configure<MessagingOptions>(options =>
|
.Configure<MessagingOptions>(options =>
|
||||||
{
|
{
|
||||||
// Configure messaging for better reliability with increased timeouts
|
// Configure messaging for better reliability with increased timeouts
|
||||||
options.ResponseTimeout = TimeSpan.FromSeconds(60);
|
// Set to 2 hours to support long-running backtests that can take 47+ minutes
|
||||||
|
options.ResponseTimeout = TimeSpan.FromHours(2);
|
||||||
options.DropExpiredMessages = true;
|
options.DropExpiredMessages = true;
|
||||||
})
|
})
|
||||||
.Configure<ClusterMembershipOptions>(options =>
|
.Configure<ClusterMembershipOptions>(options =>
|
||||||
@@ -265,7 +266,9 @@ public static class ApiBootstrap
|
|||||||
siloBuilder.Configure<GrainCollectionOptions>(options =>
|
siloBuilder.Configure<GrainCollectionOptions>(options =>
|
||||||
{
|
{
|
||||||
// Enable grain collection for active grains
|
// Enable grain collection for active grains
|
||||||
options.CollectionAge = TimeSpan.FromMinutes(10);
|
// Set to 2.5 hours to allow long-running backtests (up to 2 hours) to complete
|
||||||
|
// without being collected prematurely
|
||||||
|
options.CollectionAge = TimeSpan.FromMinutes(150); // 2.5 hours
|
||||||
options.CollectionQuantum = TimeSpan.FromMinutes(1);
|
options.CollectionQuantum = TimeSpan.FromMinutes(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -281,7 +284,16 @@ public static class ApiBootstrap
|
|||||||
}
|
}
|
||||||
|
|
||||||
siloBuilder
|
siloBuilder
|
||||||
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Information));
|
.ConfigureLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.SetMinimumLevel(LogLevel.Information);
|
||||||
|
// Reduce verbosity of Orleans status update messages for long-running operations
|
||||||
|
// These are informational and can be very verbose during long backtests
|
||||||
|
logging.AddFilter("Orleans.Runtime.Messaging.IncomingMessageAcceptor", LogLevel.Warning);
|
||||||
|
logging.AddFilter("Orleans.Runtime.Messaging.MessageCenter", LogLevel.Warning);
|
||||||
|
// Keep important Orleans logs but reduce status update noise
|
||||||
|
logging.AddFilter("Microsoft.Orleans.Runtime.Messaging", LogLevel.Warning);
|
||||||
|
});
|
||||||
|
|
||||||
// Only enable dashboard in development to avoid shutdown issues
|
// Only enable dashboard in development to avoid shutdown issues
|
||||||
if (!isProduction)
|
if (!isProduction)
|
||||||
|
|||||||
Reference in New Issue
Block a user