예제 #1
0
파일: TradingEngine.cs 프로젝트: alpo-8/MT
        private void UpdateTrailingStops(Position position)
        {
            var trailingOrderIds = position.RelatedOrders.Where(o => o.Type == OrderType.TrailingStop)
                                   .Select(o => o.Id);

            foreach (var trailingOrderId in trailingOrderIds)
            {
                if (_ordersCache.TryGetOrderById(trailingOrderId, out var trailingOrder) &&
                    trailingOrder.Price.HasValue)
                {
                    if (trailingOrder.TrailingDistance.HasValue)
                    {
                        if (Math.Abs(trailingOrder.Price.Value - position.ClosePrice) >
                            Math.Abs(trailingOrder.TrailingDistance.Value))
                        {
                            var newPrice = position.ClosePrice + trailingOrder.TrailingDistance.Value;
                            trailingOrder.ChangePrice(newPrice,
                                                      _dateService.Now(),
                                                      trailingOrder.Originator,
                                                      null,
                                                      _identityGenerator.GenerateGuid()); //todo in fact price change correlationId must be used
                        }
                    }
                    else
                    {
                        trailingOrder.SetTrailingDistance(position.ClosePrice);
                    }
                }
            }
        }
예제 #2
0
        private bool CheckZeroQuote(ExternalOrderBook orderbook, bool isEodOrderbook)
        {
            // TODO: the code below supposes we have only one quote in orderbook
            var hasZeroVolume    = orderbook.Asks[0].Volume == 0 || orderbook.Bids[0].Volume == 0;
            var hasZeroPrice     = orderbook.Asks[0].Price == 0 || orderbook.Bids[0].Price == 0;
            var isOrderbookValid = !hasZeroVolume && !hasZeroPrice;

            var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(orderbook.AssetPairId);

            if (assetPair == null)
            {
                return(isOrderbookValid);
            }

            //EOD quotes should not change asset pair state
            if (isEodOrderbook)
            {
                return(isOrderbookValid);
            }

            if (!isOrderbookValid)
            {
                // suspend instrument in case of zero volumes only
                if (!assetPair.IsSuspended && hasZeroVolume)
                {
                    assetPair.IsSuspended = true;//todo apply changes to trading engine
                    _cqrsSender.SendCommandToSettingsService(new ChangeProductSuspendedStatusCommand()
                    {
                        ProductId   = assetPair.Id,
                        OperationId = _identityGenerator.GenerateGuid(),
                        Timestamp   = _dateService.Now(),
                        IsSuspended = true,
                    });

                    _log.Info($"Suspending instrument {assetPair.Id}", context: orderbook.ToContextData()?.ToJson());
                }
            }
            else
            {
                if (assetPair.IsSuspended)
                {
                    assetPair.IsSuspended = false;//todo apply changes to trading engine
                    _cqrsSender.SendCommandToSettingsService(new ChangeProductSuspendedStatusCommand
                    {
                        ProductId   = assetPair.Id,
                        OperationId = _identityGenerator.GenerateGuid(),
                        Timestamp   = _dateService.Now(),
                        IsSuspended = false,
                    });

                    _log.Info($"Un-suspending instrument {assetPair.Id}", context: orderbook.ToContextData()?.ToJson());
                }
            }

            return(isOrderbookValid);
        }
예제 #3
0
 public void StartSpecialLiquidation(string[] positionIds, [CanBeNull] string accountId)
 {
     _cqrsSender.SendCommandToSelf(new StartSpecialLiquidationInternalCommand
     {
         OperationId    = _identityGenerator.GenerateGuid(),
         CreationTime   = _dateService.Now(),
         PositionIds    = positionIds,
         AccountId      = accountId,
         AdditionalInfo = LykkeConstants.LiquidationBySystemAdditionalInfo,
         OriginatorType = OriginatorType.System
     });
 }
