public void ArbitrageVolumePnL_Simple1_Test()
        {
            const string exchangeName = "FE";
            var          assetPair    = new AssetPair("BTC", "USD", 8, 8);
            var          timestamp    = DateTime.UtcNow;

            var asks = new List <VolumePrice>
            {
                new VolumePrice(9000, 10),
                new VolumePrice(8999.95m, 7),      // <-
                new VolumePrice(8900.12345677m, 3) // <-
            };

            var bids = new List <VolumePrice>
            {
                new VolumePrice(9000, 9), // <-
                new VolumePrice(8900, 5)
            };

            var bidsOrderBook = new OrderBook(exchangeName, assetPair, bids, new List <VolumePrice>(), timestamp);
            var asksOrderBook = new OrderBook(exchangeName, assetPair, new List <VolumePrice>(), asks, timestamp);

            var volumePnL = Arbitrage.GetArbitrageVolumePnL(bidsOrderBook.Bids, asksOrderBook.Asks);

            Assert.Equal(9, volumePnL?.Volume);
            Assert.Equal(299.92962969m, volumePnL?.PnL);
        }
        public void ArbitrageVolume_NoArbitrage_TheSameOrderBook_Test()
        {
            const string exchangeName = "FE";
            var          assetPair    = new AssetPair("BTC", "USD", 8, 8);
            var          timestamp    = DateTime.UtcNow;

            var asks = new List <VolumePrice>
            {
                new VolumePrice(9000, 10),
                new VolumePrice(8999.95m, 7),
                new VolumePrice(8900.12345677m, 3)
            };
            var bids = new List <VolumePrice>
            {
                new VolumePrice(8825, 9),
                new VolumePrice(8823, 5)
            };

            var orderBook1 = new OrderBook(exchangeName, assetPair, bids, asks, timestamp);
            var orderBook2 = new OrderBook(exchangeName, assetPair, bids, asks, timestamp);

            var volume = Arbitrage.GetArbitrageVolumePnL(orderBook1.Bids, orderBook2.Asks);

            Assert.Null(volume);
        }
        public void ArbitrageVolume_NoArbitrage_EmptyOrderBooks_Test()
        {
            const string exchangeName = "FE";
            var          assetPair    = new AssetPair("BTC", "USD", 8, 8);
            var          timestamp    = DateTime.UtcNow;

            var orderBook1 = new OrderBook(exchangeName, assetPair, new List <VolumePrice>(), new List <VolumePrice>(), timestamp);
            var orderBook2 = new OrderBook(exchangeName, assetPair, new List <VolumePrice>(), new List <VolumePrice>(), timestamp);

            var volume = Arbitrage.GetArbitrageVolumePnL(orderBook1.Bids, orderBook2.Asks);

            Assert.Null(volume);
        }
        public void ArbitrageVolumePnL_Complex2_Test()
        {
            // https://docs.google.com/spreadsheets/d/1plnbQSS-WP6ykTv8wIi_hbAhk_aSz_tllXFIE3jhFpU/edit#gid=2011486790
            const string exchangeName = "FE";
            var          assetPair    = new AssetPair("BTC", "USD", 8, 8);
            var          timestamp    = DateTime.UtcNow;

            var asks = new List <VolumePrice>
            {
                new VolumePrice(2.9m, 10),
                new VolumePrice(2.8m, 10),
                new VolumePrice(2.7m, 10),
                new VolumePrice(2.6m, 10),
                new VolumePrice(2.4m, 10),
                new VolumePrice(2.3m, 10),
                new VolumePrice(2.2m, 10),
                new VolumePrice(2.0m, 10),
                new VolumePrice(1.9m, 10),
                new VolumePrice(1.8m, 10),
                new VolumePrice(1.7m, 10),
                new VolumePrice(1.3m, 10),
                new VolumePrice(1.2m, 10),
                new VolumePrice(1.1m, 10),
            };

            var bids = new List <VolumePrice>
            {
                new VolumePrice(3.2m, 1),
                new VolumePrice(3.1m, 1),
                new VolumePrice(3m, 1),
                new VolumePrice(2.5m, 1),
                new VolumePrice(2.1m, 100),
                new VolumePrice(1.6m, 5),
                new VolumePrice(1.5m, 5),
                new VolumePrice(1.4m, 5)
            };

            var bidsOrderBook = new OrderBook(exchangeName, assetPair, bids, new List <VolumePrice>(), timestamp);
            var asksOrderBook = new OrderBook(exchangeName, assetPair, new List <VolumePrice>(), asks, timestamp);

            var volumePnL = Arbitrage.GetArbitrageVolumePnL(bidsOrderBook.Bids, asksOrderBook.Asks);

            Assert.Equal(70, volumePnL?.Volume);
            Assert.Equal(40.4m, volumePnL?.PnL);
        }
        public void ArbitrageVolumePnL_Complex1_Test()
        {
            // https://docs.google.com/spreadsheets/d/1plnbQSS-WP6ykTv8wIi_hbAhk_aSz_tllXFIE3jhFpU/edit#gid=0

            const string exchangeName = "FE";
            var          assetPair    = new AssetPair("BTC", "USD", 8, 8);
            var          timestamp    = DateTime.UtcNow;

            var asks = new List <VolumePrice>
            {
                new VolumePrice(1000, 10),
                new VolumePrice(950, 10),
                new VolumePrice(850, 10), // <-
                new VolumePrice(800, 10), // <-
                new VolumePrice(700, 10), // <-
                new VolumePrice(650, 10), // <-
                new VolumePrice(600, 10), // <-
                new VolumePrice(550, 1),  // <-
                new VolumePrice(500, 10)  // <-
            };

            var bids = new List <VolumePrice>
            {
                new VolumePrice(900, 5),   // <-
                new VolumePrice(750, 100), // <-
                new VolumePrice(550, 1)    // <-
            };

            var bidsOrderBook = new OrderBook(exchangeName, assetPair, bids, new List <VolumePrice>(), timestamp);
            var asksOrderBook = new OrderBook(exchangeName, assetPair, new List <VolumePrice>(), asks, timestamp);

            var volumePnL = Arbitrage.GetArbitrageVolumePnL(bidsOrderBook.Bids, asksOrderBook.Asks);

            Assert.Equal(41, volumePnL?.Volume);
            Assert.Equal(6450, volumePnL?.PnL);
        }
        private Task <IReadOnlyList <LykkeArbitrageRow> > GetArbitragesAsync(IReadOnlyList <OrderBook> orderBooks)
        {
            orderBooks = orderBooks.Where(x => x.BestBid.HasValue || x.BestAsk.HasValue).OrderBy(x => x.AssetPair.Name).ToList();

            var result = new List <LykkeArbitrageRow>();

            var watch = Stopwatch.StartNew();

            var synthsCount = 0;

            // O( (n^2)/2 )
            for (var i = 0; i < orderBooks.Count; i++)
            {
                if (i == orderBooks.Count - 1)
                {
                    break;
                }

                var target = orderBooks.ElementAt(i);

                for (var j = i + 1; j < orderBooks.Count; j++)
                {
                    var source = orderBooks.ElementAt(j);

                    if (target.ToString() == source.ToString())
                    {
                        continue;
                    }

                    // Calculate all synthetic order books between a source order book and a target order book
                    var synthOrderBooks = SynthOrderBook.GetSynthsFromAll(target.AssetPair, source, orderBooks);
                    synthsCount += synthOrderBooks.Count;

                    // Compare each synthetic with current target asset pair
                    foreach (var synthOrderBook in synthOrderBooks)
                    {
                        decimal spread     = 0;
                        decimal volume     = 0;
                        decimal pnL        = 0;
                        string  targetSide = null;

                        // Bid side
                        if (target.BestBid?.Price > synthOrderBook.BestAsk?.Price)
                        {
                            spread = Arbitrage.GetSpread(target.BestBid.Value.Price, synthOrderBook.BestAsk.Value.Price);
                            var volumePnL = Arbitrage.GetArbitrageVolumePnL(target.Bids, synthOrderBook.Asks);
                            Debug.Assert(volumePnL?.Volume != null);
                            Debug.Assert(volumePnL?.PnL != null);
                            targetSide = "Bid";
                            volume     = volumePnL.Value.Volume;
                            pnL        = volumePnL.Value.PnL;
                        }

                        // Ask side
                        if (synthOrderBook.BestBid?.Price > target.BestAsk?.Price)
                        {
                            spread = Arbitrage.GetSpread(synthOrderBook.BestBid.Value.Price, target.BestAsk.Value.Price);
                            var volumePnL = Arbitrage.GetArbitrageVolumePnL(synthOrderBook.Bids, target.Asks);
                            Debug.Assert(volumePnL?.Volume != null);
                            Debug.Assert(volumePnL?.PnL != null);
                            targetSide = "Ask";
                            volume     = volumePnL.Value.Volume;
                            pnL        = volumePnL.Value.PnL;
                        }

                        if (string.IsNullOrWhiteSpace(targetSide)) // no arbitrage
                        {
                            continue;
                        }

                        var baseToUsdRate  = Convert(target.AssetPair.Base, Usd, orderBooks);
                        var quoteToUsdRate = Convert(target.AssetPair.Quote, Usd, orderBooks);
                        var volumeInUsd    = volume * baseToUsdRate;
                        volumeInUsd = volumeInUsd.HasValue ? Math.Round(volumeInUsd.Value) : (decimal?)null;
                        var pnLInUsd = pnL * quoteToUsdRate;
                        pnLInUsd = pnLInUsd.HasValue ? Math.Round(pnLInUsd.Value) : (decimal?)null;

                        var lykkeArbitrage = new LykkeArbitrageRow(target.AssetPair, source.AssetPair, spread, targetSide, synthOrderBook.ConversionPath,
                                                                   volume, target.BestBid?.Price, target.BestAsk?.Price, synthOrderBook.BestBid?.Price, synthOrderBook.BestAsk?.Price, volumeInUsd,
                                                                   pnL, pnLInUsd);
                        result.Add(lykkeArbitrage);
                    }
                }
            }

            watch.Stop();
            if (watch.ElapsedMilliseconds > 1000)
            {
                _log.Info($"{watch.ElapsedMilliseconds} ms, {result.Count} arbitrages, {orderBooks.Count} order books, {synthsCount} synthetic order books.");
            }

            return(Task.FromResult(result.OrderBy(x => x.Target).ThenBy(x => x.Source).ToList() as IReadOnlyList <LykkeArbitrageRow>));
        }
