Ejemplo n.º 1
0
        public async Task <MatchedOrderCollection> MatchOrderAsync(Order order, bool shouldOpenNewPosition,
                                                                   OrderModality modality = OrderModality.Regular)
        {
            List <(string source, decimal?price)> prices = null;

            if (!string.IsNullOrEmpty(_marginTradingSettings.DefaultExternalExchangeId))
            {
                var quote = _quoteCacheService.GetQuote(order.AssetPairId);

                if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume))
                {
                    prices = new List <(string source, decimal?price)>
                    {
                        (_marginTradingSettings
                         .DefaultExternalExchangeId, quote.GetPriceForOrderDirection(order.Direction))
                    };
                }
            }

            if (prices == null)
            {
                prices = _externalOrderbookService.GetOrderedPricesForExecution(order.AssetPairId, order.Volume, shouldOpenNewPosition);

                if (prices == null || !prices.Any())
                {
                    return(new MatchedOrderCollection());
                }
            }

            var assetPair         = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId);
            var externalAssetPair = assetPair?.BasePairId ?? order.AssetPairId;

            foreach (var(source, price) in prices
                     .Where(x => string.IsNullOrEmpty(order.ExternalProviderId) || x.source == order.ExternalProviderId))
            {
                var externalOrderModel = new OrderModel();

                var orderType = order.OrderType == Core.Orders.OrderType.Limit ||
                                order.OrderType == Core.Orders.OrderType.TakeProfit
                    ? Core.Orders.OrderType.Limit
                    : Core.Orders.OrderType.Market;

                var isCancellationTrade = order.AdditionalInfo.IsCancellationTrade(out var cancellationTradeExternalId);

                var targetPrice = order.OrderType != Core.Orders.OrderType.Market || isCancellationTrade
                    ? (double?)order.Price
                    : (double?)price;

                try
                {
                    externalOrderModel = new OrderModel(
                        tradeType: order.Direction.ToType <TradeType>(),
                        orderType: orderType.ToType <OrderType>(),
                        timeInForce: TimeInForce.FillOrKill,
                        volume: (double)Math.Abs(order.Volume),
                        dateTime: _dateService.Now(),
                        exchangeName: source,
                        instrument: externalAssetPair,
                        price: targetPrice,
                        orderId: order.Id,
                        modality: modality.ToType <TradeRequestModality>(),
                        isCancellationTrade: isCancellationTrade,
                        cancellationTradeExternalId: cancellationTradeExternalId);

                    var cts = new CancellationTokenSource();
                    cts.CancelAfter(_marginTradingSettings.GavelTimeout);

                    var executionResult = await _exchangeConnectorClient.ExecuteOrder(externalOrderModel, cts.Token);

                    if (!executionResult.Success)
                    {
                        var ex = new Exception(
                            $"External order was not executed. Status: {executionResult.ExecutionStatus}. Failure: {executionResult.FailureType}");
                        LogOrderExecutionException(order, externalOrderModel, ex);
                    }
                    else
                    {
                        var executedPrice = Math.Abs(executionResult.Price) > 0
                            ? (decimal)executionResult.Price
                            : price.Value;

                        if (executedPrice.EqualsZero())
                        {
                            var ex = new Exception($"Have got execution price from Gavel equal to 0. Ignoring.");
                            LogOrderExecutionException(order, externalOrderModel, ex);
                        }
                        else
                        {
                            var matchedOrders = new MatchedOrderCollection
                            {
                                new MatchedOrder
                                {
                                    MarketMakerId = source,
                                    MatchedDate   = _dateService.Now(),
                                    OrderId       = executionResult.ExchangeOrderId,
                                    Price         = CalculatePriceWithMarkups(assetPair, order.Direction, executedPrice),
                                    Volume        = (decimal)executionResult.Volume,
                                    IsExternal    = true
                                }
                            };

                            await _rabbitMqNotifyService.ExternalOrder(executionResult);

                            _operationsLogService.AddLog("external order executed", order.AccountId,
                                                         externalOrderModel.ToJson(), executionResult.ToJson());

                            return(matchedOrders);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogOrderExecutionException(order, externalOrderModel, ex);
                    throw new OrderExecutionTechnicalException();
                }
            }

            return(new MatchedOrderCollection());
        }
        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);
            }
        }