Add copy trading functionality with StartCopyTrading endpoint and related models. Implemented position copying from master bot and subscription to copy trading stream in LiveTradingBotGrain. Updated TradingBotConfig to support copy trading parameters.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
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 Managing.Domain.Bots;
|
||||
using MediatR;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot
|
||||
{
|
||||
public class StartCopyTradingCommandHandler : IRequestHandler<StartCopyTradingCommand, string>
|
||||
{
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IGrainFactory _grainFactory;
|
||||
private readonly IBotService _botService;
|
||||
|
||||
public StartCopyTradingCommandHandler(
|
||||
IAccountService accountService, IGrainFactory grainFactory, IBotService botService)
|
||||
{
|
||||
_accountService = accountService;
|
||||
_grainFactory = grainFactory;
|
||||
_botService = botService;
|
||||
}
|
||||
|
||||
public async Task<string> Handle(StartCopyTradingCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Validate the trading balance
|
||||
if (request.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Bot trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
|
||||
}
|
||||
|
||||
// Get the master bot configuration
|
||||
var masterBot = await _botService.GetBotByIdentifier(request.MasterBotIdentifier);
|
||||
if (masterBot == null)
|
||||
{
|
||||
throw new ArgumentException($"Master bot with identifier {request.MasterBotIdentifier} not found");
|
||||
}
|
||||
|
||||
// Verify the user owns the master bot keys
|
||||
// if (masterBot.User?.Name != request.User.Name)
|
||||
// {
|
||||
// throw new UnauthorizedAccessException("You don't have permission to copy trades from this bot");
|
||||
// }
|
||||
|
||||
// Get the master bot configuration
|
||||
var masterConfig = await _botService.GetBotConfig(request.MasterBotIdentifier);
|
||||
if (masterConfig == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not retrieve configuration for master bot {request.MasterBotIdentifier}");
|
||||
}
|
||||
|
||||
// Get account information from the requesting user's accounts
|
||||
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 account = firstAccount;
|
||||
|
||||
// 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.BotTradingBalance)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Account {masterConfig.AccountName} has no USDC balance or not enough balance for copy trading");
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce allocation limit across all bots using the same account
|
||||
var availableAllocation = await _botService.GetAvailableAllocationUsdAsync(account, default);
|
||||
if (request.BotTradingBalance > availableAllocation)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Insufficient available allocation. Requested: {request.BotTradingBalance:F2} USDC, " +
|
||||
$"Balance : {usdcBalance?.Value:F2 ?? 0} Available: {availableAllocation:F2} USDC.");
|
||||
}
|
||||
|
||||
// Create copy trading configuration based on master bot
|
||||
var copyTradingConfig = new TradingBotConfig
|
||||
{
|
||||
// Copy all master configuration
|
||||
AccountName = account.Name,
|
||||
MoneyManagement = masterConfig.MoneyManagement,
|
||||
Ticker = masterConfig.Ticker,
|
||||
Scenario = masterConfig.Scenario,
|
||||
ScenarioName = masterConfig.ScenarioName,
|
||||
Timeframe = masterConfig.Timeframe,
|
||||
IsForWatchingOnly = masterConfig.IsForWatchingOnly,
|
||||
BotTradingBalance = request.BotTradingBalance, // Use the provided trading balance
|
||||
CooldownPeriod = masterConfig.CooldownPeriod,
|
||||
MaxLossStreak = masterConfig.MaxLossStreak,
|
||||
MaxPositionTimeHours = masterConfig.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = masterConfig.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = masterConfig.CloseEarlyWhenProfitable,
|
||||
UseSynthApi = masterConfig.UseSynthApi,
|
||||
UseForPositionSizing = masterConfig.UseForPositionSizing,
|
||||
UseForSignalFiltering = masterConfig.UseForSignalFiltering,
|
||||
UseForDynamicStopLoss = masterConfig.UseForDynamicStopLoss,
|
||||
FlipPosition = masterConfig.FlipPosition,
|
||||
|
||||
// Set copy trading specific properties
|
||||
IsForCopyTrading = true,
|
||||
MasterBotIdentifier = request.MasterBotIdentifier,
|
||||
|
||||
// Set computed/default properties
|
||||
IsForBacktest = false,
|
||||
Name = masterConfig.Name
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var botGrain = _grainFactory.GetGrain<ILiveTradingBotGrain>(Guid.NewGuid());
|
||||
await botGrain.CreateAsync(copyTradingConfig, request.User);
|
||||
|
||||
// Start the copy trading bot immediately
|
||||
await botGrain.StartAsync();
|
||||
|
||||
return $"Copy trading bot started successfully, following master bot '{masterConfig.Name}' with {request.BotTradingBalance:F2} USDC balance";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Failed to start copy trading bot: {ex.Message}, {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user