Example #1
0
        private static decimal?MatchBestPriceForOrder(ExternalOrderBook externalOrderbook, IOrder order, bool isOpening)
        {
            var direction = isOpening ? order.GetOrderType() : order.GetCloseType();
            var volume    = Math.Abs(order.Volume);

            return(externalOrderbook.GetMatchedPrice(volume, direction));
        }
Example #2
0
        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 async Task UpdateAsync(string exchange, string assetPair, DateTime timestamp,
                                      IReadOnlyList <OrderBookLevel> sellLevels, IReadOnlyList <OrderBookLevel> buyLevels)
        {
            ExchangeSettings exchangeSettings = await _exchangeSettingsService.GetByNameAsync(exchange);

            if (exchangeSettings == null)
            {
                throw new FailedOperationException("Exchange setting not found");
            }

            AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPair(assetPair, exchange);

            if (assetPairSettings == null)
            {
                throw new FailedOperationException("Asset pair not found");
            }

            var externalOrderBook = new ExternalOrderBook
            {
                Exchange   = exchange,
                AssetPair  = assetPair,
                Timestamp  = timestamp,
                SellLevels = sellLevels.Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 + exchangeSettings.MarketFee + exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                             .OrderBy(o => o.Price)
                             .ToList(),
                BuyLevels = buyLevels.Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 - exchangeSettings.MarketFee - exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                            .OrderByDescending(o => o.Price)
                            .ToList()
            };

            lock (_sync)
            {
                if (!_orderBooks.ContainsKey(exchange))
                {
                    _orderBooks[exchange] = new Dictionary <string, ExternalOrderBook>();
                }

                _orderBooks[exchange][assetPair] = externalOrderBook;
            }
        }
 internal static ExternalOrderBookContract ToContract(this ExternalOrderBook orderBook)
 {
     return(new ExternalOrderBookContract
     {
         ExchangeName = orderBook.ExchangeName,
         AssetPairId = orderBook.AssetPairId,
         Timestamp = orderBook.Timestamp,
         ReceiveTimestamp = orderBook.ReceiveTimestamp,
         Asks = orderBook.Asks.ToContracts(),
         Bids = orderBook.Bids.ToContracts(),
     });
 }
Example #5
0
        public void Test_ExternalOrderBooksList_Set_InvalidOrderbook(ExternalOrderBook orderBook, string expectedErrorMessage)
        {
            //Arrange
            var orderbooks = GetNewOrderbooksList();

            //Act
            orderbooks.SetOrderbook(orderBook);

            //Assert
            _logMock.Verify(
                log => log.WriteErrorAsync(It.IsAny <string>(), It.IsAny <string>(),
                                           It.Is <Exception>(ex => ex.Message.Contains(expectedErrorMessage)), null),
                Times.Once);
        }
Example #6
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));
        }
Example #7
0
        public static object ToContextData(this ExternalOrderBook orderbook)
        {
            if (orderbook == null)
            {
                return(null);
            }

            return(new
            {
                assetPairId = orderbook.AssetPairId,
                timestamp = orderbook.Timestamp,
                bestAsk = orderbook.Asks[0],
                bestBid = orderbook.Bids[0],
                exchangeName = orderbook.ExchangeName
            });
        }
Example #8
0
        public void SetOrderbook(ExternalOrderBook orderbook)
        {
            if (!ValidateOrderbook(orderbook))
            {
                return;
            }

            var bba = new InstrumentBidAskPair
            {
                Bid        = 0,
                Ask        = decimal.MaxValue,
                Date       = _dateService.Now(),
                Instrument = orderbook.AssetPairId
            };

            Dictionary <string, ExternalOrderBook> UpdateOrderbooksDictionary(string assetPairId,
                                                                              Dictionary <string, ExternalOrderBook> dict)
            {
                dict[orderbook.ExchangeName] = orderbook;
                foreach (var pair in dict.Values.RequiredNotNullOrEmptyCollection(nameof(dict)))
                {
                    // guaranteed to be sorted best first
                    var bestBid = pair.Bids.First().Price;
                    var bestAsk = pair.Asks.First().Price;
                    if (bestBid > bba.Bid)
                    {
                        bba.Bid = bestBid;
                    }

                    if (bestAsk < bba.Ask)
                    {
                        bba.Ask = bestAsk;
                    }
                }

                return(dict);
            }

            _orderbooks.AddOrUpdate(orderbook.AssetPairId,
                                    k => UpdateOrderbooksDictionary(k, new Dictionary <string, ExternalOrderBook>()),
                                    UpdateOrderbooksDictionary);

            _bestPriceChangeEventChannel.SendEvent(this, new BestPriceChangeEventArgs(bba));
        }
        private static bool AreEqual(ExternalOrderBook a, ExternalOrderBook b)
        {
            if (a == null && b == null)
            {
                return(true);
            }

            if (a == null || b == null)
            {
                return(false);
            }

            if (a.AssetPair != b.AssetPair || a.Exchange != b.Exchange)
            {
                return(false);
            }

            return(AreEqual(a.SellLevels, b.SellLevels) && AreEqual(a.BuyLevels, b.BuyLevels));
        }
