using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Trading.Commands; using Managing.Common; using Managing.Core.Exceptions; using Managing.Domain.Shared.Helpers; using Managing.Domain.Trades; using static Managing.Common.Enums; namespace Managing.Application.Trading.Handlers { public class OpenPositionCommandHandler( IExchangeService exchangeService, IAccountService accountService, ITradingService tradingService, IGrainFactory grainFactory = null) : ICommandHandler { public async Task Handle(OpenPositionRequest request) { var account = await accountService.GetAccount(request.AccountName, hideSecrets: false, getBalance: false); var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator; var position = new Position(Guid.NewGuid(), account.Id, request.Direction, request.Ticker, request.MoneyManagement, initiator, request.Date, request.User); if (!string.IsNullOrEmpty(request.SignalIdentifier)) { position.SignalIdentifier = request.SignalIdentifier; } position.InitiatorIdentifier = request.InitiatorIdentifier; // Always use BotTradingBalance directly as the balance to risk // Round to 2 decimal places to prevent precision errors decimal balanceToRisk = Math.Round(request.AmountToTrade, 2, MidpointRounding.ToZero); // Minimum check if (balanceToRisk < Constants.GMX.Config.MinimumPositionAmount) { throw new Exception( $"Bot trading balance of {balanceToRisk} USD is less than the minimum {Constants.GMX.Config.MinimumPositionAmount} USD required to trade"); } // Gas fee check for EVM exchanges if (!request.IsForPaperTrading) { if (account.Exchange == TradingExchanges.Evm || account.Exchange == TradingExchanges.GmxV2) { var currentGasFees = await exchangeService.GetFee(account); if (currentGasFees > Constants.GMX.Config.MaximumGasFeeUsd) { throw new InsufficientFundsException( $"Gas fee too high for position opening: {currentGasFees:F2} USD (threshold: {Constants.GMX.Config.MaximumGasFeeUsd} USD). Position opening rejected.", InsufficientFundsType.HighNetworkFee); } } } var price = request.IsForPaperTrading && request.Price.HasValue ? request.Price.Value : await exchangeService.GetPrice(account, request.Ticker, DateTime.Now); var quantity = balanceToRisk / price; var openPrice = request.IsForPaperTrading || request.Price.HasValue ? request.Price.Value : await exchangeService.GetBestPrice(account, request.Ticker, price, quantity, request.Direction); // Determine SL/TP Prices var stopLossPrice = RiskHelpers.GetStopLossPrice(request.Direction, openPrice, request.MoneyManagement); var takeProfitPrice = RiskHelpers.GetTakeProfitPrice(request.Direction, openPrice, request.MoneyManagement); var trade = await exchangeService.OpenTrade( account, request.Ticker, request.Direction, openPrice, quantity, request.MoneyManagement.Leverage, TradeType.Limit, isForPaperTrading: request.IsForPaperTrading, currentDate: request.Date, stopLossPrice: stopLossPrice, takeProfitPrice: takeProfitPrice); position.Open = trade; // Calculate and set fees for the position position.GasFees = TradingHelpers.CalculateOpeningGasFees(); // Set UI fees for opening var positionSizeUsd = TradingHelpers.GetVolumeForPosition(position); position.UiFees = TradingHelpers.CalculateOpeningUiFees(positionSizeUsd); var closeDirection = request.Direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long; // Stop loss position.StopLoss = exchangeService.BuildEmptyTrade( request.Ticker, stopLossPrice, position.Open.Quantity, closeDirection, request.MoneyManagement.Leverage, TradeType.StopLoss, request.Date, TradeStatus.Requested); // Take profit position.TakeProfit1 = exchangeService.BuildEmptyTrade( request.Ticker, takeProfitPrice, quantity, closeDirection, request.MoneyManagement.Leverage, TradeType.TakeProfit, request.Date, TradeStatus.Requested); position.Status = IsOpenTradeHandled(position.Open.Status) ? position.Status : PositionStatus.Rejected; if (!request.IsForPaperTrading) { await tradingService.InsertPositionAsync(position); } return position; } private static bool IsOpenTradeHandled(TradeStatus tradeStatus) { return tradeStatus == TradeStatus.Filled || tradeStatus == TradeStatus.Requested; } } }