perf: optimize TradingBotBase and TradingBox - reduce LINQ overhead and allocations (+31.1%)
This commit is contained in:
BIN
scripts/.DS_Store
vendored
BIN
scripts/.DS_Store
vendored
Binary file not shown.
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
@@ -352,15 +352,40 @@ public class TradingBotBase : ITradingBot
|
||||
private async Task ManagePositions()
|
||||
{
|
||||
// Early exit optimization - skip if no positions to manage
|
||||
var hasOpenPositions = Positions.Values.Any(p => !p.IsFinished());
|
||||
var hasWaitingSignals = Signals.Values.Any(s => s.Status == SignalStatus.WaitingForPosition);
|
||||
// Optimized: Use for loop to avoid multiple iterations
|
||||
bool hasOpenPositions = false;
|
||||
foreach (var position in Positions.Values)
|
||||
{
|
||||
if (!position.IsFinished())
|
||||
{
|
||||
hasOpenPositions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasWaitingSignals = false;
|
||||
if (!hasOpenPositions) // Only check signals if no open positions
|
||||
{
|
||||
foreach (var signal in Signals.Values)
|
||||
{
|
||||
if (signal.Status == SignalStatus.WaitingForPosition)
|
||||
{
|
||||
hasWaitingSignals = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOpenPositions && !hasWaitingSignals)
|
||||
return;
|
||||
|
||||
// First, process all existing positions that are not finished
|
||||
foreach (var position in Positions.Values.Where(p => !p.IsFinished()))
|
||||
// Optimized: Inline the filter to avoid LINQ overhead
|
||||
foreach (var position in Positions.Values)
|
||||
{
|
||||
if (position.IsFinished())
|
||||
continue;
|
||||
|
||||
var signalForPosition = Signals[position.SignalIdentifier];
|
||||
if (signalForPosition == null)
|
||||
{
|
||||
@@ -2030,20 +2055,41 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
public int GetWinRate()
|
||||
{
|
||||
var succeededPositions = Positions.Values.Where(p => p.IsValidForMetrics()).Count(p => p.IsInProfit());
|
||||
var total = Positions.Values.Where(p => p.IsValidForMetrics()).Count();
|
||||
// Optimized: Single iteration instead of multiple LINQ queries
|
||||
int succeededPositions = 0;
|
||||
int totalPositions = 0;
|
||||
|
||||
if (total == 0)
|
||||
foreach (var position in Positions.Values)
|
||||
{
|
||||
if (position.IsValidForMetrics())
|
||||
{
|
||||
totalPositions++;
|
||||
if (position.IsInProfit())
|
||||
{
|
||||
succeededPositions++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalPositions == 0)
|
||||
return 0;
|
||||
|
||||
return (succeededPositions * 100) / total;
|
||||
return (succeededPositions * 100) / totalPositions;
|
||||
}
|
||||
|
||||
public decimal GetProfitAndLoss()
|
||||
{
|
||||
// Calculate net PnL after deducting fees for each position
|
||||
var netPnl = Positions.Values.Where(p => p.IsValidForMetrics() && p.ProfitAndLoss != null)
|
||||
.Sum(p => p.GetPnLBeforeFees());
|
||||
// Optimized: Single iteration instead of LINQ chaining
|
||||
decimal netPnl = 0;
|
||||
|
||||
foreach (var position in Positions.Values)
|
||||
{
|
||||
if (position.IsValidForMetrics() && position.ProfitAndLoss != null)
|
||||
{
|
||||
netPnl += position.GetPnLBeforeFees();
|
||||
}
|
||||
}
|
||||
|
||||
return netPnl;
|
||||
}
|
||||
|
||||
@@ -2055,11 +2101,15 @@ public class TradingBotBase : ITradingBot
|
||||
/// <returns>Returns the total fees paid as a decimal value.</returns>
|
||||
public decimal GetTotalFees()
|
||||
{
|
||||
// Optimized: Avoid LINQ Where overhead, inline the check
|
||||
decimal totalFees = 0;
|
||||
|
||||
foreach (var position in Positions.Values.Where(p => p.IsValidForMetrics()))
|
||||
foreach (var position in Positions.Values)
|
||||
{
|
||||
totalFees += TradingHelpers.CalculatePositionFees(position);
|
||||
if (position.IsValidForMetrics())
|
||||
{
|
||||
totalFees += TradingHelpers.CalculatePositionFees(position);
|
||||
}
|
||||
}
|
||||
|
||||
return totalFees;
|
||||
|
||||
@@ -75,10 +75,19 @@ public static class TradingBox
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
|
||||
{
|
||||
var signalOnCandles = new List<LightSignal>();
|
||||
// Optimize list creation - avoid redundant allocations
|
||||
var limitedCandles = newCandles.Count <= 600
|
||||
? newCandles.OrderBy(c => c.Date).ToList()
|
||||
: newCandles.OrderBy(c => c.Date).TakeLast(600).ToList();
|
||||
// Optimize list creation - avoid redundant allocations and multiple ordering
|
||||
List<Candle> limitedCandles;
|
||||
if (newCandles.Count <= 600)
|
||||
{
|
||||
// For small sets, just order once
|
||||
limitedCandles = newCandles.OrderBy(c => c.Date).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// For large sets, use more efficient approach: sort then take last
|
||||
var sorted = newCandles.OrderBy(c => c.Date).ToList();
|
||||
limitedCandles = sorted.Skip(sorted.Count - 600).ToList();
|
||||
}
|
||||
|
||||
foreach (var indicator in lightScenario.Indicators)
|
||||
{
|
||||
@@ -97,7 +106,8 @@ public static class TradingBox
|
||||
signals = indicatorInstance.Run(newCandles);
|
||||
}
|
||||
|
||||
if (signals == null || signals.Count() == 0)
|
||||
// Optimized: Use Count property instead of Count() LINQ method
|
||||
if (signals == null || signals.Count == 0)
|
||||
{
|
||||
// For trend and context strategies, lack of signal might be meaningful
|
||||
// Signal strategies are expected to be sparse, so we continue
|
||||
@@ -112,10 +122,11 @@ public static class TradingBox
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure limitedCandles is ordered chronologically
|
||||
var orderedCandles = limitedCandles.OrderBy(c => c.Date).ToList();
|
||||
// Optimized: limitedCandles is already ordered, no need to re-order
|
||||
var loopback = loopbackPeriod.HasValue && loopbackPeriod > 1 ? loopbackPeriod.Value : 1;
|
||||
var candleLoopback = orderedCandles.TakeLast(loopback).ToList();
|
||||
var candleLoopback = limitedCandles.Count > loopback
|
||||
? limitedCandles.Skip(limitedCandles.Count - loopback).ToList()
|
||||
: limitedCandles;
|
||||
|
||||
if (!candleLoopback.Any())
|
||||
{
|
||||
@@ -174,7 +185,9 @@ public static class TradingBox
|
||||
return signalOnCandles.Single();
|
||||
}
|
||||
|
||||
signalOnCandles = signalOnCandles.OrderBy(s => s.Date).ToHashSet();
|
||||
// Optimized: Sort only if needed, then convert to HashSet
|
||||
var orderedSignals = signalOnCandles.OrderBy(s => s.Date).ToList();
|
||||
signalOnCandles = new HashSet<LightSignal>(orderedSignals);
|
||||
|
||||
// Check if all strategies produced signals - this is required for composite signals
|
||||
var strategyNames = scenario.Indicators.Select(s => s.Name).ToHashSet();
|
||||
|
||||
@@ -20,3 +20,5 @@ DateTime,TestName,CandlesCount,ExecutionTimeSeconds,ProcessingRateCandlesPerSec,
|
||||
2025-11-11T04:57:14Z,ExecuteBacktest_With_Large_Dataset_Should_Show_Performance_Telemetry,5760,1.99,2883.5,15.26,13.73,25.11,1589.82,3828,33.2,258.98,0.21,0.04,24560.79,38,24.56,6015,2a0fbf9b,dev,development
|
||||
2025-11-11T04:59:09Z,ExecuteBacktest_With_Large_Dataset_Should_Show_Performance_Telemetry,5760,2.695,2127.6,15.26,13.64,24.65,2283.69,3828,33.2,209.33,0.30,0.04,24560.79,38,24.56,6015,2a0fbf9b,dev,development
|
||||
2025-11-11T05:13:30Z,ExecuteBacktest_With_Large_Dataset_Should_Show_Performance_Telemetry,5760,2.49,2300.8,15.27,13.68,25.14,2085.01,3828,33.2,232.91,0.27,0.04,24560.79,38,24.56,6015,2a0fbf9b,dev,development
|
||||
2025-11-11T05:18:07Z,ExecuteBacktest_With_Large_Dataset_Should_Show_Performance_Telemetry,5760,1.325,4316.5,15.25,13.83,24.63,1119.29,3828,33.2,112.94,0.15,0.02,24560.79,38,24.56,6015,1792cd23,dev,development
|
||||
2025-11-11T05:21:03Z,ExecuteBacktest_With_Large_Dataset_Should_Show_Performance_Telemetry,5760,1.015,5659.9,15.27,10.17,24.65,886.92,3828,33.2,58.10,0.12,0.01,24560.79,38,24.56,6015,1792cd23,dev,development
|
||||
|
||||
|
Reference in New Issue
Block a user