diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs
index 61e222f0..feadefad 100644
--- a/src/Managing.Api/Controllers/DataController.cs
+++ b/src/Managing.Api/Controllers/DataController.cs
@@ -329,31 +329,24 @@ public class DataController : ControllerBase
}
///
- /// Retrieves the top 3 performing strategies based on ROI.
+ /// Retrieves the top 3 performing strategies based on PnL.
///
/// A containing the top performing strategies.
[HttpGet("GetTopStrategies")]
public async Task> GetTopStrategies()
{
- // Get active bots
- var activeBots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Running));
-
- // Calculate PnL for each bot once and store in a list of tuples
- var botsWithPnL = activeBots
- .Select(bot => new { Bot = bot, PnL = bot.Pnl, agentName = bot.User.AgentName })
- .OrderByDescending(item => item.PnL)
- .Take(3)
- .ToList();
+ // Get top 3 bots by PnL directly from database (both running and stopped)
+ var bots = await _mediator.Send(new GetTopBotsByPnLCommand(new[] { BotStatus.Running, BotStatus.Stopped }, 3));
// Map to view model
var topStrategies = new TopStrategiesViewModel
{
- TopStrategies = botsWithPnL
- .Select(item => new StrategyPerformance
+ TopStrategies = bots
+ .Select(bot => new StrategyPerformance
{
- StrategyName = item.Bot.Name,
- PnL = item.PnL,
- AgentName = item.agentName,
+ StrategyName = bot.Name,
+ PnL = bot.Pnl,
+ AgentName = bot.User.AgentName,
})
.ToList()
};
@@ -368,26 +361,19 @@ public class DataController : ControllerBase
[HttpGet("GetTopStrategiesByRoi")]
public async Task> GetTopStrategiesByRoi()
{
- // Get active bots
- var activeBots = await _mediator.Send(new GetBotsByStatusCommand(BotStatus.Running));
-
- // Filter bots with valid ROI data and order by ROI
- var botsWithRoi = activeBots
- .Select(bot => new { Bot = bot, Roi = bot.Roi, PnL = bot.Pnl, Volume = bot.Volume })
- .OrderByDescending(item => item.Roi)
- .Take(3)
- .ToList();
+ // Get top 3 bots by ROI directly from database (both running and stopped)
+ var bots = await _mediator.Send(new GetTopBotsByRoiCommand(new[] { BotStatus.Running, BotStatus.Stopped }, 3));
// Map to view model
var topStrategiesByRoi = new TopStrategiesByRoiViewModel
{
- TopStrategiesByRoi = botsWithRoi
- .Select(item => new StrategyRoiPerformance
+ TopStrategiesByRoi = bots
+ .Select(bot => new StrategyRoiPerformance
{
- StrategyName = item.Bot.Name,
- Roi = item.Roi,
- PnL = item.PnL,
- Volume = item.Volume
+ StrategyName = bot.Name,
+ Roi = bot.Roi,
+ PnL = bot.Pnl,
+ Volume = bot.Volume
})
.ToList()
};
diff --git a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs
index 7e82ee8f..82d902cb 100644
--- a/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs
+++ b/src/Managing.Application.Abstractions/Repositories/IBotRepository.cs
@@ -37,4 +37,20 @@ public interface IBotRepository
string? agentName = null,
string sortBy = "CreateDate",
string sortDirection = "Desc");
+
+ ///
+ /// Gets the top performing bots by PnL from the specified statuses
+ ///
+ /// Bot statuses to include in the query
+ /// Number of top performers to return (default: 3)
+ /// Top performing bots ordered by PnL descending
+ Task> GetTopBotsByPnLAsync(IEnumerable statuses, int count = 3);
+
+ ///
+ /// Gets the top performing bots by ROI from the specified statuses
+ ///
+ /// Bot statuses to include in the query
+ /// Number of top performers to return (default: 3)
+ /// Top performing bots ordered by ROI descending
+ Task> GetTopBotsByRoiAsync(IEnumerable statuses, int count = 3);
}
\ No newline at end of file
diff --git a/src/Managing.Application/ManageBot/Commands/GetTopBotsByPnLCommand.cs b/src/Managing.Application/ManageBot/Commands/GetTopBotsByPnLCommand.cs
new file mode 100644
index 00000000..7bf7c613
--- /dev/null
+++ b/src/Managing.Application/ManageBot/Commands/GetTopBotsByPnLCommand.cs
@@ -0,0 +1,18 @@
+using Managing.Domain.Bots;
+using MediatR;
+using static Managing.Common.Enums;
+
+namespace Managing.Application.ManageBot.Commands
+{
+ public class GetTopBotsByPnLCommand : IRequest>
+ {
+ public IEnumerable Statuses { get; }
+ public int Count { get; }
+
+ public GetTopBotsByPnLCommand(IEnumerable statuses, int count = 3)
+ {
+ Statuses = statuses;
+ Count = count;
+ }
+ }
+}
diff --git a/src/Managing.Application/ManageBot/Commands/GetTopBotsByRoiCommand.cs b/src/Managing.Application/ManageBot/Commands/GetTopBotsByRoiCommand.cs
new file mode 100644
index 00000000..d404fd0e
--- /dev/null
+++ b/src/Managing.Application/ManageBot/Commands/GetTopBotsByRoiCommand.cs
@@ -0,0 +1,18 @@
+using Managing.Domain.Bots;
+using MediatR;
+using static Managing.Common.Enums;
+
+namespace Managing.Application.ManageBot.Commands
+{
+ public class GetTopBotsByRoiCommand : IRequest>
+ {
+ public IEnumerable Statuses { get; }
+ public int Count { get; }
+
+ public GetTopBotsByRoiCommand(IEnumerable statuses, int count = 3)
+ {
+ Statuses = statuses;
+ Count = count;
+ }
+ }
+}
diff --git a/src/Managing.Application/ManageBot/GetTopBotsByPnLCommandHandler.cs b/src/Managing.Application/ManageBot/GetTopBotsByPnLCommandHandler.cs
new file mode 100644
index 00000000..f8213314
--- /dev/null
+++ b/src/Managing.Application/ManageBot/GetTopBotsByPnLCommandHandler.cs
@@ -0,0 +1,16 @@
+using Managing.Application.Abstractions.Repositories;
+using Managing.Application.ManageBot.Commands;
+using Managing.Domain.Bots;
+using MediatR;
+
+namespace Managing.Application.ManageBot
+{
+ public class GetTopBotsByPnLCommandHandler(IBotRepository botRepository)
+ : IRequestHandler>
+ {
+ public async Task> Handle(GetTopBotsByPnLCommand request, CancellationToken cancellationToken)
+ {
+ return await botRepository.GetTopBotsByPnLAsync(request.Statuses, request.Count);
+ }
+ }
+}
diff --git a/src/Managing.Application/ManageBot/GetTopBotsByRoiCommandHandler.cs b/src/Managing.Application/ManageBot/GetTopBotsByRoiCommandHandler.cs
new file mode 100644
index 00000000..52a01c75
--- /dev/null
+++ b/src/Managing.Application/ManageBot/GetTopBotsByRoiCommandHandler.cs
@@ -0,0 +1,16 @@
+using Managing.Application.Abstractions.Repositories;
+using Managing.Application.ManageBot.Commands;
+using Managing.Domain.Bots;
+using MediatR;
+
+namespace Managing.Application.ManageBot
+{
+ public class GetTopBotsByRoiCommandHandler(IBotRepository botRepository)
+ : IRequestHandler>
+ {
+ public async Task> Handle(GetTopBotsByRoiCommand request, CancellationToken cancellationToken)
+ {
+ return await botRepository.GetTopBotsByRoiAsync(request.Statuses, request.Count);
+ }
+ }
+}
diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs
index 8dd64d5a..955f4b27 100644
--- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs
+++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBotRepository.cs
@@ -239,4 +239,32 @@ public class PostgreSqlBotRepository : IBotRepository
var bots = PostgreSqlMappers.Map(entities);
return (bots, totalCount);
}
+
+ public async Task> GetTopBotsByPnLAsync(IEnumerable statuses, int count = 3)
+ {
+ var entities = await _context.Bots
+ .AsNoTracking()
+ .Include(m => m.User)
+ .Where(b => statuses.Contains(b.Status))
+ .OrderByDescending(b => b.Pnl)
+ .Take(count)
+ .ToListAsync()
+ .ConfigureAwait(false);
+
+ return PostgreSqlMappers.Map(entities);
+ }
+
+ public async Task> GetTopBotsByRoiAsync(IEnumerable statuses, int count = 3)
+ {
+ var entities = await _context.Bots
+ .AsNoTracking()
+ .Include(m => m.User)
+ .Where(b => statuses.Contains(b.Status))
+ .OrderByDescending(b => b.Roi)
+ .Take(count)
+ .ToListAsync()
+ .ConfigureAwait(false);
+
+ return PostgreSqlMappers.Map(entities);
+ }
}
\ No newline at end of file