Files
managing-apps/src/Managing.Application/ManageBot/StartBotCommandHandler.cs
cryptooda e69dd43ace 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.
2025-11-22 13:34:26 +07:00

146 lines
6.4 KiB
C#

using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot.Commands;
using Managing.Common;
using Managing.Domain.Accounts;
using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
public class StartBotCommandHandler : IRequestHandler<StartBotCommand, BotStatus>
{
private readonly IAccountService _accountService;
private readonly IGrainFactory _grainFactory;
private readonly IBotService _botService;
private readonly ITradingService _tradingService;
public StartBotCommandHandler(
IAccountService accountService, IGrainFactory grainFactory, IBotService botService, ITradingService tradingService)
{
_accountService = accountService;
_grainFactory = grainFactory;
_botService = botService;
_tradingService = tradingService;
}
public async Task<BotStatus> Handle(StartBotCommand request, CancellationToken cancellationToken)
{
// Validate the configuration
if (request.Config == null)
{
throw new ArgumentException("Bot configuration is required");
}
if (request.Config.Scenario == null || !request.Config.Scenario.Indicators.Any())
{
throw new InvalidOperationException(
"Scenario or indicators not loaded properly in constructor. This indicates a configuration error.");
}
if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
{
throw new ArgumentException(
$"Bot trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
}
// Check if user already has a bot on this ticker
var hasExistingBotOnTicker = await _botService.HasUserBotOnTickerAsync(request.User.Id, request.Config.Ticker);
if (hasExistingBotOnTicker)
{
throw new InvalidOperationException(
$"You already have a strategy running or saved on ticker {request.Config.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;
if (string.IsNullOrEmpty(request.Config.AccountName))
{
// Fallback: Get the first account for the user
var userAccounts = await _accountService.GetAccountsByUserAsync(request.User, true, true);
var firstAccount = userAccounts.FirstOrDefault();
if (firstAccount == null)
{
throw new InvalidOperationException($"User '{request.User.Name}' has no accounts configured.");
}
account = firstAccount;
}
else
{
account = await _accountService.GetAccount(request.Config.AccountName, true, true);
}
// Check balances for EVM/GMX V2 accounts before starting
if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2)
{
var balanceCheckResult = await _botService.CheckAccountBalancesAsync(account);
if (!balanceCheckResult.IsSuccessful)
{
throw new InvalidOperationException(balanceCheckResult.Message);
}
}
// Check GMX initialization status for GMX V2 accounts
if (account.Exchange == TradingExchanges.GmxV2 && !account.IsGmxInitialized)
{
var initResult = await _tradingService.InitPrivyWallet(account.Key, TradingExchanges.GmxV2);
if (!initResult.Success)
{
throw new InvalidOperationException($"Failed to initialize GMX wallet: {initResult.Error}");
}
}
Balance usdcBalance = null;
// For other exchanges, keep the original USDC balance check
if (account.Exchange != TradingExchanges.Evm && account.Exchange != TradingExchanges.GmxV2)
{
usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString());
if (usdcBalance == null ||
usdcBalance.Value < Constants.GMX.Config.MinimumPositionAmount ||
usdcBalance.Value < request.Config.BotTradingBalance)
{
throw new Exception(
$"Account {request.Config.AccountName} has no USDC balance or not enough balance");
}
}
// Enforce allocation limit across all bots using the same account
var availableAllocation = await _botService.GetAvailableAllocationUsdAsync(account, default);
if (request.Config.BotTradingBalance > availableAllocation)
{
throw new InvalidOperationException(
$"Insufficient available allocation. Requested: {request.Config.BotTradingBalance:F2} USDC, " +
$"Balance : {usdcBalance?.Value:F2 ?? 0} Available: {availableAllocation:F2} USDC.");
}
try
{
var botGrain = _grainFactory.GetGrain<ILiveTradingBotGrain>(Guid.NewGuid());
await botGrain.CreateAsync(request.Config, request.User);
// Only start the bot if createOnly is false
if (!request.CreateOnly)
{
await botGrain.StartAsync();
}
}
catch (Exception ex)
{
throw new Exception($"Failed to start bot: {ex.Message}, {ex.StackTrace}");
}
return request.CreateOnly ? BotStatus.Saved : BotStatus.Running;
}
}
}