示例#1
0
        public bool IsOutdated(ExternalOrderbook orderbook, DateTime now)
        {
            var age       = now - orderbook.LastUpdatedTime;
            var threshold = _extPricesSettingsService.GetOrderbookOutdatingThreshold(orderbook.AssetPairId, orderbook.ExchangeName, now);

            return(age > threshold);
        }
        public Task ProcessNewExternalOrderbookAsync(ExternalExchangeOrderbookMessage orderbook)
        {
            var quotesSource = _assetPairSourceTypeService.Get(orderbook.AssetPairId);

            if (quotesSource != AssetPairQuotesSourceTypeDomainEnum.External ||
                (orderbook.Bids?.Count ?? 0) == 0 || (orderbook.Asks?.Count ?? 0) == 0)
            {
                return(Task.CompletedTask);
            }

            var externalOrderbook = new ExternalOrderbook(orderbook.AssetPairId, orderbook.Source, _system.UtcNow,
                                                          orderbook.Bids.OrderByDescending(p => p.Price).Select(b => new OrderbookPosition(b.Price, b.Volume))
                                                          .ToImmutableArray(),
                                                          orderbook.Asks.OrderBy(p => p.Price).Select(b => new OrderbookPosition(b.Price, b.Volume))
                                                          .ToImmutableArray());
            var resultingOrderbook = _generateOrderbookService.OnNewOrderbook(externalOrderbook);

            if (resultingOrderbook == null)
            {
                return(Task.CompletedTask);
            }

            var orderbooksToSend = _crossRatesService.CalcDependentOrderbooks(resultingOrderbook)
                                   .Add(resultingOrderbook);

            return(SendOrderCommandsAsync(orderbooksToSend));
        }
示例#3
0
        public ImmutableDictionary <string, ExternalOrderbook> AddAndGetByAssetPair(ExternalOrderbook orderbook)
        {
            var existingExchanges = _extPricesSettingsService.Get(orderbook.AssetPairId).Exchanges.Keys;

            return(_orderbooks.AddOrUpdate(orderbook.AssetPairId,
                                           k => ImmutableDictionary.Create <string, ExternalOrderbook>().Add(orderbook.ExchangeName, orderbook),
                                           (k, dict) => AddOrderbookAndFilterByExchangeExistance(orderbook, dict, existingExchanges)));
        }
        private (Orderbook Orderbook, ExchangeQuality PrimaryExchange, string Problem) OnNewOrderbookInternal(
            ExternalOrderbook orderbook)
        {
            var assetPairId = orderbook.AssetPairId;

            if (!_extPricesSettingsService.IsExchangeConfigured(assetPairId, orderbook.ExchangeName))
            {
                Trace.Write(TraceLevelGroupEnum.WarnTrace, assetPairId,
                            $"Skipping not configured exchange {orderbook.ExchangeName}",
                            new { Event = "NotConfiguredExchangeSkipped", orderbook.ExchangeName });
                return(null, null, "Skipping not configured exchange");
            }

            var allOrderbooks = _orderbooksService.AddAndGetByAssetPair(orderbook);
            var now           = orderbook.LastUpdatedTime;

            var(exchangesErrors, validOrderbooks) = MarkExchangesErrors(assetPairId, allOrderbooks, now);
            var primaryExchangeQuality =
                _primaryExchangeService.GetPrimaryExchange(assetPairId, exchangesErrors, now, orderbook.ExchangeName);

            if (primaryExchangeQuality == null)
            {
                return(null, null, "No primary exchange");
            }

            var primaryExchangeName = primaryExchangeQuality.ExchangeName;

            if (primaryExchangeName != orderbook.ExchangeName)
            {
                return(null, primaryExchangeQuality, "Orderbook not from primary exchange");
            }

            if (primaryExchangeQuality.ErrorState == ExchangeErrorStateDomainEnum.Outlier)
            {
                return(null, primaryExchangeQuality, "Primary exchange is an outlier, skipping price update");
            }

            if (!allOrderbooks.TryGetValue(primaryExchangeName, out var primaryOrderbook))
            {
                _log.WriteErrorAsync(nameof(GenerateOrderbookService), null,
                                     new Exception($"{primaryExchangeName} not found in allOrderbooks ({allOrderbooks.Keys.ToJson()})")
                {
                    Data = { { "AssetPairId", assetPairId } }
                });
                return(null, primaryExchangeQuality, "Primary exchange orderbook not found");
            }

            _stopTradesService.FinishCycle(primaryOrderbook, now);
            var resultingOrderbook = Transform(primaryOrderbook, validOrderbooks);

            if (TryFindSkipOrderbookReason(resultingOrderbook) is string reason)
            {
                return(null, primaryExchangeQuality, reason);
            }

            return(resultingOrderbook, primaryExchangeQuality, null);
        }
