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