Enhance DataController and BotService with new configuration and bot name checks

- Added IConfiguration dependency to DataController for environment variable access.
- Updated GetPaginatedAgentSummariesCommand to include a flag for filtering profitable agents.
- Implemented HasUserBotWithNameAsync method in IBotService and BotService to check for existing bots by name.
- Modified StartBotCommandHandler and StartCopyTradingCommandHandler to prevent duplicate bot names during strategy creation.
This commit is contained in:
2025-11-22 13:34:26 +07:00
parent 269bbfaab0
commit e69dd43ace
9 changed files with 65 additions and 7 deletions

View File

@@ -17,6 +17,7 @@ using Managing.Domain.Trades;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using static Managing.Common.Enums; using static Managing.Common.Enums;
using DailySnapshot = Managing.Api.Models.Responses.DailySnapshot; using DailySnapshot = Managing.Api.Models.Responses.DailySnapshot;
@@ -41,6 +42,7 @@ public class DataController : ControllerBase
private readonly IGrainFactory _grainFactory; private readonly IGrainFactory _grainFactory;
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IBotService _botService; private readonly IBotService _botService;
private readonly IConfiguration _configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DataController"/> class. /// Initializes a new instance of the <see cref="DataController"/> class.
@@ -50,12 +52,12 @@ public class DataController : ControllerBase
/// <param name="cacheService">Service for caching data.</param> /// <param name="cacheService">Service for caching data.</param>
/// <param name="statisticService">Service for statistical analysis.</param> /// <param name="statisticService">Service for statistical analysis.</param>
/// <param name="agentService">Service for agents</param> /// <param name="agentService">Service for agents</param>
/// <param name="hubContext">SignalR hub context for real-time communication.</param>
/// <param name="mediator">Mediator for handling commands and queries.</param> /// <param name="mediator">Mediator for handling commands and queries.</param>
/// <param name="tradingService">Service for trading operations.</param> /// <param name="tradingService">Service for trading operations.</param>
/// <param name="grainFactory">Orleans grain factory for accessing grains.</param> /// <param name="grainFactory">Orleans grain factory for accessing grains.</param>
/// <param name="serviceScopeFactory">Service scope factory for creating scoped services.</param> /// <param name="serviceScopeFactory">Service scope factory for creating scoped services.</param>
/// <param name="botService">Service for bot operations.</param> /// <param name="botService">Service for bot operations.</param>
/// <param name="configuration">Configuration for accessing environment variables.</param>
public DataController( public DataController(
IExchangeService exchangeService, IExchangeService exchangeService,
IAccountService accountService, IAccountService accountService,
@@ -66,7 +68,8 @@ public class DataController : ControllerBase
ITradingService tradingService, ITradingService tradingService,
IGrainFactory grainFactory, IGrainFactory grainFactory,
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
IBotService botService) IBotService botService,
IConfiguration configuration)
{ {
_exchangeService = exchangeService; _exchangeService = exchangeService;
_accountService = accountService; _accountService = accountService;
@@ -78,6 +81,7 @@ public class DataController : ControllerBase
_grainFactory = grainFactory; _grainFactory = grainFactory;
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_botService = botService; _botService = botService;
_configuration = configuration;
} }
/// <summary> /// <summary>
@@ -706,8 +710,11 @@ public class DataController : ControllerBase
.ToList(); .ToList();
} }
// Check environment variable for filtering profitable agents only
var showOnlyProfitableAgent = _configuration.GetValue<bool>("showOnlyProfitableAgent", false);
// Get paginated results from database // 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 result = await _mediator.Send(command);
var agentSummaries = result.Results; var agentSummaries = result.Results;
var totalCount = result.TotalCount; var totalCount = result.TotalCount;

View File

@@ -20,13 +20,15 @@ public interface IAgentSummaryRepository
/// <param name="sortBy">Field to sort by</param> /// <param name="sortBy">Field to sort by</param>
/// <param name="sortOrder">Sort order (asc or desc)</param> /// <param name="sortOrder">Sort order (asc or desc)</param>
/// <param name="agentNames">Optional list of agent names to filter by</param> /// <param name="agentNames">Optional list of agent names to filter by</param>
/// <param name="showOnlyProfitableAgent">Whether to show only agents with ROI > 0</param>
/// <returns>Tuple containing the paginated results and total count</returns> /// <returns>Tuple containing the paginated results and total count</returns>
Task<(IEnumerable<AgentSummary> Results, int TotalCount)> GetPaginatedAsync( Task<(IEnumerable<AgentSummary> Results, int TotalCount)> GetPaginatedAsync(
int page, int page,
int pageSize, int pageSize,
SortableFields sortBy, SortableFields sortBy,
string sortOrder, string sortOrder,
IEnumerable<string>? agentNames = null); IEnumerable<string>? agentNames = null,
bool showOnlyProfitableAgent = false);
Task<IEnumerable<AgentSummary>> GetAllAgentWithRunningBots(); Task<IEnumerable<AgentSummary>> GetAllAgentWithRunningBots();
/// <summary> /// <summary>

