- 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.
146 lines
6.4 KiB
C#
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;
|
|
}
|
|
}
|
|
} |