Example #1
0
        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>()
            });
        }
Example #2
0
        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);
        }
Example #3
0
        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());
        }
Example #4
0
        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);
            });
        }
Example #5
0
        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);
            });
        }
Example #6
0
        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);
            });
        }
Example #7
0
        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());
        }
Example #8
0
        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());
        }