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>)); }
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); }