Fix genetic db connection pool
This commit is contained in:
@@ -2,9 +2,11 @@ using System.Text.Json;
|
|||||||
using GeneticSharp;
|
using GeneticSharp;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Application.Backtests;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
|
using Managing.Domain.Candles;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Risk;
|
using Managing.Domain.Risk;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
@@ -346,10 +348,50 @@ public class GeneticService : IGeneticService
|
|||||||
_logger.LogInformation("Starting fresh genetic algorithm for request {RequestId}", request.RequestId);
|
_logger.LogInformation("Starting fresh genetic algorithm for request {RequestId}", request.RequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create fitness function first
|
// Load candles once at the beginning to avoid repeated database queries
|
||||||
var fitness = new TradingBotFitness(_serviceScopeFactory, request, _logger);
|
// This significantly reduces database connections during genetic algorithm execution
|
||||||
|
_logger.LogInformation("Loading candles for genetic algorithm {RequestId}: {Ticker} on {Timeframe} from {StartDate} to {EndDate}",
|
||||||
|
request.RequestId, request.Ticker, request.Timeframe, request.StartDate, request.EndDate);
|
||||||
|
|
||||||
|
HashSet<Candle> candles;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
candles = await ServiceScopeHelpers.WithScopedService<ICandleRepository, HashSet<Candle>>(
|
||||||
|
_serviceScopeFactory,
|
||||||
|
async candleRepository => await candleRepository.GetCandles(
|
||||||
|
TradingExchanges.Evm, // Default exchange for genetic algorithms
|
||||||
|
request.Ticker,
|
||||||
|
request.Timeframe,
|
||||||
|
request.StartDate,
|
||||||
|
request.EndDate
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (candles == null || candles.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No candles found for {request.Ticker} on {request.Timeframe} from {request.StartDate} to {request.EndDate}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Loaded {CandleCount} candles for genetic algorithm {RequestId}",
|
||||||
|
candles.Count, request.RequestId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to load candles for genetic algorithm {RequestId}", request.RequestId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fitness function with pre-loaded candles to avoid database queries during evaluation
|
||||||
|
var fitness = new TradingBotFitness(_serviceScopeFactory, request, candles, _logger);
|
||||||
|
|
||||||
// Create genetic algorithm with better configuration
|
// Create genetic algorithm with better configuration
|
||||||
|
// Limit parallelism to prevent database connection pool exhaustion
|
||||||
|
// Each fitness evaluation creates a database connection, so we need to limit
|
||||||
|
// the number of parallel evaluations to stay within connection pool limits
|
||||||
|
// Default PostgreSQL pool is 20 connections, so we limit to 4 threads per genetic job
|
||||||
|
// This leaves room for other operations and multiple concurrent genetic jobs
|
||||||
|
var maxParallelThreads = Math.Min(4, Environment.ProcessorCount);
|
||||||
var ga = new GeneticAlgorithm(
|
var ga = new GeneticAlgorithm(
|
||||||
population,
|
population,
|
||||||
fitness,
|
fitness,
|
||||||
@@ -366,7 +408,7 @@ public class GeneticService : IGeneticService
|
|||||||
TaskExecutor = new ParallelTaskExecutor
|
TaskExecutor = new ParallelTaskExecutor
|
||||||
{
|
{
|
||||||
MinThreads = 2,
|
MinThreads = 2,
|
||||||
MaxThreads = Environment.ProcessorCount
|
MaxThreads = maxParallelThreads
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -963,12 +1005,18 @@ public class TradingBotFitness : IFitness
|
|||||||
private readonly GeneticRequest _request;
|
private readonly GeneticRequest _request;
|
||||||
private GeneticAlgorithm _geneticAlgorithm;
|
private GeneticAlgorithm _geneticAlgorithm;
|
||||||
private readonly ILogger<GeneticService> _logger;
|
private readonly ILogger<GeneticService> _logger;
|
||||||
|
private readonly HashSet<Candle> _candles;
|
||||||
|
private static readonly SemaphoreSlim _dbSemaphore = new SemaphoreSlim(4, 4); // Limit concurrent DB operations
|
||||||
|
|
||||||
public TradingBotFitness(IServiceScopeFactory serviceScopeFactory, GeneticRequest request,
|
public TradingBotFitness(
|
||||||
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
GeneticRequest request,
|
||||||
|
HashSet<Candle> candles,
|
||||||
ILogger<GeneticService> logger)
|
ILogger<GeneticService> logger)
|
||||||
{
|
{
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_request = request;
|
_request = request;
|
||||||
|
_candles = candles;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,23 +1035,30 @@ public class TradingBotFitness : IFitness
|
|||||||
|
|
||||||
var config = tradingBotChromosome.GetTradingBotConfig(_request);
|
var config = tradingBotChromosome.GetTradingBotConfig(_request);
|
||||||
|
|
||||||
// Get current generation number (default to 0 if not available)
|
// Run backtest synchronously using BacktestExecutor (no job creation, no DB writes)
|
||||||
var currentGeneration = _geneticAlgorithm?.GenerationsNumber ?? 0;
|
// Use semaphore to limit concurrent database operations (for indicator calculations)
|
||||||
|
// This prevents connection pool exhaustion when running large populations
|
||||||
// Run backtest using scoped service to avoid DbContext concurrency issues
|
_dbSemaphore.Wait();
|
||||||
var lightBacktest = ServiceScopeHelpers.WithScopedService<IBacktester, LightBacktest>(
|
LightBacktest lightBacktest;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lightBacktest = ServiceScopeHelpers.WithScopedService<BacktestExecutor, LightBacktest>(
|
||||||
_serviceScopeFactory,
|
_serviceScopeFactory,
|
||||||
async backtester => await backtester.RunTradingBotBacktest(
|
async executor => await executor.ExecuteAsync(
|
||||||
config,
|
config,
|
||||||
_request.StartDate,
|
_candles,
|
||||||
_request.EndDate,
|
|
||||||
_request.User,
|
_request.User,
|
||||||
true,
|
save: false, // Don't save backtest results for genetic algorithm
|
||||||
false, // Don't include candles
|
withCandles: false,
|
||||||
_request.RequestId,
|
requestId: _request.RequestId,
|
||||||
new GeneticBacktestMetadata(currentGeneration, _request.RequestId)
|
metadata: new GeneticBacktestMetadata(_geneticAlgorithm?.GenerationsNumber ?? 0, _request.RequestId)
|
||||||
)
|
)
|
||||||
).GetAwaiter().GetResult();
|
).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_dbSemaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate multi-objective fitness based on backtest results
|
// Calculate multi-objective fitness based on backtest results
|
||||||
var fitness = CalculateFitness(lightBacktest, config);
|
var fitness = CalculateFitness(lightBacktest, config);
|
||||||
|
|||||||
Reference in New Issue
Block a user