Example #10
0
        private static decimal?MatchBestPriceForOrderExecution(ExternalOrderBook externalOrderBook, decimal volume,
                                                               bool validateOppositeDirectionVolume)
        {
            var direction = volume.GetOrderDirection();

            var price = externalOrderBook.GetMatchedPrice(volume, direction);

            if (price != null && validateOppositeDirectionVolume)
            {
                var closePrice = externalOrderBook.GetMatchedPrice(volume, direction.GetOpositeDirection());

                //if no liquidity for close, should not use price for open
                if (closePrice == null)
                {
                    return(null);
                }
            }

            return(price);
        }
        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));
        }
        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);
        }
Example #13
0
        private bool ValidateOrderbook(ExternalOrderBook orderbook)
        {
            try
            {
                orderbook.AssetPairId.RequiredNotNullOrWhiteSpace("orderbook.AssetPairId");
                orderbook.ExchangeName.RequiredNotNullOrWhiteSpace("orderbook.ExchangeName");
                orderbook.RequiredNotNull(nameof(orderbook));

                orderbook.Bids.RequiredNotNullOrEmpty("orderbook.Bids");
                orderbook.Bids.RemoveAll(e => e == null || e.Price <= 0 || e.Volume == 0);
                ValidatePricesSorted(orderbook.Bids, false);

                orderbook.Asks.RequiredNotNullOrEmpty("orderbook.Asks");
                orderbook.Asks.RemoveAll(e => e == null || e.Price <= 0 || e.Volume == 0);
                ValidatePricesSorted(orderbook.Asks, true);

                return(true);
            }
            catch (Exception e)
            {
                _log.WriteError(nameof(ExternalOrderBooksList), orderbook.ToJson(), e);
                return(false);
            }
        }
