public Task <bool> IsInstrumentEnabled(string assetPairId) { _assetPairsCache.GetAssetPairById(assetPairId); var isEnabled = !_assetPairDayOffService.IsDayOff(assetPairId); return(Task.FromResult(isEnabled)); }
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()); }
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); } }
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); }
private void ValidateDayOff(params string[] assetPairIds) { foreach (var instrument in assetPairIds) { if (_assetDayOffService.IsDayOff(instrument)) { throw new InvalidOperationException($"Trades for {instrument} are not available"); } } }
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); }
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); }
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)); }
//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()}"); } }
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); } }
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); } } }