Example #1
0
        public Task <bool> IsInstrumentEnabled(string assetPairId)
        {
            _assetPairsCache.GetAssetPairById(assetPairId);

            var isEnabled = !_assetPairDayOffService.IsDayOff(assetPairId);

            return(Task.FromResult(isEnabled));
        }
Example #2
0
        private (string AssetPairId, string[] Positions)? GetLiquidationData(
            LiquidationOperationData data)
        {
            var positionsOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(data.AccountId);

            //group positions and take only not processed, filtered and with open market
            var positionGroups = positionsOnAccount
                                 .Where(p => !data.ProcessedPositionIds.Contains(p.Id) &&
                                        (string.IsNullOrEmpty(data.AssetPairId) || p.AssetPairId == data.AssetPairId) &&
                                        (data.Direction == null || p.Direction == data.Direction))
                                 .GroupBy(p => p.AssetPairId)
                                 .Where(gr => !_assetPairDayOffService.IsDayOff(gr.Key))
                                 .ToArray();

            IGrouping <string, Position> targetPositions = null;

            //take positions from group with max margin used or max initially used margin
            targetPositions = positionGroups
                              .OrderByDescending(gr => gr.Sum(p => Math.Max(p.GetMarginMaintenance(), p.GetInitialMargin())))
                              .FirstOrDefault();

            if (targetPositions == null)
            {
                return(null);
            }

            return(targetPositions.Key, targetPositions.Select(p => p.Id).ToArray());
        }
Example #3
0
        public void ProcessOrderCommands(MarketMakerOrderCommandsBatchMessage batch)
        {
            batch.AssetPairId.RequiredNotNullOrWhiteSpace(nameof(batch.AssetPairId));
            batch.Commands.RequiredNotNull(nameof(batch.Commands));

            if (_maintenanceModeService.CheckIsEnabled())
            {
                return;
            }

            if (_assetPairDayOffService.IsDayOff(batch.AssetPairId))
            {
                return;
            }

            var model = new SetOrderModel {
                MarketMakerId = batch.MarketMakerId
            };

            ConvertCommandsToOrders(batch, model);
            if (model.OrdersToAdd?.Count > 0 || model.DeleteByInstrumentsBuy?.Count > 0 ||
                model.DeleteByInstrumentsSell?.Count > 0)
            {
                _matchingEngine.SetOrders(model);
            }
        }
Example #4
0
        public Task SetQuote(ExternalExchangeOrderbookMessage orderBookMessage)
        {
            var isDayOff       = _assetPairDayOffService.IsDayOff(orderBookMessage.AssetPairId);
            var isEodOrderbook = orderBookMessage.ExchangeName == ExternalOrderbookService.EodExternalExchange;

            // we should process normal orderbook only if asset is currently tradable
            if (isDayOff && !isEodOrderbook)
            {
                return(Task.CompletedTask);
            }

            // and process EOD orderbook only if asset is currently not tradable
            if (!isDayOff && isEodOrderbook)
            {
                _log.WriteWarning("EOD FX quotes processing", "",
                                  $"EOD FX quote for {orderBookMessage.AssetPairId} is skipped, because instrument is within trading hours");

                return(Task.CompletedTask);
            }

            var bidAskPair = CreatePair(orderBookMessage);

            if (bidAskPair == null)
            {
                return(Task.CompletedTask);
            }

            SetQuote(bidAskPair);

            _fxBestPriceChangeEventChannel.SendEvent(this, new FxBestPriceChangeEventArgs(bidAskPair));

            return(Task.CompletedTask);
        }
Example #5
0
 private void ValidateDayOff(params string[] assetPairIds)
 {
     foreach (var instrument in assetPairIds)
     {
         if (_assetDayOffService.IsDayOff(instrument))
         {
             throw new InvalidOperationException($"Trades for {instrument} are not available");
         }
     }
 }
Example #6
0
        public IAssetPair GetAssetPairIfAvailableForTrading(string assetPairId, OrderType orderType,
                                                            bool shouldOpenNewPosition, bool isPreTradeValidation)
        {
            if (isPreTradeValidation || orderType == OrderType.Market)
            {
                if (_assetDayOffService.IsDayOff(assetPairId))
                {
                    throw new ValidateOrderException(OrderRejectReason.NoLiquidity,
                                                     "Trades for instrument are not available");
                }
            }
            else if (_assetDayOffService.ArePendingOrdersDisabled(assetPairId))
            {
                throw new ValidateOrderException(OrderRejectReason.NoLiquidity,
                                                 "Pending orders for instrument are not available");
            }

            var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(assetPairId);

            if (assetPair == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, "Instrument not found");
            }

            if (assetPair.IsDiscontinued)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 "Trading for the instrument is discontinued");
            }

            if (assetPair.IsSuspended)
            {
                if (isPreTradeValidation)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                     "Orders execution for instrument is temporarily unavailable");
                }

                if (orderType == OrderType.Market)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                     "Market orders for instrument are temporarily unavailable");
                }
            }

            if (assetPair.IsFrozen && shouldOpenNewPosition)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 "Opening new positions is temporarily unavailable");
            }

            return(assetPair);
        }
