public void ArbitrageVolumePnL_Simple2_Test()
        {
            var eurgbpOB = new OrderBook("FE", _eurgbp,
                                         new List <LimitOrder> (), // bids
                                         new List <LimitOrder>     // asks
            {
                new LimitOrder(0.90477m, 757921.29m)
            },
                                         DateTime.Now);

            var eurusdOB = new OrderBook("FE", _eurusd,
                                         new List <LimitOrder> // bids
            {
                new LimitOrder(1.16211m, 1923.11m),
                new LimitOrder(0.58117m, 100)
            },
                                         new List <LimitOrder>(), // asks
                                         DateTime.Now);

            var gbpusdOB = new OrderBook("FE", _gbpusd,
                                         new List <LimitOrder>(), // bids
                                         new List <LimitOrder>    // asks
            {
                new LimitOrder(1.28167m, 2909.98m),               // 0.78023, 3729.63406 (reciprocal)
                new LimitOrder(1.29906m, 50000m)                  // 0.76978, 64953.0 (reciprocal)
            },
                                         DateTime.Now);

            var eurgbpSynth = SynthOrderBook.FromOrderBooks(eurusdOB, gbpusdOB, _eurgbp);

            var volumePnL = Arbitrage.GetArbitrageVolumeAndPnL(eurgbpSynth.Bids, eurgbpOB.Asks);

            Assert.NotNull(volumePnL?.Volume);
            Assert.NotNull(volumePnL?.PnL);
        }
        public void ArbitrageVolumePnL_Simple1_Test()
        {
            const string source    = "FE";
            var          timestamp = DateTime.UtcNow;

            var bids = new List <LimitOrder>
            {
                new LimitOrder("", "", 9000, 9), // <-
                new LimitOrder("", "", 8900, 5)
            };
            var asks = new List <LimitOrder>
            {
                new LimitOrder("", "", 9000, 10),
                new LimitOrder("", "", 8999.95m, 7),      // <-
                new LimitOrder("", "", 8900.12345677m, 3) // <-
            };

            var bidsOrderBook = new OrderBook(source, _btcusd, bids, new List <LimitOrder>(), timestamp);
            var asksOrderBook = new OrderBook(source, _btcusd, new List <LimitOrder>(), asks, timestamp);

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

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

            const string source    = "FE";
            var          timestamp = DateTime.UtcNow;

            var bids = new List <LimitOrder>
            {
                new LimitOrder("", "", 900, 5),   // <-
                new LimitOrder("", "", 750, 100), // <-
                new LimitOrder("", "", 550, 1)    // <-
            };
            var asks = new List <LimitOrder>
            {
                new LimitOrder("", "", 1000, 10),
                new LimitOrder("", "", 950, 10),
                new LimitOrder("", "", 850, 10), // <-
                new LimitOrder("", "", 800, 10), // <-
                new LimitOrder("", "", 700, 10), // <-
                new LimitOrder("", "", 650, 10), // <-
                new LimitOrder("", "", 600, 10), // <-
                new LimitOrder("", "", 550, 1),  // <-
                new LimitOrder("", "", 500, 10)  // <-
            };

            var bidsOrderBook = new OrderBook(source, _btcusd, bids, new List <LimitOrder>(), timestamp);
            var asksOrderBook = new OrderBook(source, _btcusd, new List <LimitOrder>(), asks, timestamp);

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

            Assert.Equal(41, volumePnL?.Volume);
            Assert.Equal(6450, volumePnL?.PnL);
        }
        public void ArbitrageVolume_NoArbitrage_EmptyOrderBooks_Test()
        {
            const string source    = "FE";
            var          timestamp = DateTime.UtcNow;

            var orderBook1 = new OrderBook(source, _btcusd, new List <LimitOrder>(), new List <LimitOrder>(), timestamp);
            var orderBook2 = new OrderBook(source, _btcusd, new List <LimitOrder>(), new List <LimitOrder>(), timestamp);

            var volume = Arbitrage.GetArbitrageVolumeAndPnL(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 source    = "FE";
            var          timestamp = DateTime.UtcNow;

            var bids = new List <LimitOrder>
            {
                new LimitOrder("", "", 3.2m, 1),
                new LimitOrder("", "", 3.1m, 1),
                new LimitOrder("", "", 3m, 1),
                new LimitOrder("", "", 2.5m, 1),
                new LimitOrder("", "", 2.1m, 100),
                new LimitOrder("", "", 1.6m, 5),
                new LimitOrder("", "", 1.5m, 5),
                new LimitOrder("", "", 1.4m, 5)
            };
            var asks = new List <LimitOrder>
            {
                new LimitOrder("", "", 2.9m, 10),
                new LimitOrder("", "", 2.8m, 10),
                new LimitOrder("", "", 2.7m, 10),
                new LimitOrder("", "", 2.6m, 10),
                new LimitOrder("", "", 2.4m, 10),
                new LimitOrder("", "", 2.3m, 10),
                new LimitOrder("", "", 2.2m, 10),
                new LimitOrder("", "", 2.0m, 10),
                new LimitOrder("", "", 1.9m, 10),
                new LimitOrder("", "", 1.8m, 10),
                new LimitOrder("", "", 1.7m, 10),
                new LimitOrder("", "", 1.3m, 10),
                new LimitOrder("", "", 1.2m, 10),
                new LimitOrder("", "", 1.1m, 10),
            };

            var bidsOrderBook = new OrderBook(source, _btcusd, bids, new List <LimitOrder>(), timestamp);
            var asksOrderBook = new OrderBook(source, _btcusd, new List <LimitOrder>(), asks, timestamp);

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

            Assert.Equal(70, volumePnL?.Volume);
            Assert.Equal(40.4m, volumePnL?.PnL);
        }
        public void ArbitrageVolume_NoArbitrage_TheSameOrderBook_Test()
        {
            const string source    = "FE";
            var          timestamp = DateTime.UtcNow;

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

            var orderBook1 = new OrderBook(source, _btcusd, bids, asks, timestamp);
            var orderBook2 = new OrderBook(source, _btcusd, bids, asks, timestamp);

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

            Assert.Null(volume);
        }
コード例 #7
0
        private IEnumerable <Arbitrage> GetArbitrages(IReadOnlyCollection <OrderBook> orderBooks)
        {
            orderBooks = orderBooks.Where(x => x.BestBid != null || x.BestAsk != null)
                         .OrderBy(x => x.AssetPair.Name).ToList();

            var result = new List <Arbitrage>();

            var watch = Stopwatch.StartNew();

            var synthsCount = 0;

            // O( (n^2)/2 )
            // TODO: cache should be implemented to avoid iterating over all asset pairs every time
            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 source order book and target order book
                    var synthOrderBooks = SynthOrderBook.GetSynthsFromAll(target.AssetPair, source, orderBooks);
                    synthsCount += synthOrderBooks.Count;

                    // Compare each synthetic with target
                    foreach (var synthOrderBook in synthOrderBooks)
                    {
                        decimal spread     = 0;
                        decimal volume     = 0;
                        decimal pnL        = 0;
                        string  targetSide = null;
                        IReadOnlyList <string> marketMakers = new List <string>();

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

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

                        if (targetSide == null) // no arbitrages
                        {
                            continue;
                        }

                        var volumeInUsd = _orderBooksService.ConvertToUsd(target.AssetPair.Base.Id, volume);
                        var pnLInUsd    = _orderBooksService.ConvertToUsd(target.AssetPair.Quote.Id, pnL);

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

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

            return(result.ToList());
        }