Example #14
0
        private static decimal?MatchBestPriceForPositionClose(ExternalOrderBook externalOrderBook, decimal volume)
        {
            var direction = volume.GetClosePositionOrderDirection();

            return(externalOrderBook.GetMatchedPrice(Math.Abs(volume), direction));
        }
        public Task <ExecutionReport> ExecuteOrder(OrderModel orderModel, CancellationToken cancellationToken)
        {
            if (orderModel == null || orderModel.Volume == 0)
            {
                return(Task.FromResult(new ExecutionReport
                {
                    Success = false,
                    ExecutionStatus = OrderExecutionStatus.Rejected,
                    FailureType = OrderStatusUpdateFailureType.ConnectorError,
                }));
            }

            ExecutionReport result;

            try
            {
                _chaosKitty.Meow(nameof(FakeExchangeConnectorClient));

                ExternalOrderBook orderbook;
                decimal?          currentPrice;

                if (orderModel.Modality == TradeRequestModality.Liquidation_CorporateAction
                    ||
                    orderModel.Modality == TradeRequestModality.Liquidation_MarginCall)
                {
                    if (orderModel.Price == null)
                    {
                        throw new InvalidOperationException("Order should have price specified in case of special liquidation");
                    }

                    currentPrice = (decimal?)orderModel.Price;

                    orderbook = new ExternalOrderBook(MatchingEngineConstants.DefaultSpecialLiquidation,
                                                      orderModel.Instrument, _dateService.Now(), new
                                                      []
                    {
                        new VolumePrice
                        {
                            Price = currentPrice.Value, Volume = (decimal)orderModel.Volume
                        }
                    },
                                                      new
                                                      []
                    {
                        new VolumePrice
                        {
                            Price = currentPrice.Value, Volume = (decimal)orderModel.Volume
                        }
                    });
                }
                else
                {
                    orderbook = _orderbookService.GetOrderBook(orderModel.Instrument);

                    if (orderbook == null)
                    {
                        throw new InvalidOperationException("Orderbook was not found");
                    }

                    currentPrice = orderbook.GetMatchedPrice((decimal)orderModel.Volume,
                                                             orderModel.TradeType == TradeType.Buy ? OrderDirection.Buy : OrderDirection.Sell);
                }

                result = new ExecutionReport(
                    type: orderModel.TradeType,
                    time: DateTime.UtcNow,
                    price: (double)(currentPrice ?? throw new Exception("No price")),
                    volume: orderModel.Volume,
                    fee: 0,
                    success: true,
                    executionStatus: OrderExecutionStatus.Fill,
                    failureType: OrderStatusUpdateFailureType.None,
                    orderType: orderModel.OrderType,
                    execType: ExecType.Trade,
                    clientOrderId: Guid.NewGuid().ToString(),
                    exchangeOrderId: Guid.NewGuid().ToString(),
                    instrument: new Instrument(orderModel.Instrument, orderModel.ExchangeName));

                _cqrsSender.PublishEvent(new OrderExecutionOrderBookContract
                {
                    OrderId         = orderModel.OrderId,
                    Volume          = (decimal)orderModel.Volume,
                    ExternalOrderId = result.ExchangeOrderId,
                    OrderBook       = new ExternalOrderBookContract
                    {
                        AssetPairId      = orderbook.AssetPairId,
                        ExchangeName     = orderbook.ExchangeName,
                        Timestamp        = orderbook.Timestamp,
                        ReceiveTimestamp = _dateService.Now(),
                        Asks             = orderbook.Asks.Select(a => new VolumePriceContract
                        {
                            Price = a.Price, Volume = a.Volume
                        }).ToList(),
                        Bids = orderbook.Bids.Select(a => new VolumePriceContract
                        {
                            Price = a.Price, Volume = a.Volume
                        }).ToList()
                    }
                }, _settings.Cqrs.ContextNames.Gavel);
            }
            catch (Exception ex)
            {
                _log.WriteErrorAsync(nameof(FakeExchangeConnectorClient), nameof(ExecuteOrder),
                                     orderModel.ToJson(), ex);

                result = new ExecutionReport(
                    type: orderModel.TradeType,
                    time: DateTime.UtcNow,
                    price: 0,
                    volume: 0,
                    fee: 0,
                    success: false,
                    executionStatus: OrderExecutionStatus.Rejected,
                    failureType: OrderStatusUpdateFailureType.ExchangeError,
                    orderType: orderModel.OrderType,
                    execType: ExecType.Trade,
                    clientOrderId: null,
                    exchangeOrderId: null,
                    instrument: new Instrument(orderModel.Instrument, orderModel.ExchangeName));
            }

            return(Task.FromResult(result));
        }
        public async Task Create_Order_Book()
        {
            // arrange

            string assetPair = "BTCUSD";

            var exchangeSettings = new ExchangeSettings
            {
                Name           = "exchange1",
                MarketFee      = .1m,
                TransactionFee = .01m
            };

            _exchangeSettings.Add(exchangeSettings);

            var assetPairSettings = new AssetPairModel
            {
                Exchange      = exchangeSettings.Name,
                Name          = assetPair,
                PriceAccuracy = 3
            };

            _assetPairs.Add(assetPairSettings);


            var sellLevels = new List <OrderBookLevel>
            {
                new OrderBookLevel {
                    Price = 1.732235m, Volume = 3
                },
                new OrderBookLevel {
                    Price = 0.833000m, Volume = 1
                }
            };

            var buyLevels = new List <OrderBookLevel>
            {
                new OrderBookLevel {
                    Price = 0.712397m, Volume = 1
                },
                new OrderBookLevel {
                    Price = 0.111000m, Volume = 5
                }
            };

            var expectedExternalOrderBook = new ExternalOrderBook
            {
                Exchange   = exchangeSettings.Name,
                Timestamp  = DateTime.UtcNow,
                AssetPair  = assetPair,
                SellLevels = sellLevels
                             .Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 + exchangeSettings.MarketFee + exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                             .ToList(),
                BuyLevels = buyLevels
                            .Select(o => new ExternalOrderBookLevel
                {
                    Price = (o.Price * (1 - exchangeSettings.MarketFee - exchangeSettings.TransactionFee))
                            .TruncateDecimalPlaces(assetPairSettings.PriceAccuracy),
                    Volume        = o.Volume,
                    Markup        = exchangeSettings.MarketFee + exchangeSettings.TransactionFee,
                    OriginalPrice = o.Price
                })
                            .ToList()
            };

            // act

            await _service.UpdateAsync(exchangeSettings.Name, assetPair, DateTime.UtcNow, sellLevels, buyLevels);

            ExternalOrderBook actualExternalOrderBook = _service.GetByAssetPair(assetPair).Single();

            // assert

            Assert.IsTrue(AreEqual(expectedExternalOrderBook, actualExternalOrderBook));
        }