Files
managing-apps/src/Managing.Application/Trading/Handlers/ClosePositionCommandHandler.cs
2025-08-15 01:23:39 +07:00

110 lines
4.7 KiB
C#

using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Grains;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Trading.Handlers;
public class ClosePositionCommandHandler(
IExchangeService exchangeService,
IAccountService accountService,
ITradingService tradingService,
IGrainFactory? grainFactory = null,
ILogger<ClosePositionCommandHandler> logger = null)
: ICommandHandler<ClosePositionCommand, Position>
{
public async Task<Position> Handle(ClosePositionCommand request)
{
try
{
// Get Trade
var account = await accountService.GetAccount(request.Position.AccountName, false, false);
if (request.Position == null)
{
_ = await exchangeService.CancelOrder(account, request.Position.Ticker);
return request.Position;
}
var isForPaperTrading = request.IsForBacktest;
var lastPrice = request.Position.Initiator == PositionInitiator.PaperTrading
? request.ExecutionPrice.GetValueOrDefault()
: await exchangeService.GetPrice(account, request.Position.Ticker, DateTime.UtcNow);
// Check if position still open
if (!request.IsForBacktest)
{
var p = (await exchangeService.GetBrokerPositions(account))
.FirstOrDefault(x => x.Ticker == request.Position.Ticker);
// Position not available on the broker, so be sure to update the status
if (p == null)
{
request.Position.Status = PositionStatus.Finished;
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, request.Position.Open.Quantity, lastPrice,
request.Position.Open.Leverage);
await tradingService.UpdatePositionAsync(request.Position);
return request.Position;
}
}
var closeRequestedOrders =
true; // TODO: For gmx no need to close orders since they are closed automatically
// Close market
var closedPosition =
await exchangeService.ClosePosition(account, request.Position, lastPrice, isForPaperTrading);
if (closeRequestedOrders || closedPosition.Status == (TradeStatus.PendingOpen | TradeStatus.Filled))
{
request.Position.Status = PositionStatus.Finished;
request.Position.ProfitAndLoss =
TradingBox.GetProfitAndLoss(request.Position, closedPosition.Quantity, lastPrice,
request.Position.Open.Leverage);
if (!request.IsForBacktest)
await tradingService.UpdatePositionAsync(request.Position);
// Notify platform summary about the closed position
try
{
var platformGrain = grainFactory?.GetGrain<IPlatformSummaryGrain>("platform-summary");
if (platformGrain != null)
{
var positionClosedEvent = new PositionClosedEvent
{
PositionId = request.Position.Identifier,
Ticker = request.Position.Ticker,
RealizedPnL = request.Position.ProfitAndLoss?.Realized ?? 0,
Volume = closedPosition.Quantity * lastPrice * request.Position.Open.Leverage,
InitialVolume = request.Position.Open.Quantity * request.Position.Open.Price * request.Position.Open.Leverage
};
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
}
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
logger?.LogError(ex, "Failed to notify platform summary about position closure for position {PositionId}", request.Position.Identifier);
}
}
return request.Position;
}
catch (Exception ex)
{
logger?.LogError(ex, "Error closing position: {Message} \n Stacktrace : {StackTrace}", ex.Message,
ex.StackTrace);
SentrySdk.CaptureException(ex);
throw;
}
}
}