From c297429b18235259fe770c0ef8a4ecfd5ae2c3fb Mon Sep 17 00:00:00 2001 From: cryptooda Date: Thu, 25 Sep 2025 11:08:28 +0700 Subject: [PATCH] Fix bot TOP 3 --- .../Controllers/DataController.cs | 46 +++++++------------ .../Repositories/IBotRepository.cs | 16 +++++++ .../Commands/GetTopBotsByPnLCommand.cs | 18 ++++++++ .../Commands/GetTopBotsByRoiCommand.cs | 18 ++++++++ .../GetTopBotsByPnLCommandHandler.cs | 16 +++++++ .../GetTopBotsByRoiCommandHandler.cs | 16 +++++++ .../PostgreSql/PostgreSqlBotRepository.cs | 28 +++++++++++ 7 files changed, 128 insertions(+), 30 deletions(-) create mode 100644 src/Managing.Application/ManageBot/Commands/GetTopBotsByPnLCommand.cs create mode 100644 src/Managing.Application/ManageBot/Commands/GetTopBotsByRoiCommand.cs create mode 100644 src/Managing.Application/ManageBot/GetTopBotsByPnLCommandHandler.cs create mode 100644 src/Managing.Application/ManageBot/GetTopBotsByRoiCommandHandler.cs 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