Example #7
0
        public async Task <BackendResponse <bool> > CloseOrder([FromBody] CloseOrderBackendRequest request)
        {
            if (!_ordersCache.ActiveOrders.TryGetOrderById(request.OrderId, out var order))
            {
                return(BackendResponse <bool> .Error("Order not found"));
            }

            if (_assetDayOffService.IsDayOff(order.Instrument))
            {
                return(BackendResponse <bool> .Error("Trades for instrument are not available"));
            }

            if (order.ClientId != request.ClientId || order.AccountId != request.AccountId)
            {
                return(BackendResponse <bool> .Error("Order is not available for user"));
            }

            if (request.IsForcedByBroker && string.IsNullOrEmpty(request.Comment))
            {
                return(BackendResponse <bool> .Error("For operation forced by broker, comment is mandatory"));
            }

            var reason = request.IsForcedByBroker ? OrderCloseReason.ClosedByBroker : OrderCloseReason.Close;

            order = await _tradingEngine.CloseActiveOrderAsync(request.OrderId, reason, request.Comment);

            var result = new BackendResponse <bool>
            {
                Result  = order.Status == OrderStatus.Closed || order.Status == OrderStatus.Closing,
                Message = order.CloseRejectReasonText
            };

            _consoleWriter.WriteLine(
                $"action order.close for clientId = {request.ClientId}, orderId = {request.OrderId}");
            _operationsLogService.AddLog("action order.close", request.ClientId, order.AccountId, request.ToJson(),
                                         result.ToJson());

            return(result);
        }
Example #8
0
        public async Task <MtBackendResponse <bool> > CloseOrder([FromBody] CloseOrderBackendRequest request)
        {
            if (!_ordersCache.ActiveOrders.TryGetOrderById(request.OrderId, out var order))
            {
                return(new MtBackendResponse <bool> {
                    Message = "Order not found"
                });
            }

            if (_assetDayOffService.IsDayOff(order.Instrument))
            {
                return(new MtBackendResponse <bool> {
                    Message = "Trades for instrument are not available"
                });
            }

            if (order.ClientId != request.ClientId || order.AccountId != request.AccountId)
            {
                return(new MtBackendResponse <bool> {
                    Message = "Order is not available for user"
                });
            }

            order = await _tradingEngine.CloseActiveOrderAsync(request.OrderId, OrderCloseReason.Close);

            var result = new MtBackendResponse <bool>
            {
                Result  = order.Status == OrderStatus.Closed || order.Status == OrderStatus.Closing,
                Message = order.CloseRejectReasonText
            };

            _consoleWriter.WriteLine(
                $"action order.close for clientId = {request.ClientId}, orderId = {request.OrderId}");
            _operationsLogService.AddLog("action order.close", request.ClientId, order.AccountId, request.ToJson(),
                                         result.ToJson());

            return(result);
        }
        public void SetOrderbook(ExternalOrderBook orderbook)
        {
            if (!CheckZeroQuote(orderbook))
            {
                return;
            }

            var isDayOff       = _assetPairDayOffService.IsDayOff(orderbook.AssetPairId);
            var isEodOrderbook = orderbook.ExchangeName == ExternalOrderbookService.EodExternalExchange;

            // we should process normal orderbook only if instrument is currently tradable
            if (isDayOff && !isEodOrderbook)
            {
                return;
            }

            // and process EOD orderbook only if instrument is currently not tradable
            if (!isDayOff && isEodOrderbook)
            {
                //log current schedule for the instrument
                var schedule = _scheduleSettingsCache.GetCompiledScheduleSettings(
                    orderbook.AssetPairId,
                    _dateService.Now(),
                    TimeSpan.Zero);

                _log.WriteWarning("EOD quotes processing", $"Current schedule: {schedule.ToJson()}",
                                  $"EOD quote for {orderbook.AssetPairId} is skipped, because instrument is within trading hours");

                return;
            }

            orderbook.ApplyExchangeIdFromSettings(_defaultExternalExchangeId);

            var bba = orderbook.GetBestPrice();

            _orderbooks.AddOrUpdate(orderbook.AssetPairId, a => orderbook, (s, book) => orderbook);

            _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba));
        }
