private static PriceByOrderSide FilterByPercentileTopPrices(OrderBookSideFlat asks, OrderBookSideFlat bids)
        {
            const decimal percentileRate = 0.1m;
            var           topAsk         = FilterByAmountPercentileAndGetTop(asks, percentileRate);
            var           topBid         = FilterByAmountPercentileAndGetTop(bids, percentileRate);

            return(new PriceByOrderSide(topBid, topAsk));
        }
        private static void RemoveOrders(
            IEnumerable <Order> notFinishedOrders,
            OrderBookSideFlat asks,
            OrderBookSideFlat bids)
        {
            foreach (var order in notFinishedOrders)
            {
                if (!order.Price.HasValue)
                {
                    continue;
                }

                var bookSide = order.Side == OrderSide.Buy ? bids : asks;
                var index    = Array.BinarySearch(bookSide.Prices, order.Price.Value, bookSide.Comparer);
                if (index >= 0)
                {
                    bookSide.Amounts[index] -= order.Amount - order.FilledAmount;
                }
            }
        }
        private static (OrderBookSideFlat asks, OrderBookSideFlat bids) FlattenSnapshot(ILocalOrderBookSnapshot snapshot)
        {
            SortedDictionary <decimal, decimal> asks, bids;

            switch (snapshot)
            {
            case L1LocalOrderBookSnapshot l1:
                asks = l1.GetAsks();
                bids = l1.GetBids();
                break;

            case L2LocalOrderBookSnapshot l2:
                asks = l2.Asks;
                bids = l2.Bids;
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(snapshot), snapshot, null);
            }

            return(new OrderBookSideFlat(asks), new OrderBookSideFlat(bids));
        }
        private static decimal?FilterByAmountPercentileAndGetTop(OrderBookSideFlat bookSide, decimal percentileRate)
        {
            if (bookSide.Size == 0)
            {
                return(null);
            }

            var percentileAmount = bookSide.Size >= 5
                ? GetPercentileAmount(bookSide.Amounts, percentileRate)
                : 0m; // it is not necessary to filter side if there are not enough levels

            // prices are sorted, so we can go from top (because we want to remove top positions only)
            for (int i = 0; i < bookSide.Size; i++)
            {
                if (bookSide.Amounts[i] > percentileAmount)
                {
                    return(bookSide.Prices[i]);
                }
            }

            return(null);
        }