diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index bbfda255..cec89f33 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -480,8 +480,23 @@ public class DataController : ControllerBase decimal totalVolume = strategy.Volume; decimal volumeLast24h = strategy.Volume; - // Fetch positions associated with this bot using the provided trading service - var positions = await tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier); + // Use caching for position data in UI context (not critical trading operations) + var cacheKey = $"positions_{strategy.Identifier}"; + var cachedPositions = _cacheService.GetValue>(cacheKey); + + List positions; + if (cachedPositions != null) + { + positions = cachedPositions; + } + else + { + // Fetch positions associated with this bot using the provided trading service + positions = (await tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier)).ToList(); + + // Cache positions for 2 minutes for UI display purposes + _cacheService.SaveValue(cacheKey, positions, TimeSpan.FromMinutes(2)); + } // Calculate win/loss statistics from actual positions (including open positions) int wins = positions.Count(p => p.ProfitAndLoss != null && p.ProfitAndLoss.Realized > 0); diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs index e73be6cd..9a850b7d 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs @@ -273,17 +273,33 @@ public class PostgreSqlTradingRepository : BaseRepositoryWithLogging, ITradingRe { await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); + // Optimized query with explicit JOINs to avoid N+1 queries var position = await _context.Positions .AsNoTracking() - .Include(p => p.User) - .Include(p => p.OpenTrade) - .Include(p => p.StopLossTrade) - .Include(p => p.TakeProfit1Trade) - .Include(p => p.TakeProfit2Trade) - .FirstOrDefaultAsync(p => p.Identifier == identifier) + .Where(p => p.Identifier == identifier) + .Select(p => new + { + Position = p, + User = p.User, + OpenTrade = p.OpenTrade, + StopLossTrade = p.StopLossTrade, + TakeProfit1Trade = p.TakeProfit1Trade, + TakeProfit2Trade = p.TakeProfit2Trade + }) + .FirstOrDefaultAsync() .ConfigureAwait(false); - return PostgreSqlMappers.Map(position ?? throw new InvalidOperationException("Position not found")); + if (position == null) + throw new InvalidOperationException("Position not found"); + + // Manually assign navigation properties to avoid lazy loading + position.Position.User = position.User; + position.Position.OpenTrade = position.OpenTrade; + position.Position.StopLossTrade = position.StopLossTrade; + position.Position.TakeProfit1Trade = position.TakeProfit1Trade; + position.Position.TakeProfit2Trade = position.TakeProfit2Trade; + + return PostgreSqlMappers.Map(position.Position); } finally {