예제 #4
0
        public async Task <string> PlaceAsync([FromBody] OrderPlaceRequest request)
        {
            var(baseOrder, relatedOrders) = (default(Order), default(List <Order>));
            try
            {
                (baseOrder, relatedOrders) = await _validateOrderService.ValidateRequestAndCreateOrders(request);
            }
            catch (ValidateOrderException exception)
            {
                _cqrsSender.PublishEvent(new OrderPlacementRejectedEvent
                {
                    CorrelationId     = request.CorrelationId ?? _identityGenerator.GenerateGuid(),
                    EventTimestamp    = _dateService.Now(),
                    OrderPlaceRequest = request,
                    RejectReason      = exception.RejectReason.ToType <OrderRejectReasonContract>(),
                    RejectReasonText  = exception.Message,
                });
                throw;
            }

            var placedOrder = await _tradingEngine.PlaceOrderAsync(baseOrder);

            _operationsLogService.AddLog("action order.place", request.AccountId, request.ToJson(),
                                         placedOrder.ToJson());

            if (placedOrder.Status == OrderStatus.Rejected)
            {
                throw new Exception($"Order is rejected: {placedOrder.RejectReason} ({placedOrder.RejectReasonText})");
            }

            foreach (var order in relatedOrders)
            {
                var placedRelatedOrder = await _tradingEngine.PlaceOrderAsync(order);

                _operationsLogService.AddLog("action related.order.place", request.AccountId, request.ToJson(),
                                             placedRelatedOrder.ToJson());
            }

            return(placedOrder.Id);
        }
        private bool CheckZeroQuote(ExternalOrderBook orderbook)
        {
            var isOrderbookValid = orderbook.Asks[0].Volume != 0 && orderbook.Bids[0].Volume != 0;

            var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(orderbook.AssetPairId);

            if (assetPair == null)
            {
                return(isOrderbookValid);
            }

            if (!isOrderbookValid)
            {
                if (!assetPair.IsSuspended)
                {
                    assetPair.IsSuspended = true;//todo apply changes to trading engine
                    _cqrsSender.SendCommandToSettingsService(new SuspendAssetPairCommand
                    {
                        AssetPairId = assetPair.Id,
                        OperationId = _identityGenerator.GenerateGuid(),
                    });
                }
            }
            else
            {
                if (assetPair.IsSuspended)
                {
                    assetPair.IsSuspended = false;//todo apply changes to trading engine
                    _cqrsSender.SendCommandToSettingsService(new UnsuspendAssetPairCommand
                    {
                        AssetPairId = assetPair.Id,
                        OperationId = _identityGenerator.GenerateGuid(),
                    });
                }
            }

            return(isOrderbookValid);
        }
예제 #6
0
        public async Task <string> MakeTradingDataSnapshot([FromQuery] DateTime tradingDay, [FromQuery] string correlationId = null)
        {
            if (tradingDay == default)
            {
                throw new Exception($"{nameof(tradingDay)} must be set");
            }

            if (string.IsNullOrWhiteSpace(correlationId))
            {
                correlationId = _identityGenerator.GenerateGuid();
            }

            return(await _snapshotService.MakeTradingDataSnapshot(tradingDay, correlationId));
        }
예제 #7
0
        public async Task CloseAsync([CanBeNull][FromRoute] string positionId,
                                     [FromBody] PositionCloseRequest request = null)
        {
            if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position))
            {
                throw new InvalidOperationException("Position not found");
            }

            ValidateDayOff(position.AssetPairId);

            var originator = GetOriginator(request?.Originator);

            var correlationId = request?.CorrelationId ?? _identityGenerator.GenerateGuid();

            var order = await _tradingEngine.ClosePositionsAsync(
                new PositionsCloseData(
                    new List <Position> {
                position
            },
                    position.AccountId,
                    position.AssetPairId,
                    position.Volume,
                    position.OpenMatchingEngineId,
                    position.ExternalProviderId,
                    originator,
                    request?.AdditionalInfo,
                    correlationId,
                    position.EquivalentAsset));

            if (order.Status != OrderStatus.Executed && order.Status != OrderStatus.ExecutionStarted)
            {
                throw new InvalidOperationException(order.RejectReasonText);
            }

            _operationsLogService.AddLog("action order.close", order.AccountId, request?.ToJson(),
                                         order.ToJson());
        }
예제 #8
0
        public async Task Handle(MarketStateChangedEvent e)
        {
            if (!e.IsPlatformClosureEvent())
            {
                return;
            }

            var tradingDay = e.EventTimestamp.Date;

            await _log.WriteInfoAsync(nameof(PlatformClosureProjection), nameof(Handle), e.ToJson(),
                                      $"Platform is being closed. Starting trading snapshot backup for [{tradingDay}]");

            var result = await _snapshotService.MakeTradingDataSnapshot(e.EventTimestamp.Date,
                                                                        _identityGenerator.GenerateGuid(),
                                                                        SnapshotStatus.Draft);

            await _log.WriteInfoAsync(nameof(PlatformClosureProjection),
                                      nameof(Handle),
                                      e.ToJson(),
                                      result);
        }
예제 #9
0
        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;
            }

            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(),
                    });
                }
                else
                {
                    var order = new OrderModel(
                        tradeType: command.Volume > 0 ? TradeType.Buy : TradeType.Sell,
                        orderType: OrderType.Market.ToType <Lykke.Service.ExchangeConnector.Client.Models.OrderType>(),
                        timeInForce: TimeInForce.FillOrKill,
                        volume: (double)Math.Abs(command.Volume),
                        dateTime: _dateService.Now(),
                        exchangeName: executionInfo.Data.ExternalProviderId,
                        instrument: command.Instrument,
                        price: (double?)command.Price,
                        orderId: _identityGenerator.GenerateAlphanumericId());

                    try
                    {
                        var executionResult = await _exchangeConnectorService.CreateOrderAsync(order);

                        publisher.PublishEvent(new SpecialLiquidationOrderExecutedEvent
                        {
                            OperationId   = command.OperationId,
                            CreationTime  = _dateService.Now(),
                            MarketMakerId = executionInfo.Data.ExternalProviderId,
                            ExecutionTime = executionResult.Time,
                            OrderId       = executionResult.ExchangeOrderId,
                        });
                    }
                    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);
            }
        }