Example #10
0
        //has to be beyond global lock
        public void Validate(Order order)
        {
            #region Validate input params

            if (_assetDayOffService.IsDayOff(order.Instrument))
            {
                throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Trades for instrument are not available");
            }

            if (order.Volume == 0)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume, "Volume cannot be 0");
            }

            var asset = _assetPairsCache.TryGetAssetPairById(order.Instrument);

            if (asset == null)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument, "Instrument not found");
            }

            var account = _accountsCacheService.TryGet(order.ClientId, order.AccountId);

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

            if (!_quoteCashService.TryGetQuoteById(order.Instrument, out var quote))
            {
                throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Quote not found");
            }

            #endregion

            order.AssetAccuracy      = asset.Accuracy;
            order.AccountAssetId     = account.BaseAssetId;
            order.TradingConditionId = account.TradingConditionId;

            //check ExpectedOpenPrice for pending order
            if (order.ExpectedOpenPrice.HasValue)
            {
                if (_assetDayOffService.ArePendingOrdersDisabled(order.Instrument))
                {
                    throw new ValidateOrderException(OrderRejectReason.NoLiquidity, "Trades for instrument are not available");
                }

                if (order.ExpectedOpenPrice <= 0)
                {
                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, "Incorrect expected open price");
                }

                order.ExpectedOpenPrice = Math.Round(order.ExpectedOpenPrice ?? 0, order.AssetAccuracy);

                if (order.GetOrderType() == OrderDirection.Buy && order.ExpectedOpenPrice > quote.Ask ||
                    order.GetOrderType() == OrderDirection.Sell && order.ExpectedOpenPrice < quote.Bid)
                {
                    var reasonText = order.GetOrderType() == OrderDirection.Buy
                        ? string.Format(MtMessages.Validation_PriceAboveAsk, order.ExpectedOpenPrice, quote.Ask)
                        : string.Format(MtMessages.Validation_PriceBelowBid, order.ExpectedOpenPrice, quote.Bid);

                    throw new ValidateOrderException(OrderRejectReason.InvalidExpectedOpenPrice, reasonText, $"{order.Instrument} quote (bid/ask): {quote.Bid}/{quote.Ask}");
                }
            }

            var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument);

            if (accountAsset.DealLimit > 0 && Math.Abs(order.Volume) > accountAsset.DealLimit)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidVolume,
                                                 $"Margin Trading is in beta testing. The volume of a single order is temporarily limited to {accountAsset.DealLimit} {accountAsset.Instrument}. Thank you for using Lykke Margin Trading, the limit will be cancelled soon!");
            }

            //check TP/SL
            if (order.TakeProfit.HasValue)
            {
                order.TakeProfit = Math.Round(order.TakeProfit.Value, order.AssetAccuracy);
            }

            if (order.StopLoss.HasValue)
            {
                order.StopLoss = Math.Round(order.StopLoss.Value, order.AssetAccuracy);
            }

            ValidateOrderStops(order.GetOrderType(), quote, accountAsset.DeltaBid, accountAsset.DeltaAsk, order.TakeProfit, order.StopLoss, order.ExpectedOpenPrice, order.AssetAccuracy);

            ValidateInstrumentPositionVolume(accountAsset, order);

            if (!_accountUpdateService.IsEnoughBalance(order))
            {
                throw new ValidateOrderException(OrderRejectReason.NotEnoughBalance, MtMessages.Validation_NotEnoughBalance, $"Account available balance is {account.GetTotalCapital()}");
            }
        }
