Files
managing-apps/src/Managing.Application/ManageBot/StartBotCommandHandler.cs

125 lines
5.3 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;
public StartBotCommandHandler(
IAccountService accountService, IGrainFactory grainFactory, IBotService botService)
{
_accountService = accountService;
_grainFactory = grainFactory;
_botService = botService;
}
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.");
}
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);
}
}
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;
}
}
}