diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index 959281fe..ab1d7f46 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -17,6 +17,7 @@ using Managing.Domain.Trades; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using static Managing.Common.Enums; using DailySnapshot = Managing.Api.Models.Responses.DailySnapshot; @@ -41,6 +42,7 @@ public class DataController : ControllerBase private readonly IGrainFactory _grainFactory; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IBotService _botService; + private readonly IConfiguration _configuration; /// /// Initializes a new instance of the class. @@ -50,12 +52,12 @@ public class DataController : ControllerBase /// Service for caching data. /// Service for statistical analysis. /// Service for agents - /// SignalR hub context for real-time communication. /// Mediator for handling commands and queries. /// Service for trading operations. /// Orleans grain factory for accessing grains. /// Service scope factory for creating scoped services. /// Service for bot operations. + /// Configuration for accessing environment variables. public DataController( IExchangeService exchangeService, IAccountService accountService, @@ -66,7 +68,8 @@ public class DataController : ControllerBase ITradingService tradingService, IGrainFactory grainFactory, IServiceScopeFactory serviceScopeFactory, - IBotService botService) + IBotService botService, + IConfiguration configuration) { _exchangeService = exchangeService; _accountService = accountService; @@ -78,6 +81,7 @@ public class DataController : ControllerBase _grainFactory = grainFactory; _serviceScopeFactory = serviceScopeFactory; _botService = botService; + _configuration = configuration; } /// @@ -706,8 +710,11 @@ public class DataController : ControllerBase .ToList(); } + // Check environment variable for filtering profitable agents only + var showOnlyProfitableAgent = _configuration.GetValue("showOnlyProfitableAgent", false); + // Get paginated results from database - var command = new GetPaginatedAgentSummariesCommand(page, pageSize, sortBy, sortOrder, agentNamesList); + var command = new GetPaginatedAgentSummariesCommand(page, pageSize, sortBy, sortOrder, agentNamesList, showOnlyProfitableAgent); var result = await _mediator.Send(command); var agentSummaries = result.Results; var totalCount = result.TotalCount; diff --git a/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs b/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs index 63f3aa58..82be378d 100644 --- a/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs @@ -20,13 +20,15 @@ public interface IAgentSummaryRepository /// Field to sort by /// Sort order (asc or desc) /// Optional list of agent names to filter by + /// Whether to show only agents with ROI > 0 /// Tuple containing the paginated results and total count Task<(IEnumerable Results, int TotalCount)> GetPaginatedAsync( int page, int pageSize, SortableFields sortBy, string sortOrder, - IEnumerable? agentNames = null); + IEnumerable? agentNames = null, + bool showOnlyProfitableAgent = false); Task> GetAllAgentWithRunningBots(); /// diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index 79c01a15..9c10217f 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -73,4 +73,12 @@ public interface IBotService /// The ticker to check /// True if the user has a bot on this ticker, false otherwise Task HasUserBotOnTickerAsync(int userId, Ticker ticker); + + /// + /// Checks if the user already has a bot (Running or Saved) with the specified name + /// + /// The user ID + /// The bot name to check + /// True if the user has a bot with this name, false otherwise + Task HasUserBotWithNameAsync(int userId, string name); } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 2398f52c..17225769 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -559,5 +559,13 @@ namespace Managing.Application.ManageBot bot.Ticker == ticker && (bot.Status == BotStatus.Running || bot.Status == BotStatus.Saved)); } + + public async Task HasUserBotWithNameAsync(int userId, string name) + { + var userBots = await _botRepository.GetBotsByUserIdAsync(userId); + return userBots.Any(bot => + bot.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && + (bot.Status == BotStatus.Running || bot.Status == BotStatus.Saved)); + } } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/Commands/GetPaginatedAgentSummariesCommand.cs b/src/Managing.Application/ManageBot/Commands/GetPaginatedAgentSummariesCommand.cs index 928d12c2..a792bfa3 100644 --- a/src/Managing.Application/ManageBot/Commands/GetPaginatedAgentSummariesCommand.cs +++ b/src/Managing.Application/ManageBot/Commands/GetPaginatedAgentSummariesCommand.cs @@ -34,18 +34,25 @@ namespace Managing.Application.ManageBot.Commands /// public IEnumerable? AgentNames { get; } + /// + /// Whether to show only profitable agents (ROI > 0) + /// + public bool ShowOnlyProfitableAgent { get; } + public GetPaginatedAgentSummariesCommand( int page = 1, int pageSize = 10, SortableFields sortBy = SortableFields.NetPnL, string sortOrder = "desc", - IEnumerable? agentNames = null) + IEnumerable? agentNames = null, + bool showOnlyProfitableAgent = false) { Page = page; PageSize = pageSize; SortBy = sortBy; SortOrder = sortOrder; AgentNames = agentNames; + ShowOnlyProfitableAgent = showOnlyProfitableAgent; } } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/GetPaginatedAgentSummariesCommandHandler.cs b/src/Managing.Application/ManageBot/GetPaginatedAgentSummariesCommandHandler.cs index 21edbb7c..ee5c255e 100644 --- a/src/Managing.Application/ManageBot/GetPaginatedAgentSummariesCommandHandler.cs +++ b/src/Managing.Application/ManageBot/GetPaginatedAgentSummariesCommandHandler.cs @@ -27,7 +27,8 @@ namespace Managing.Application.ManageBot request.PageSize, request.SortBy, request.SortOrder, - request.AgentNames); + request.AgentNames, + request.ShowOnlyProfitableAgent); } } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs index 36e606c0..eb519ced 100644 --- a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -54,6 +54,15 @@ namespace Managing.Application.ManageBot "You cannot create multiple strategies on the same ticker."); } + // Check if user already has a bot with this name + var hasExistingBotWithName = await _botService.HasUserBotWithNameAsync(request.User.Id, request.Config.Name); + if (hasExistingBotWithName) + { + throw new InvalidOperationException( + $"You already have a strategy running or saved with the name '{request.Config.Name}'. " + + "You cannot create multiple strategies with the same name."); + } + Account account; if (string.IsNullOrEmpty(request.Config.AccountName)) { diff --git a/src/Managing.Application/ManageBot/StartCopyTradingCommandHandler.cs b/src/Managing.Application/ManageBot/StartCopyTradingCommandHandler.cs index 399ffd5d..46f541cc 100644 --- a/src/Managing.Application/ManageBot/StartCopyTradingCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartCopyTradingCommandHandler.cs @@ -95,6 +95,15 @@ namespace Managing.Application.ManageBot "You cannot create multiple strategies on the same ticker."); } + // Check if user already has a bot with the same name as the master bot + var hasExistingBotWithName = await _botService.HasUserBotWithNameAsync(request.User.Id, masterConfig.Name); + if (hasExistingBotWithName) + { + throw new InvalidOperationException( + $"You already have a strategy running or saved with the name '{masterConfig.Name}'. " + + "You cannot create multiple strategies with the same name."); + } + // Get account information from the requesting user's accounts var userAccounts = await _accountService.GetAccountsByUserAsync(request.User, true, true); var firstAccount = userAccounts.FirstOrDefault(); diff --git a/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs index e27dd620..160999bb 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs @@ -170,7 +170,8 @@ public class AgentSummaryRepository : IAgentSummaryRepository int pageSize, SortableFields sortBy, string sortOrder, - IEnumerable? agentNames = null) + IEnumerable? agentNames = null, + bool showOnlyProfitableAgent = false) { // Start with base query var query = _context.AgentSummaries.Include(a => a.User).AsQueryable(); @@ -181,6 +182,12 @@ public class AgentSummaryRepository : IAgentSummaryRepository query = query.Where(a => agentNames.Contains(a.AgentName)); } + // Apply profitable agent filtering if specified + if (showOnlyProfitableAgent) + { + query = query.Where(a => a.TotalROI > 0); + } + // Get total count before applying pagination var totalCount = await query.CountAsync();