示例#5
0
        private static ImmutableDictionary <string, ExternalOrderbook> AddOrderbookAndFilterByExchangeExistance(
            ExternalOrderbook orderbook, ImmutableDictionary <string, ExternalOrderbook> dict,
            IEnumerable <string> existingExchanges)
        {
            dict = dict.SetItem(orderbook.ExchangeName, orderbook);
            foreach (var exchangeToDelete in dict.Keys.Except(existingExchanges))
            {
                dict = dict.Remove(exchangeToDelete);
            }

            return(dict);
        }
示例#6
0
        private Orderbook Transform(
            ExternalOrderbook primaryOrderbook,
            ImmutableDictionary <string, ExternalOrderbook> validOrderbooks)
        {
            if (!_extPricesSettingsService.IsStepEnabled(OrderbookGeneratorStepDomainEnum.Transform,
                                                         primaryOrderbook.AssetPairId))
            {
                return(primaryOrderbook);
            }

            var bestPrices =
                validOrderbooks.Values.ToDictionary(o => o.ExchangeName,
                                                    orderbook => _bestPricesService.CalcExternal(orderbook));

            return(_transformOrderbookService.Transform(primaryOrderbook, bestPrices));
        }
示例#7
0
        private void LogCycle(ExternalOrderbook orderbook, [CanBeNull] Orderbook resultingOrderbook, Stopwatch watch,
                              ExchangeQuality primaryExchangeQuality, [CanBeNull] string problem)
        {
            var elapsedMilliseconds = watch.ElapsedMilliseconds;

            if (elapsedMilliseconds > 20)
            {
                _telemetryService.PublishEventMetrics(nameof(GenerateOrderbookService) + '.' + nameof(OnNewOrderbook),
                                                      null,
                                                      new Dictionary <string, double> {
                    { "ProcessingTime", elapsedMilliseconds }
                },
                                                      new Dictionary <string, string>
                {
                    { "AssetPairId", orderbook.AssetPairId },
                    { "Exchange", orderbook.ExchangeName },
                    { "IsPrimary", (orderbook.ExchangeName == primaryExchangeQuality.ExchangeName).ToString() },
                    { "IsSkip", (resultingOrderbook == null).ToString() },
                });
            }

            var bestPrices          = _bestPricesService.Calc(orderbook);
            var resultingBestPrices = resultingOrderbook == null ? null : _bestPricesService.Calc(resultingOrderbook);
            var action = resultingOrderbook == null ? "Skipped" : "Processed";

            Trace.Write(TraceLevelGroupEnum.Trace, orderbook.AssetPairId,
                        $"{action} from {orderbook.ExchangeName}, " +
                        $"primary: {primaryExchangeQuality}, time: {elapsedMilliseconds} ms",
                        new
            {
                Event  = "ExternalOrderbook" + action,
                Reason = problem,
                orderbook.ExchangeName,
                PrimaryExchange     = primaryExchangeQuality,
                ElapsedMilliseconds = elapsedMilliseconds,
                IsSkip = resultingOrderbook == null,
                bestPrices.BestBid,
                bestPrices.BestAsk,
                ResultingBestBid    = resultingBestPrices?.BestBid,
                ResultingBestAsk    = resultingBestPrices?.BestAsk,
                BidsDepth           = orderbook.Bids.Length,
                AsksDepth           = orderbook.Asks.Length,
                ResultingsBidsDepth = resultingOrderbook?.Bids.Length,
                ResultingsAsksDepth = resultingOrderbook?.Asks.Length,
            });
        }
