public async Task <PositionCloseResponse> CloseAsync([CanBeNull][FromRoute] string positionId, [FromBody] PositionCloseRequest request = null, [FromQuery] string accountId = null) { if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position)) { throw new InvalidOperationException("Position not found"); } ValidationHelper.ValidateAccountId(position, accountId); try { ValidateDayOff(position.AssetPairId); } catch (Exception e) { if (e.Message.Contains(CommonErrorCodes.InstrumentTradingDisabled)) { return(new PositionCloseResponse() { PositionId = positionId, Result = PositionCloseResultContract.FailedToClose, ErrorCode = CommonErrorCodes.InstrumentTradingDisabled, }); } throw; } var originator = GetOriginator(request?.Originator); var closeResult = await _tradingEngine.ClosePositionsAsync( new PositionsCloseData( position, position.AccountId, position.AssetPairId, position.OpenMatchingEngineId, position.ExternalProviderId, originator, request?.AdditionalInfo, position.EquivalentAsset), true); _operationsLogService.AddLog("action order.close", position.AccountId, request?.ToJson(), closeResult.ToJson()); return(new PositionCloseResponse { PositionId = positionId, OrderId = closeResult.order?.Id, Result = closeResult.result.ToType <PositionCloseResultContract>() }); }
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); }
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()); }
void IEventConsumer <MarginCallEventArgs> .ConsumeEvent(object sender, MarginCallEventArgs ea) { var account = ea.Account; var eventTime = _dateService.Now(); var(level, lastNotifications) = LevelAndNotificationsCache(ea.MarginCallLevel); if (lastNotifications == null) { return; } var accountMarginEventMessage = AccountMarginEventMessageConverter.Create(account, level, eventTime); _threadSwitcher.SwitchThread(async() => { if (lastNotifications.TryGetValue(account.Id, out var lastNotification) && lastNotification.AddMinutes(_settings.Throttling.MarginCallThrottlingPeriodMin) > eventTime) { _log.WriteInfo(nameof(MarginCallConsumer), nameof(IEventConsumer <MarginCallEventArgs> .ConsumeEvent), $"MarginCall event is ignored for accountId {account.Id} because of throttling: event time {eventTime}, last notification was sent at {lastNotification}"); return; } var marginEventTask = _rabbitMqNotifyService.AccountMarginEvent(accountMarginEventMessage); _operationsLogService.AddLog($"margin call: {level.ToString()}", account.Id, "", ea.ToJson()); var clientEmail = await _clientAccountService.GetEmail(account.ClientId); var emailTask = !string.IsNullOrEmpty(clientEmail) ? _emailService.SendMarginCallEmailAsync(clientEmail, account.BaseAssetId, account.Id) : Task.CompletedTask; await Task.WhenAll(marginEventTask, emailTask); lastNotifications.AddOrUpdate(account.Id, eventTime, (s, times) => eventTime); }); }
void IEventConsumer <StopOutEventArgs> .ConsumeEvent(object sender, StopOutEventArgs ea) { var account = ea.Account; var eventTime = _dateService.Now(); var accountMarginEventMessage = AccountMarginEventMessageConverter.Create(account, MarginEventTypeContract.Stopout, eventTime); _threadSwitcher.SwitchThread(async() => { if (_lastNotifications.TryGetValue(account.Id, out var lastNotification) && lastNotification.AddMinutes(_settings.Throttling.StopOutThrottlingPeriodMin) > eventTime) { return; } _operationsLogService.AddLog("stopout", account.Id, "", ea.ToJson()); await _rabbitMqNotifyService.AccountMarginEvent(accountMarginEventMessage); _lastNotifications.AddOrUpdate(account.Id, eventTime, (s, times) => eventTime); }); }
void IEventConsumer <StopOutEventArgs> .ConsumeEvent(object sender, StopOutEventArgs ea) { var account = ea.Account; var eventTime = _dateService.Now(); var accountMarginEventMessage = AccountMarginEventMessageConverter.Create(account, MarginEventTypeContract.Stopout, eventTime); _threadSwitcher.SwitchThread(async() => { if (_lastNotifications.TryGetValue(account.Id, out var lastNotification) && lastNotification.AddMinutes(_settings.Throttling.StopOutThrottlingPeriodMin) > eventTime) { _log.WriteInfo(nameof(StopOutConsumer), nameof(IEventConsumer <StopOutEventArgs> .ConsumeEvent), $"StopOut event is ignored for accountId {account.Id} because of throttling: event time {eventTime}, last notification was sent at {lastNotification}"); return; } _operationsLogService.AddLog("stopout", account.Id, "", ea.ToJson()); await _rabbitMqNotifyService.AccountMarginEvent(accountMarginEventMessage); _lastNotifications.AddOrUpdate(account.Id, eventTime, (s, times) => eventTime); }); }
public async Task <MatchedOrderCollection> MatchOrderAsync(Order order, bool shouldOpenNewPosition, OrderModality modality = OrderModality.Regular) { List <(string source, decimal?price)> prices = null; if (!string.IsNullOrEmpty(_marginTradingSettings.DefaultExternalExchangeId)) { var quote = _quoteCacheService.GetQuote(order.AssetPairId); if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume)) { prices = new List <(string source, decimal?price)> { (_marginTradingSettings .DefaultExternalExchangeId, quote.GetPriceForOrderDirection(order.Direction)) }; } } if (prices == null) { prices = _externalOrderbookService.GetOrderedPricesForExecution(order.AssetPairId, order.Volume, shouldOpenNewPosition); if (prices == null || !prices.Any()) { return(new MatchedOrderCollection()); } } var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId); var externalAssetPair = assetPair?.BasePairId ?? order.AssetPairId; foreach (var(source, price) in prices .Where(x => string.IsNullOrEmpty(order.ExternalProviderId) || x.source == order.ExternalProviderId)) { var externalOrderModel = new OrderModel(); var orderType = order.OrderType == Core.Orders.OrderType.Limit || order.OrderType == Core.Orders.OrderType.TakeProfit ? Core.Orders.OrderType.Limit : Core.Orders.OrderType.Market; var isCancellationTrade = order.AdditionalInfo.IsCancellationTrade(out var cancellationTradeExternalId); var targetPrice = order.OrderType != Core.Orders.OrderType.Market || isCancellationTrade ? (double?)order.Price : (double?)price; try { externalOrderModel = new OrderModel( tradeType: order.Direction.ToType <TradeType>(), orderType: orderType.ToType <OrderType>(), timeInForce: TimeInForce.FillOrKill, volume: (double)Math.Abs(order.Volume), dateTime: _dateService.Now(), exchangeName: source, instrument: externalAssetPair, price: targetPrice, orderId: order.Id, modality: modality.ToType <TradeRequestModality>(), isCancellationTrade: isCancellationTrade, cancellationTradeExternalId: cancellationTradeExternalId); var cts = new CancellationTokenSource(); cts.CancelAfter(_marginTradingSettings.GavelTimeout); var executionResult = await _exchangeConnectorClient.ExecuteOrder(externalOrderModel, cts.Token); if (!executionResult.Success) { var ex = new Exception( $"External order was not executed. Status: {executionResult.ExecutionStatus}. Failure: {executionResult.FailureType}"); LogOrderExecutionException(order, externalOrderModel, ex); } else { var executedPrice = Math.Abs(executionResult.Price) > 0 ? (decimal)executionResult.Price : price.Value; if (executedPrice.EqualsZero()) { var ex = new Exception($"Have got execution price from Gavel equal to 0. Ignoring."); LogOrderExecutionException(order, externalOrderModel, ex); } else { var matchedOrders = new MatchedOrderCollection { new MatchedOrder { MarketMakerId = source, MatchedDate = _dateService.Now(), OrderId = executionResult.ExchangeOrderId, Price = CalculatePriceWithMarkups(assetPair, order.Direction, executedPrice), Volume = (decimal)executionResult.Volume, IsExternal = true } }; await _rabbitMqNotifyService.ExternalOrder(executionResult); _operationsLogService.AddLog("external order executed", order.AccountId, externalOrderModel.ToJson(), executionResult.ToJson()); return(matchedOrders); } } } catch (Exception ex) { LogOrderExecutionException(order, externalOrderModel, ex); throw new OrderExecutionTechnicalException(); } } return(new MatchedOrderCollection()); }
public async Task <MatchedOrderCollection> MatchOrderAsync(Order order, bool shouldOpenNewPosition, OrderModality modality = OrderModality.Regular) { List <(string source, decimal?price)> prices = null; if (!string.IsNullOrEmpty(_marginTradingSettings.DefaultExternalExchangeId)) { var quote = _quoteCacheService.GetQuote(order.AssetPairId); if (quote.GetVolumeForOrderDirection(order.Direction) >= Math.Abs(order.Volume)) { prices = new List <(string source, decimal?price)> { (_marginTradingSettings .DefaultExternalExchangeId, quote.GetPriceForOrderDirection(order.Direction)) }; } } if (prices == null) { prices = _externalOrderbookService.GetOrderedPricesForExecution(order.AssetPairId, order.Volume, shouldOpenNewPosition); if (prices == null || !prices.Any()) { return(new MatchedOrderCollection()); } } var assetPair = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId); var externalAssetPair = assetPair?.BasePairId ?? order.AssetPairId; foreach (var(source, price) in prices .Where(x => string.IsNullOrEmpty(order.ExternalProviderId) || x.source == order.ExternalProviderId)) { var externalOrderModel = new OrderModel(); var orderType = order.OrderType == Core.Orders.OrderType.Limit || order.OrderType == Core.Orders.OrderType.TakeProfit ? OrderType.Limit : OrderType.Market; var targetPrice = order.OrderType == Core.Orders.OrderType.Market ? (double?)price : (double?)order.Price; try { externalOrderModel = new OrderModel( tradeType: order.Direction.ToType <TradeType>(), orderType: orderType, timeInForce: TimeInForce.FillOrKill, volume: (double)Math.Abs(order.Volume), dateTime: _dateService.Now(), exchangeName: source, instrument: externalAssetPair, price: targetPrice, orderId: order.Id, modality: modality.ToType <TradeRequestModality>()); var cts = new CancellationTokenSource(); cts.CancelAfter(_marginTradingSettings.GavelTimeout); var executionResult = await _exchangeConnectorService.CreateOrderAsync(externalOrderModel, cts.Token); if (!executionResult.Success) { throw new Exception( $"External order was not executed. Status: {executionResult.ExecutionStatus}. Failure: {executionResult.FailureType}"); } var executedPrice = Math.Abs(executionResult.Price) > 0 ? (decimal)executionResult.Price : price.Value; var matchedOrders = new MatchedOrderCollection { new MatchedOrder { MarketMakerId = source, MatchedDate = _dateService.Now(), OrderId = executionResult.ExchangeOrderId, Price = CalculatePriceWithMarkups(assetPair, order.Direction, executedPrice), Volume = (decimal)executionResult.Volume, IsExternal = true } }; await _rabbitMqNotifyService.ExternalOrder(executionResult); _operationsLogService.AddLog("external order executed", order.AccountId, externalOrderModel.ToJson(), executionResult.ToJson()); return(matchedOrders); } catch (Exception e) { var connector = _marginTradingSettings.ExchangeConnector == ExchangeConnectorType.FakeExchangeConnector ? "Fake" : _exchangeConnectorService.BaseUri.OriginalString; _log.WriteError( $"{nameof(StpMatchingEngine)}:{nameof(MatchOrderAsync)}:{connector}", $"Internal order: {order.ToJson()}, External order model: {externalOrderModel.ToJson()}", e); } } return(new MatchedOrderCollection()); }