View File

@@ -73,4 +73,12 @@ public interface IBotService
/// <param name="ticker">The ticker to check</param> /// <param name="ticker">The ticker to check</param>
/// <returns>True if the user has a bot on this ticker, false otherwise</returns> /// <returns>True if the user has a bot on this ticker, false otherwise</returns>
Task<bool> HasUserBotOnTickerAsync(int userId, Ticker ticker); Task<bool> HasUserBotOnTickerAsync(int userId, Ticker ticker);
/// <summary>
/// Checks if the user already has a bot (Running or Saved) with the specified name
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="name">The bot name to check</param>
/// <returns>True if the user has a bot with this name, false otherwise</returns>
Task<bool> HasUserBotWithNameAsync(int userId, string name);
} }

View File

@@ -559,5 +559,13 @@ namespace Managing.Application.ManageBot
bot.Ticker == ticker && bot.Ticker == ticker &&
(bot.Status == BotStatus.Running || bot.Status == BotStatus.Saved)); (bot.Status == BotStatus.Running || bot.Status == BotStatus.Saved));
} }
public async Task<bool> 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));
}
} }
} }

View File

@@ -34,18 +34,25 @@ namespace Managing.Application.ManageBot.Commands
/// </summary> /// </summary>
public IEnumerable<string>? AgentNames { get; } public IEnumerable<string>? AgentNames { get; }
/// <summary>
/// Whether to show only profitable agents (ROI > 0)
/// </summary>
public bool ShowOnlyProfitableAgent { get; }
public GetPaginatedAgentSummariesCommand( public GetPaginatedAgentSummariesCommand(
int page = 1, int page = 1,
int pageSize = 10, int pageSize = 10,
SortableFields sortBy = SortableFields.NetPnL, SortableFields sortBy = SortableFields.NetPnL,
string sortOrder = "desc", string sortOrder = "desc",
IEnumerable<string>? agentNames = null) IEnumerable<string>? agentNames = null,
bool showOnlyProfitableAgent = false)
{ {
Page = page; Page = page;
PageSize = pageSize; PageSize = pageSize;
SortBy = sortBy; SortBy = sortBy;
SortOrder = sortOrder; SortOrder = sortOrder;
AgentNames = agentNames; AgentNames = agentNames;
ShowOnlyProfitableAgent = showOnlyProfitableAgent;
} }
} }
} }

View File

@@ -27,7 +27,8 @@ namespace Managing.Application.ManageBot
request.PageSize, request.PageSize,
request.SortBy, request.SortBy,
request.SortOrder, request.SortOrder,
request.AgentNames); request.AgentNames,
request.ShowOnlyProfitableAgent);
} }
} }
} }

View File

@@ -54,6 +54,15 @@ namespace Managing.Application.ManageBot
"You cannot create multiple strategies on the same ticker."); "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; Account account;
if (string.IsNullOrEmpty(request.Config.AccountName)) if (string.IsNullOrEmpty(request.Config.AccountName))
{ {

View File

@@ -95,6 +95,15 @@ namespace Managing.Application.ManageBot
"You cannot create multiple strategies on the same ticker."); "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 // Get account information from the requesting user's accounts
var userAccounts = await _accountService.GetAccountsByUserAsync(request.User, true, true); var userAccounts = await _accountService.GetAccountsByUserAsync(request.User, true, true);
var firstAccount = userAccounts.FirstOrDefault(); var firstAccount = userAccounts.FirstOrDefault();

View File

@@ -170,7 +170,8 @@ public class AgentSummaryRepository : IAgentSummaryRepository
int pageSize, int pageSize,
SortableFields sortBy, SortableFields sortBy,
string sortOrder, string sortOrder,
IEnumerable<string>? agentNames = null) IEnumerable<string>? agentNames = null,
bool showOnlyProfitableAgent = false)
{ {
// Start with base query // Start with base query
var query = _context.AgentSummaries.Include(a => a.User).AsQueryable(); 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)); 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 // Get total count before applying pagination
var totalCount = await query.CountAsync(); var totalCount = await query.CountAsync();