示例#8
0
        public Orderbook OnNewOrderbook(ExternalOrderbook orderbook)
        {
            if (orderbook == null)
            {
                throw new ArgumentNullException(nameof(orderbook));
            }
            var watch = Stopwatch.StartNew();

            orderbook = _testingHelperService.ModifyOrderbookIfNeeded(orderbook);
            if (orderbook == null)
            {
                return(null);
            }

            var(result, primaryExchange, problem) = OnNewOrderbookInternal(orderbook);
            LogCycle(orderbook, result, watch, primaryExchange, problem);
            return(result);
        }
示例#9
0
        public void FinishCycle(ExternalOrderbook primaryOrderbook, DateTime now)
        {
            if (!_lastStates.TryGetValue(primaryOrderbook.AssetPairId, out var dict) ||
                !dict.TryGetValue(now, out var state))
            {
                return;
            }

            var isPrimaryOk = state.PrimaryState == null ||
                              state.PrimaryState.HedgingPreference > 0 &&
                              (state.PrimaryState.ErrorState == ExchangeErrorStateDomainEnum.Valid ||
                               state.PrimaryState.ErrorState == ExchangeErrorStateDomainEnum.Outlier);

            var isFreshOk = state.FreshOrderbooksState == null ||
                            state.FreshOrderbooksState.FreshOrderbooksKeys.Length > 2;

            var stop       = !isPrimaryOk || !isFreshOk;
            var wasStopped = false;

            _stoppedTradesAssetPairs.AddOrUpdate(primaryOrderbook.AssetPairId, k => stop, (k, old) =>
            {
                wasStopped = old;
                return(stop);
            });

            if (stop != wasStopped)
            {
                string reason = null;
                if (state.PrimaryState != null)
                {
                    reason +=
                        $"Primary exchange \"{state.PrimaryState.Name}\" has hedging preference \"{state.PrimaryState.HedgingPreference}\" and error state \"{state.PrimaryState.ErrorState}\". ";
                }

                if (state.FreshOrderbooksState != null)
                {
                    reason +=
                        $"Found {state.FreshOrderbooksState.FreshOrderbooksKeys.Length} fresh orderbooks: {state.FreshOrderbooksState.FreshOrderbooksKeys.ToJson()}. ";
                }

                reason = reason ?? "Everything is ok";
                _alertService.StopOrAllowNewTrades(primaryOrderbook.AssetPairId, reason, stop);
            }
        }
        public async Task IfResultingOrderbookIsNull_ShouldSkip()
        {
            //arrange
            var incomingMessage = new ExternalExchangeOrderbookMessage
            {
                AssetPairId = "pair",
                Bids        = new List <VolumePrice>
                {
                    new VolumePrice {
                        Price = 1, Volume = 2
                    },
                    new VolumePrice {
                        Price = 3, Volume = 4
                    }
                },
                Asks = new List <VolumePrice>
                {
                    new VolumePrice {
                        Price = 5, Volume = 6
                    },
                    new VolumePrice {
                        Price = 7, Volume = 8
                    }
                },
                Source = "source",
            };

            var externalOrderbook = new ExternalOrderbook("pair", "source", _now,
                                                          ImmutableArray.Create(new OrderbookPosition(1, 2), new OrderbookPosition(3, 4)),
                                                          ImmutableArray.Create(new OrderbookPosition(5, 6), new OrderbookPosition(7, 8)));

            _testSuit
            .Setup <IAssetPairSourceTypeService>(s =>
                                                 s.Get("pair") == AssetPairQuotesSourceTypeDomainEnum.External)
            .Setup <ISystem>(s => s.UtcNow == _now)
            .Setup <IGenerateOrderbookService>(s => s.OnNewOrderbook(externalOrderbook.Equivalent()) == null);


            //act
            await _testSuit.Sut.ProcessNewExternalOrderbookAsync(incomingMessage);

            //assert
            _sentMessages.Should().BeEmpty();
        }
        public Orderbook Transform(ExternalOrderbook primaryOrderbook,
                                   IReadOnlyDictionary <string, BestPrices> bestPrices)
        {
            var isArbitrageFreeSpreadEnabled = _extPricesSettingsService.IsStepEnabled(
                OrderbookGeneratorStepDomainEnum.GetArbitrageFreeSpread,
                primaryOrderbook.AssetPairId);

            var arbitrageFreeSpread = isArbitrageFreeSpreadEnabled
                ? GetArbitrageFreeSpread(bestPrices)
                : GetArbitrageFreeSpread(
                ImmutableDictionary.CreateRange(bestPrices.Where(p => p.Key == primaryOrderbook.ExchangeName)));
            var primaryBestPrices = bestPrices[primaryOrderbook.ExchangeName];
            var bidShift          = arbitrageFreeSpread.WorstBid - primaryBestPrices.BestBid; // negative
            var askShift          = arbitrageFreeSpread.WorstAsk - primaryBestPrices.BestAsk; // positive
            var volumeMultiplier  =
                _extPricesSettingsService.GetVolumeMultiplier(primaryOrderbook.AssetPairId,
                                                              primaryOrderbook.ExchangeName);
            var priceMarkups = _extPricesSettingsService.GetPriceMarkups(primaryOrderbook.AssetPairId);

            return(Transform(primaryOrderbook, bidShift + priceMarkups.Bid, askShift + priceMarkups.Ask, volumeMultiplier));
        }
        public async Task IfResultingOrderbookNotNull_ShouldSendItWithDependentOnes()
        {
            //arrange
            var incomingMessage = new ExternalExchangeOrderbookMessage
            {
                AssetPairId = "pair",
                Bids        = new List <VolumePrice>
                {
                    new VolumePrice {
                        Price = 1, Volume = 2
                    },
                    new VolumePrice {
                        Price = 3, Volume = 4
                    }
                },
                Asks = new List <VolumePrice>
                {
                    new VolumePrice {
                        Price = 5, Volume = 6
                    },
                    new VolumePrice {
                        Price = 7, Volume = 8
                    }
                },
                Source = "source",
            };

            var externalOrderbook = new ExternalOrderbook("pair", "source", _now,
                                                          ImmutableArray.Create(new OrderbookPosition(1, 2), new OrderbookPosition(3, 4)),
                                                          ImmutableArray.Create(new OrderbookPosition(5, 6), new OrderbookPosition(7, 8)));

            var dependentOrderbooks = ImmutableList.Create(
                new Orderbook("dependent pair 1",
                              ImmutableArray.Create(new OrderbookPosition(11, 12), new OrderbookPosition(13, 14)),
                              ImmutableArray.Create(new OrderbookPosition(15, 16), new OrderbookPosition(17, 18))),
                new Orderbook("dependent pair 2",
                              ImmutableArray.Create(new OrderbookPosition(21, 22), new OrderbookPosition(23, 24)),
                              ImmutableArray.Create(new OrderbookPosition(25, 26), new OrderbookPosition(27, 28))));
            var resultingOrderbook = new Orderbook("resulting pair",
                                                   ImmutableArray.Create(new OrderbookPosition(31, 32), new OrderbookPosition(33, 34)),
                                                   ImmutableArray.Create(new OrderbookPosition(35, 36), new OrderbookPosition(37, 38)));

            _testSuit
            .Setup <IAssetPairSourceTypeService>(s =>
                                                 s.Get("pair") == AssetPairQuotesSourceTypeDomainEnum.External)
            .Setup <ISystem>(s => s.UtcNow == _now)
            .Setup <IGenerateOrderbookService>(s =>
                                               s.OnNewOrderbook(externalOrderbook.Equivalent()) == resultingOrderbook)
            .Setup <ICrossRatesService>(s => s.CalcDependentOrderbooks(resultingOrderbook) == dependentOrderbooks)
            .Setup <IReloadingManager <MarginTradingMarketMakerSettings> >(s =>
                                                                           s.CurrentValue == new MarginTradingMarketMakerSettings {
                MarketMakerId = "mm id"
            })
            .Setup <IPriceRoundingService>(m => m.Setup(s => s.GetRoundFunc(It.IsNotNull <string>())).Returns(p => p));


            //act
            await _testSuit.Sut.ProcessNewExternalOrderbookAsync(incomingMessage);

            //assert
            var expectation = new List <OrderCommandsBatchMessage>
            {
                MakeOrderCommandsBatchMessage("dependent pair 1", 10),
                MakeOrderCommandsBatchMessage("dependent pair 2", 20),
                MakeOrderCommandsBatchMessage("resulting pair", 30),
            };

            _sentMessages.Should().BeEquivalentTo(expectation, o => o.WithAutoConversion());
        }