예제 #10
0
        public async Task <(Order order, List <Order> relatedOrders)> ValidateRequestAndCreateOrders(
            OrderPlaceRequest request)
        {
            #region Validate properties

            if (request.Volume == 0)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume can not be 0");
            }

            var assetPair = GetAssetPairIfAvailableForTrading(request.InstrumentId, request.Type.ToType <OrderType>(),
                                                              request.ForceOpen, false);

            if (request.Type != OrderTypeContract.Market)
            {
                if (!request.Price.HasValue)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice,
                                                     $"Price is required for {request.Type} order");
                }
                else
                {
                    request.Price = Math.Round(request.Price.Value, assetPair.Accuracy);

                    if (request.Price <= 0)
                    {
                        throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice,
                                                         $"Price should be more than 0");
                    }
                }
            }
            else
            {
                //always ignore price for market orders
                request.Price = null;
            }

            var account = _accountsCacheService.TryGet(request.AccountId);

            if (account == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidAccount, "Account not found");
            }

            ValidateValidity(request.Validity, request.Type.ToType <OrderType>());

            ITradingInstrument tradingInstrument;
            try
            {
                tradingInstrument =
                    _tradingInstrumentsCache.GetTradingInstrument(account.TradingConditionId, assetPair.Id);
            }
            catch
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 "Instrument is not available for trading on selected account");
            }

            if (tradingInstrument.DealMinLimit > 0 && Math.Abs(request.Volume) < tradingInstrument.DealMinLimit)
            {
                throw new ValidateOrderException(OrderRejectReason.MinOrderSizeLimit,
                                                 $"The minimum volume of a single order is limited to {tradingInstrument.DealMinLimit} {tradingInstrument.Instrument}.");
            }

            //set special account-quote instrument
//            if (_assetPairsCache.TryGetAssetPairQuoteSubst(order.AccountAssetId, order.Instrument,
//                    order.LegalEntity, out var substAssetPair))
//            {
//                order.MarginCalcInstrument = substAssetPair.Id;
//            }
            if (string.IsNullOrWhiteSpace(request.CorrelationId))
            {
                request.CorrelationId = _identityGenerator.GenerateGuid();
            }

            #endregion

            var equivalentSettings = GetReportingEquivalentPricesSettings(account.LegalEntity);

            if (request.Type == OrderTypeContract.StopLoss ||
                request.Type == OrderTypeContract.TakeProfit ||
                request.Type == OrderTypeContract.TrailingStop)
            {
                var order = await ValidateAndGetSlOrTpOrder(request, request.Type, request.Price, equivalentSettings,
                                                            null);

                return(order, new List <Order>());
            }

            var initialParameters = await GetOrderInitialParameters(request.InstrumentId, account.LegalEntity,
                                                                    equivalentSettings, account.BaseAssetId);

            var volume = request.Direction == OrderDirectionContract.Sell ? -Math.Abs(request.Volume)
                : request.Direction == OrderDirectionContract.Buy ? Math.Abs(request.Volume)
                : request.Volume;

            var originator = GetOriginator(request.Originator);

            var baseOrder = new Order(initialParameters.Id, initialParameters.Code, request.InstrumentId, volume,
                                      initialParameters.Now, initialParameters.Now, request.Validity, account.Id, account.TradingConditionId,
                                      account.BaseAssetId, request.Price, equivalentSettings.EquivalentAsset, OrderFillType.FillOrKill,
                                      string.Empty, account.LegalEntity, request.ForceOpen, request.Type.ToType <OrderType>(),
                                      request.ParentOrderId, null, originator, initialParameters.EquivalentPrice,
                                      initialParameters.FxPrice, initialParameters.FxAssetPairId, initialParameters.FxToAssetPairDirection,
                                      OrderStatus.Placed, request.AdditionalInfo, request.CorrelationId);

            ValidateBaseOrderPrice(baseOrder, baseOrder.Price);

            var relatedOrders = new List <Order>();

            if (request.StopLoss.HasValue)
            {
                request.StopLoss = Math.Round(request.StopLoss.Value, assetPair.Accuracy);

                if (request.StopLoss <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidStoploss,
                                                     $"StopLoss should be more then 0");
                }

                var orderType = request.UseTrailingStop ? OrderTypeContract.TrailingStop : OrderTypeContract.StopLoss;

                var sl = await ValidateAndGetSlOrTpOrder(request, orderType, request.StopLoss,
                                                         equivalentSettings, baseOrder);

                if (sl != null)
                {
                    relatedOrders.Add(sl);
                }
            }

            if (request.TakeProfit.HasValue)
            {
                request.TakeProfit = Math.Round(request.TakeProfit.Value, assetPair.Accuracy);

                if (request.TakeProfit <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidTakeProfit,
                                                     $"TakeProfit should be more then 0");
                }

                var tp = await ValidateAndGetSlOrTpOrder(request, OrderTypeContract.TakeProfit, request.TakeProfit,
                                                         equivalentSettings, baseOrder);

                if (tp != null)
                {
                    relatedOrders.Add(tp);
                }
            }

            return(baseOrder, relatedOrders);
        }
        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);
            }
        }