public Task <IEnumerable <SynthOrderBook> > CalculateSynthOrderBooksAsync() { var watch = Stopwatch.StartNew(); var newActualSynthOrderBooks = new List <SynthOrderBook>(); var orderBooks = GetWantedActualOrderBooks().ToList(); var settings = Settings(); foreach (var @base in settings.BaseAssets) { var target = new AssetPair(@base, settings.QuoteAsset, 8, 8); var newActualSynthsFromAll = SynthOrderBook.GetSynthsFromAll(target, orderBooks, orderBooks); newActualSynthOrderBooks.AddRange(newActualSynthsFromAll); } foreach (var newSynthOrderBook in newActualSynthOrderBooks) { _synthOrderBooks[new AssetPairSource(newSynthOrderBook.Source, newSynthOrderBook.AssetPair)] = newSynthOrderBook; } watch.Stop(); if (watch.ElapsedMilliseconds > 500) { _log.Info($"{watch.ElapsedMilliseconds} ms, {_synthOrderBooks.Count} synthetic order books, {orderBooks.Count} order books."); } return(Task.FromResult(_synthOrderBooks.Select(x => x.Value))); }
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()); }
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>)); }