public Task <bool> IsInstrumentEnabled(string assetPairId)
        {
            _assetPairsCache.GetAssetPairById(assetPairId);

            var isEnabled = !_assetPairDayOffService.IsAssetTradingDisabled(assetPairId);

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

            if (data.LiquidationType == LiquidationType.Forced)
            {
                positionsOnAccount = positionsOnAccount.Where(p => p.OpenDate < data.StartedAt).ToList();

                //group positions by asset and pnl sign, take only not processed, filtered and with open market
                //groups are ordered by positive pnl first
                var targetPositionsByPnlSign = positionsOnAccount
                                               .Where(p => !data.ProcessedPositionIds.Contains(p.Id))
                                               .GroupBy(p => (p.AssetPairId, p.GetUnrealisedFpl() >= 0))
                                               .Where(gr => !_assetPairDayOffService.IsAssetTradingDisabled(gr.Key.AssetPairId))
                                               .OrderByDescending(gr => gr.Key.Item2)
                                               .FirstOrDefault();

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

                return(targetPositionsByPnlSign.Key.AssetPairId, targetPositionsByPnlSign.Select(p => p.Id).ToArray());
            }

            //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.IsAssetTradingDisabled(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());
        }
Beispiel #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.IsAssetTradingDisabled(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);
            }
        }
 private void ValidateDayOff(params string[] assetPairIds)
 {
     foreach (var instrument in assetPairIds)
     {
         var instrumentTradingStatus = _assetDayOffService.IsAssetTradingDisabled(instrument);
         if (!instrumentTradingStatus.TradingEnabled)
         {
             if (instrumentTradingStatus.Reason == InstrumentTradingDisabledReason.InstrumentTradingDisabled)
             {
                 throw new InvalidOperationException($"Trades for {instrument} are disabled. Error code: {CommonErrorCodes.InstrumentTradingDisabled}");
             }
             throw new InvalidOperationException($"Trades for {instrument} are not available");
         }
     }
 }
Beispiel #5
0
        public void SetOrderbook(ExternalOrderBook orderbook)
        {
            var isEodOrderbook = orderbook.ExchangeName == ExternalOrderbookService.EodExternalExchange;

            var instrumentTradingStatus = _assetPairDayOffService.IsAssetTradingDisabled(orderbook.AssetPairId);

            if (!isEodOrderbook &&
                !instrumentTradingStatus.TradingEnabled &&
                instrumentTradingStatus.Reason == InstrumentTradingDisabledReason.InstrumentTradingDisabled)
            {
                return;
            }

            if (_orderbookValidation.ValidateInstrumentStatusForEodQuotes && isEodOrderbook ||
                _orderbookValidation.ValidateInstrumentStatusForTradingQuotes && !isEodOrderbook)
            {
                // we should process normal orderbook only if instrument is currently tradable
                if (_orderbookValidation.ValidateInstrumentStatusForTradingQuotes && instrumentTradingStatus && !isEodOrderbook)
                {
                    return;
                }

                // and process EOD orderbook only if instrument is currently not tradable
                if (_orderbookValidation.ValidateInstrumentStatusForEodQuotes && !instrumentTradingStatus && isEodOrderbook)
                {
                    //log current schedule for the instrument
                    var schedule = _scheduleSettingsCache.GetMarketTradingScheduleByAssetPair(orderbook.AssetPairId);

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

                    return;
                }
            }

            if (!CheckZeroQuote(orderbook, isEodOrderbook))
            {
                return;
            }

            orderbook.ApplyExchangeIdFromSettings(_defaultExternalExchangeId);

            var bba = orderbook.GetBestPrice();

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

            _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba, isEodOrderbook));
        }
Beispiel #6
0
        public Task SetQuote(ExternalExchangeOrderbookMessage orderBookMessage)
        {
            var isEodOrderbook = orderBookMessage.ExchangeName == ExternalOrderbookService.EodExternalExchange;

            if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForEodFx && isEodOrderbook ||
                _marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForTradingFx && !isEodOrderbook)
            {
                var isAssetTradingDisabled = _assetPairDayOffService.IsAssetTradingDisabled(orderBookMessage.AssetPairId);

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

                // and process EOD orderbook only if asset is currently not tradable
                if (_marginTradingSettings.OrderbookValidation.ValidateInstrumentStatusForEodFx && !isAssetTradingDisabled && 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);
        }
