diff --git a/.DS_Store b/.DS_Store
index b062e956..3a04a93b 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/scripts/.DS_Store b/scripts/.DS_Store
index 7d4b09cc..e52549c7 100644
Binary files a/scripts/.DS_Store and b/scripts/.DS_Store differ
diff --git a/src/.DS_Store b/src/.DS_Store
index 88317301..59dd6be1 100644
Binary files a/src/.DS_Store and b/src/.DS_Store differ
diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs
index f019a431..e07f4ad4 100644
--- a/src/Managing.Application/Bots/TradingBotBase.cs
+++ b/src/Managing.Application/Bots/TradingBotBase.cs
@@ -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 the total fees paid as a decimal value.
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;
diff --git a/src/Managing.Domain/Shared/Helpers/TradingBox.cs b/src/Managing.Domain/Shared/Helpers/TradingBox.cs
index 4fee2d4a..3b0fe887 100644
--- a/src/Managing.Domain/Shared/Helpers/TradingBox.cs
+++ b/src/Managing.Domain/Shared/Helpers/TradingBox.cs
@@ -75,10 +75,19 @@ public static class TradingBox
Dictionary preCalculatedIndicatorValues)
{
var signalOnCandles = new List();
- // 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 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(orderedSignals);
// Check if all strategies produced signals - this is required for composite signals
var strategyNames = scenario.Indicators.Select(s => s.Name).ToHashSet();
diff --git a/src/Managing.Workers.Tests/performance-benchmarks.csv b/src/Managing.Workers.Tests/performance-benchmarks.csv
index a5d0486b..229d1b56 100644
--- a/src/Managing.Workers.Tests/performance-benchmarks.csv
+++ b/src/Managing.Workers.Tests/performance-benchmarks.csv
@@ -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