Example #11
0
        private async Task Handle(StartSpecialLiquidationCommand command, IEventPublisher publisher)
        {
            if (!_marginTradingSettings.SpecialLiquidation.Enabled)
            {
                publisher.PublishEvent(new SpecialLiquidationFailedEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    Reason       = "Special liquidation is disabled in settings",
                });

                return;
            }

            //Validate that market is closed for instrument .. only in ExchangeConnector == Real mode
            if (_marginTradingSettings.ExchangeConnector == ExchangeConnectorType.RealExchangeConnector &&
                !_assetPairDayOffService.IsDayOff(command.Instrument))
            {
                publisher.PublishEvent(new SpecialLiquidationFailedEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    Reason       = $"Asset pair {command.Instrument} market must be disabled to start Special Liquidation",
                });

                return;
            }

            if (_assetPairsCache.GetAssetPairById(command.Instrument).IsDiscontinued)
            {
                publisher.PublishEvent(new SpecialLiquidationFailedEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    Reason       = $"Asset pair {command.Instrument} is discontinued",
                });

                return;
            }

            var openedPositions = _orderReader.GetPositions().Where(x => x.AssetPairId == command.Instrument).ToList();

            if (!openedPositions.Any())
            {
                publisher.PublishEvent(new SpecialLiquidationFailedEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    Reason       = "No positions to liquidate",
                });
                return;
            }

            if (!TryGetExchangeNameFromPositions(openedPositions, out var externalProviderId))
            {
                publisher.PublishEvent(new SpecialLiquidationFailedEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    Reason       = "All requested positions must be open on the same external exchange",
                });
                return;
            }

            var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync(
                operationName : SpecialLiquidationSaga.OperationName,
                operationId : command.OperationId,
                factory : () => new OperationExecutionInfo <SpecialLiquidationOperationData>(
                    operationName: SpecialLiquidationSaga.OperationName,
                    id: command.OperationId,
                    lastModified: _dateService.Now(),
                    data: new SpecialLiquidationOperationData
            {
                State              = SpecialLiquidationOperationState.Initiated,
                Instrument         = command.Instrument,
                PositionIds        = openedPositions.Select(x => x.Id).ToList(),
                ExternalProviderId = externalProviderId,
                OriginatorType     = OriginatorType.System,
                AdditionalInfo     = LykkeConstants.LiquidationByCaAdditionalInfo,
            }
                    ));

            if (executionInfo.Data.SwitchState(SpecialLiquidationOperationState.Initiated, SpecialLiquidationOperationState.Started))
            {
                publisher.PublishEvent(new SpecialLiquidationStartedInternalEvent
                {
                    OperationId  = command.OperationId,
                    CreationTime = _dateService.Now(),
                    Instrument   = command.Instrument,
                });

                _chaosKitty.Meow(command.OperationId);

                await _operationExecutionInfoRepository.Save(executionInfo);
            }
        }
Example #12
0
        private async Task PlacePendingOrder(Order order)
        {
            if (order.IsBasicPendingOrder() || !string.IsNullOrEmpty(order.ParentPositionId))
            {
                Position parentPosition = null;

                if (!string.IsNullOrEmpty(order.ParentPositionId))
                {
                    parentPosition = _ordersCache.Positions.GetPositionById(order.ParentPositionId);
                    parentPosition.AddRelatedOrder(order);
                }

                order.Activate(_dateService.Now(), false, parentPosition?.ClosePrice);
                _ordersCache.Active.Add(order);
                _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(order));
            }
            else if (!string.IsNullOrEmpty(order.ParentOrderId))
            {
                if (_ordersCache.TryGetOrderById(order.ParentOrderId, out var parentOrder))
                {
                    parentOrder.AddRelatedOrder(order);
                    order.MakeInactive(_dateService.Now());
                    _ordersCache.Inactive.Add(order);
                    return;
                }

                //may be it was market and now it is position
                if (_ordersCache.Positions.TryGetPositionById(order.ParentOrderId, out var parentPosition))
                {
                    parentPosition.AddRelatedOrder(order);
                    if (parentPosition.Volume != -order.Volume)
                    {
                        order.ChangeVolume(-parentPosition.Volume, _dateService.Now(), OriginatorType.System);
                    }

                    order.Activate(_dateService.Now(), true, parentPosition.ClosePrice);
                    _ordersCache.Active.Add(order);
                    _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(order));
                }
                else
                {
                    order.MakeInactive(_dateService.Now());
                    _ordersCache.Inactive.Add(order);
                    CancelPendingOrder(order.Id, order.AdditionalInfo,
                                       _identityGenerator.GenerateAlphanumericId(),
                                       $"Parent order closed the position, so {order.OrderType.ToString()} order is cancelled");
                }
            }
            else
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidParent, "Order parent is not valid");
            }

            if (order.Status == OrderStatus.Active &&
                _quoteCacheService.TryGetQuoteById(order.AssetPairId, out var pair))
            {
                var price = pair.GetPriceForOrderDirection(order.Direction);

                if (!_assetPairDayOffService.IsDayOff(order.AssetPairId) && //!_assetPairDayOffService.ArePendingOrdersDisabled(order.AssetPairId))
                    order.IsSuitablePriceForPendingOrder(price))
                {
                    _ordersCache.Active.Remove(order);
                    await PlaceOrderByMarketPrice(order);
                }
            }
        }