Beispiel #7
0
        public Dictionary <string, (PositionCloseResult, Order)> StartLiquidation(string accountId,
                                                                                  OriginatorType originator, string additionalInfo, string operationId)
        {
            var result = new Dictionary <string, (PositionCloseResult, Order)>();

            var command = new StartLiquidationInternalCommand
            {
                OperationId     = operationId,
                CreationTime    = _dateService.Now(),
                AccountId       = accountId,
                LiquidationType = LiquidationType.Forced,
                OriginatorType  = originator,
                AdditionalInfo  = additionalInfo
            };

            _cqrsSender.SendCommandToSelf(command);

            var positions = _ordersCache.Positions.GetPositionsByAccountIds(accountId);

            var openPositions = new List <Position>();

            foreach (var position in positions)
            {
                switch (position.Status)
                {
                case PositionStatus.Active:
                    openPositions.Add(position);
                    break;

                case PositionStatus.Closing:
                    result.Add(position.Id, (PositionCloseResult.ClosingIsInProgress, null));
                    break;

                case PositionStatus.Closed:
                    result.Add(position.Id, (PositionCloseResult.Closed, null));
                    break;

                default:
                    throw new InvalidOperationException($"Position state {position.Status.ToString()} is not handled");
                }
            }

            foreach (var group in openPositions.GroupBy(p => p.AssetPairId))
            {
                // if asset pair is not available for trading, we will not try to close these positions
                if (_assetPairDayOffService.IsAssetTradingDisabled(group.Key))
                {
                    continue;
                }

                var positionGroup = group.ToArray();

                // if the net volume can be liquidated, we assume that positions will be closed without special liquidation
                if (CheckIfNetVolumeCanBeLiquidated(group.Key, positionGroup, out _))
                {
                    positionGroup.ForEach(p => result.Add(p.Id, (PositionCloseResult.Closed, null)));
                }
                else
                {
                    positionGroup.ForEach(p => result.Add(p.Id, (PositionCloseResult.ClosingStarted, null)));
                }
            }

            return(result);
        }
Beispiel #8
0
        public IAssetPair GetAssetPairIfAvailableForTrading(string assetPairId, OrderType orderType,
                                                            bool shouldOpenNewPosition, bool isPreTradeValidation, bool validateForEdit = false)
        {
            if (isPreTradeValidation || orderType == OrderType.Market)
            {
                var tradingStatus = _assetDayOffService.IsAssetTradingDisabled(assetPairId);
                if (tradingStatus)
                {
                    if (tradingStatus.Reason == InstrumentTradingDisabledReason.InstrumentTradingDisabled)
                    {
                        throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                         $"Trading for the instrument {assetPairId} is disabled. Error code: {CommonErrorCodes.InstrumentTradingDisabled}");
                    }
                    throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                     $"Trades for instrument {assetPairId} are not available due to trading is closed");
                }
            }
            else if (_assetDayOffService.ArePendingOrdersDisabled(assetPairId))
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 $"Pending orders for instrument {assetPairId} are not available");
            }

            var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(assetPairId);

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

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

            if (assetPair.IsTradingDisabled && !validateForEdit)
            {
                throw new ValidateOrderException(OrderRejectReason.InvalidInstrument,
                                                 $"Trading for the instrument {assetPairId} is disabled. Error code: {CommonErrorCodes.InstrumentTradingDisabled}");
            }

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

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

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

            return(assetPair);
        }
        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.IsAssetTradingDisabled(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;
            }

            var openedPositions = _ordersCache.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 (_assetPairsCache.GetAssetPairById(command.Instrument).IsDiscontinued)
            {
                await _log.WriteWarningAsync(
                    nameof(StartSpecialLiquidationCommand),
                    nameof(SpecialLiquidationCommandsHandler),
                    $"Position(s) {string.Join(", ", openedPositions.Select(x => x.Id))} should be closed for discontinued instrument {command.Instrument}.");
            }

            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,
                RequestedFromCorporateActions = command.IsTriggeredByCa,
            }
                    ));

            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);
            }
        }