Exemplo n.º 7
0
        public Matrix GetMatrix(string assetPair, bool isPublic = false, bool depositFee = false, bool tradingFee = false)
        {
            if (string.IsNullOrWhiteSpace(assetPair))
            {
                return(null);
            }

            var result = new Matrix(assetPair);
            var s      = Settings();

            // Filter by asset pair
            var orderBooks = _orderBooksService.GetAll().Where(x =>
                                                               string.Equals(x.AssetPair.Name, assetPair.Replace("/", ""), StringComparison.OrdinalIgnoreCase)).ToList();

            // Filter by exchanges
            if (isPublic && s.PublicMatrixExchanges.Any())
            {
                orderBooks = orderBooks.Where(x => s.PublicMatrixExchanges.Keys.Contains(x.Source)).ToList();
            }

            // Order by exchange name
            orderBooks = orderBooks.OrderBy(x => x.Source).ToList();

            // Fees
            var exchangesFees = new List <ExchangeFees>();

            foreach (var orderBook in orderBooks)
            {
                var exchangeFees = s.ExchangesFees.SingleOrDefault(x => x.ExchangeName.Equals(orderBook.Source, StringComparison.OrdinalIgnoreCase))
                                   ?? new ExchangeFees(orderBook.Source, 0, 0); // deposit and trading fees = 0 by default
                exchangesFees.Add(exchangeFees);
            }

            // Order books with fees
            var useFees            = depositFee || tradingFee;
            var orderBooksWithFees = useFees ? new List <OrderBook>() : null;

            if (useFees)
            {
                // Put fees into prices
                foreach (var orderBook in orderBooks)
                {
                    var exchangeFees      = exchangesFees.Single(x => x.ExchangeName == orderBook.Source);
                    var totalFee          = (depositFee ? exchangeFees.DepositFee : 0) + (tradingFee ? exchangeFees.TradingFee : 0);
                    var orderBookWithFees = orderBook.DeepClone(totalFee);
                    orderBooksWithFees.Add(orderBookWithFees);
                }
                orderBooks = orderBooksWithFees;
            }

            var exchangesNames = orderBooks.Select(x => x.Source).ToList();

            // Raplace exchange names
            if (isPublic && s.PublicMatrixExchanges.Any())
            {
                exchangesNames = exchangesNames.Select(x => x.Replace(x, s.PublicMatrixExchanges[x])).ToList();
            }

            var matrixSide = exchangesNames.Count;

            for (var row = 0; row < matrixSide; row++)
            {
                var orderBookRow = orderBooks[row];
                var cellsRow     = new List <MatrixCell>();
                var isActual     = (DateTime.UtcNow - orderBookRow.Timestamp).TotalSeconds < s.ExpirationTimeInSeconds;
                var assetPairObj = orderBookRow.AssetPair;

                // Add ask and exchange
                var exchangeName = orderBookRow.Source;
                var exchangeFees = exchangesFees.Single(x => x.ExchangeName == exchangeName);
                result.Exchanges.Add(new Exchange(exchangeName, isActual, exchangeFees));
                result.Asks.Add(GetPriceWithAccuracy(orderBookRow.BestAsk?.Price, assetPairObj));

                for (var col = 0; col < matrixSide; col++)
                {
                    var orderBookCol = orderBooks[col];

                    // Add bid
                    if (row == 0)
                    {
                        result.Bids.Add(GetPriceWithAccuracy(orderBookCol.BestBid?.Price, assetPairObj));
                    }

                    // If the same exchanges than cell = null
                    MatrixCell cell;
                    if (row == col)
                    {
                        cellsRow.Add(null);
                        continue;
                    }

                    // If current cell doesn't have prices on one or both sides.
                    if (orderBookCol.BestBid == null || orderBookRow.BestAsk == null)
                    {
                        cell = new MatrixCell(null, null);
                        cellsRow.Add(cell);
                        continue;
                    }

                    var spread = Arbitrage.GetSpread(orderBookCol.BestBid.Value.Price, orderBookRow.BestAsk.Value.Price);
                    spread = Math.Round(spread, 2);
                    decimal?volume = null;
                    if (spread < 0)
                    {
                        volume = Arbitrage.GetArbitrageVolumePnL(orderBookCol.Bids, orderBookRow.Asks)?.Volume;
                        volume = GetVolumeWithAccuracy(volume, assetPairObj);
                    }

                    cell = new MatrixCell(spread, volume);
                    cellsRow.Add(cell);
                }

                // row ends
                result.Cells.Add(cellsRow);
            }

            result.DateTime = DateTime.UtcNow;

            return(result);
        }