Exemple #1
0
        public async Task Handle(LiquidatePositionsInternalCommand command, IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>(
                operationName : LiquidationSaga.OperationName,
                id : command.OperationId);

            if (executionInfo?.Data == null)
            {
                return;
            }

            await _log.WriteInfoAsync(nameof(LiquidationCommandsHandler),
                                      nameof(LiquidatePositionsInternalCommand),
                                      command.ToJson(),
                                      "Checking if position liquidation should be failed");

            var account = _accountsCache.Get(executionInfo.Data.AccountId);

            if (ShouldFailExecution(account.GetAccountLevel(), executionInfo.Data.LiquidationType))
            {
                await _log.WriteWarningAsync(
                    nameof(LiquidationCommandsHandler),
                    nameof(LiquidatePositionsInternalCommand),
                    new { accountId = account.Id, accountLevel = account.GetAccountLevel().ToString() }.ToJson(),
                    $"Unable to liquidate positions since account level is not {ValidAccountLevel.ToString()}.");

                await _failureExecutor.ExecuteAsync(publisher,
                                                    account.Id,
                                                    command.OperationId,
                                                    $"Account level is not {ValidAccountLevel.ToString()}.");

                return;
            }

            var positions = _ordersCache.Positions
                            .GetPositionsByAccountIds(executionInfo.Data.AccountId)
                            .Where(p => command.PositionIds.Contains(p.Id))
                            .ToArray();

            if (!positions.Any())
            {
                publisher.PublishEvent(new PositionsLiquidationFinishedInternalEvent
                {
                    OperationId      = command.OperationId,
                    CreationTime     = _dateService.Now(),
                    LiquidationInfos = command.PositionIds.Select(p =>
                                                                  new LiquidationInfo
                    {
                        PositionId   = p,
                        IsLiquidated = false,
                        Comment      = "Opened position was not found"
                    }).ToList()
                });
                return;
            }

            if (!_liquidationHelper.CheckIfNetVolumeCanBeLiquidated(command.AssetPairId, positions, out var details))
            {
                publisher.PublishEvent(new NotEnoughLiquidityInternalEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    PositionIds  = command.PositionIds,
                    Details      = details
                });
                return;
            }

            var liquidationInfos = new List <LiquidationInfo>();

            var comment = string.Empty;

            switch (executionInfo.Data.LiquidationType)
            {
            case LiquidationType.Mco:
                comment = "MCO liquidation";
                break;

            case LiquidationType.Normal:
                comment = "Liquidation";
                break;

            case LiquidationType.Forced:
                comment = "Close positions group";
                break;
            }

            var positionGroups = positions
                                 .GroupBy(p => (p.AssetPairId, p.AccountId, p.Direction, p
                                                .OpenMatchingEngineId, p.ExternalProviderId, p.EquivalentAsset))
                                 .Select(gr => new PositionsCloseData(
                                             gr.FreezeOrder(),
                                             gr.Key.AccountId,
                                             gr.Key.AssetPairId,
                                             gr.Key.OpenMatchingEngineId,
                                             gr.Key.ExternalProviderId,
                                             executionInfo.Data.OriginatorType,
                                             executionInfo.Data.AdditionalInfo,
                                             gr.Key.EquivalentAsset,
                                             comment));

            foreach (var positionGroup in positionGroups)
            {
                try
                {
                    var(result, order) = await _tradingEngine.ClosePositionsAsync(positionGroup, false);

                    foreach (var position in positionGroup.Positions)
                    {
                        liquidationInfos.Add(new LiquidationInfo
                        {
                            PositionId   = position.Value.Id,
                            IsLiquidated = true,
                            Comment      = order != null ? $"Order: {order.Id}" : result.ToString()
                        });
                    }
                }
                catch (Exception ex)
                {
                    await _log.WriteWarningAsync(nameof(LiquidationCommandsHandler),
                                                 nameof(LiquidatePositionsInternalCommand),
                                                 $"Failed to close positions {string.Join(",", positionGroup.Positions.Select(p => p.Value.Id))} on liquidation operation #{command.OperationId}",
                                                 ex);

                    foreach (var position in positionGroup.Positions)
                    {
                        liquidationInfos.Add(new LiquidationInfo
                        {
                            PositionId   = position.Value.Id,
                            IsLiquidated = false,
                            Comment      = $"Close position failed: {ex.Message}"
                        });
                    }
                }
            }

            publisher.PublishEvent(new PositionsLiquidationFinishedInternalEvent
            {
                OperationId      = command.OperationId,
                CreationTime     = _dateService.Now(),
                LiquidationInfos = liquidationInfos
            });
        }
        private async Task Handle(ExecuteSpecialLiquidationOrderCommand command, IEventPublisher publisher)
        {
            var executionInfo = await _operationExecutionInfoRepository.GetAsync <SpecialLiquidationOperationData>(
                operationName : SpecialLiquidationSaga.OperationName,
                id : command.OperationId);

            if (executionInfo?.Data == null)
            {
                return;
            }

            await _log.WriteInfoAsync(nameof(SpecialLiquidationCommandsHandler),
                                      nameof(ExecuteSpecialLiquidationOrderCommand),
                                      command.ToJson(),
                                      "Checking if position special liquidation should be failed");

            if (!string.IsNullOrEmpty(executionInfo.Data.CausationOperationId))
            {
                await _log.WriteInfoAsync(nameof(SpecialLiquidationCommandsHandler),
                                          nameof(ExecuteSpecialLiquidationOrderCommand),
                                          command.ToJson(),
                                          "Special liquidation is caused by regular liquidation, checking liquidation type.");

                var liquidationInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>(
                    operationName : LiquidationSaga.OperationName,
                    id : executionInfo.Data.CausationOperationId);

                if (liquidationInfo == null)
                {
                    await _log.WriteInfoAsync(nameof(SpecialLiquidationCommandsHandler),
                                              nameof(ExecuteSpecialLiquidationOrderCommand),
                                              command.ToJson(),
                                              "Regular liquidation does not exist, position close will not be failed.");
                }
                else
                {
                    if (liquidationInfo.Data.LiquidationType == LiquidationType.Forced)
                    {
                        await _log.WriteInfoAsync(nameof(SpecialLiquidationCommandsHandler),
                                                  nameof(ExecuteSpecialLiquidationOrderCommand),
                                                  command.ToJson(),
                                                  "Regular liquidation type is Forced (Close All), position close will not be failed.");
                    }
                    else
                    {
                        var account = _accountsCacheService.Get(executionInfo.Data.AccountId);
                        var level   = account.GetAccountLevel();

                        await _log.WriteInfoAsync(nameof(SpecialLiquidationCommandsHandler),
                                                  nameof(ExecuteSpecialLiquidationOrderCommand),
                                                  command.ToJson(),
                                                  $"Current account state: {account.ToJson()}, level: {level}");

                        if (level != ValidAccountLevel)
                        {
                            await _log.WriteWarningAsync(
                                nameof(SpecialLiquidationCommandsHandler),
                                nameof(ExecuteSpecialLiquidationOrderCommand),
                                new { accountId = account.Id, accountLevel = level.ToString() }.ToJson(),
                                $"Unable to execute special liquidation since account level is not {ValidAccountLevel.ToString()}.");

                            publisher.PublishEvent(new SpecialLiquidationCancelledEvent()
                            {
                                OperationId  = command.OperationId,
                                CreationTime = _dateService.Now(),
                                Reason       = $"Account level is not {ValidAccountLevel.ToString()}.",
                            });

                            return;
                        }
                    }
                }
            }

            if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.PriceReceived,
                                               SpecialLiquidationOperationState.ExternalOrderExecuted))
            {
                if (command.Volume == 0)
                {
                    publisher.PublishEvent(new SpecialLiquidationOrderExecutedEvent
                    {
                        OperationId    = command.OperationId,
                        CreationTime   = _dateService.Now(),
                        MarketMakerId  = "ZeroNetVolume",
                        ExecutionTime  = _dateService.Now(),
                        OrderId        = _identityGenerator.GenerateGuid(),
                        ExecutionPrice = command.Price
                    });
                }
                else
                {
                    var operationInfo = new TradeOperationInfo
                    {
                        OperationId   = executionInfo.Id,
                        RequestNumber = executionInfo.Data.RequestNumber
                    };

                    var order = new OrderModel(
                        tradeType: command.Volume > 0 ? TradeType.Buy : TradeType.Sell,
                        orderType: OrderType.Market.ToType <Contracts.ExchangeConnector.OrderType>(),
                        timeInForce: TimeInForce.FillOrKill,
                        volume: (double)Math.Abs(command.Volume),
                        dateTime: _dateService.Now(),
                        exchangeName: operationInfo.ToJson(), //hack, but ExchangeName is not used and we need this info
                                                              // TODO: create a separate field and remove hack (?)
                        instrument: command.Instrument,
                        price: (double?)command.Price,
                        orderId: _identityGenerator.GenerateAlphanumericId(),
                        modality: executionInfo.Data.RequestedFromCorporateActions ? TradeRequestModality.Liquidation_CorporateAction : TradeRequestModality.Liquidation_MarginCall);

                    try
                    {
                        var executionResult = await _exchangeConnectorClient.ExecuteOrder(order);

                        if (!executionResult.Success)
                        {
                            throw new Exception(
                                      $"External order was not executed. Status: {executionResult.ExecutionStatus}. " +
                                      $"Failure: {executionResult.FailureType}");
                        }

                        var executionPrice = (decimal)executionResult.Price == default
                            ? command.Price
                            : (decimal)executionResult.Price;

                        if (executionPrice.EqualsZero())
                        {
                            throw new Exception("Execution price is equal to 0.");
                        }

                        publisher.PublishEvent(new SpecialLiquidationOrderExecutedEvent
                        {
                            OperationId    = command.OperationId,
                            CreationTime   = _dateService.Now(),
                            MarketMakerId  = executionInfo.Data.ExternalProviderId,
                            ExecutionTime  = executionResult.Time,
                            OrderId        = executionResult.ExchangeOrderId,
                            ExecutionPrice = executionPrice
                        });
                    }
                    catch (Exception exception)
                    {
                        publisher.PublishEvent(new SpecialLiquidationOrderExecutionFailedEvent
                        {
                            OperationId  = command.OperationId,
                            CreationTime = _dateService.Now(),
                            Reason       = exception.Message
                        });
                        await _log.WriteWarningAsync(nameof(SpecialLiquidationCommandsHandler),
                                                     nameof(ExecuteSpecialLiquidationOrderCommand),
                                                     $"Failed to execute the order: {order.ToJson()}",
                                                     exception);
                    }
                }

                await _operationExecutionInfoRepository.Save(executionInfo);
            }
        }