示例#1
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());
        }
示例#2
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);
        }
        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>));
        }
示例#4
0
        public Task <Dictionary <string, Arbitrage> > CalculateArbitrages()
        {
            var newArbitrages         = new Dictionary <string, Arbitrage>();
            var actualSynthOrderBooks = GetActualSynthOrderBooks();
            var settings = Settings();

            // For each asset pair
            var uniqueAssetPairs = actualSynthOrderBooks.Select(x => x.AssetPair).Distinct().ToList();

            foreach (var assetPair in uniqueAssetPairs)
            {
                var watch = Stopwatch.StartNew();

                var assetPairSynthOrderBooks = actualSynthOrderBooks.Where(x => x.AssetPair.Equals(assetPair)).ToList();

                // For each synthetic order book make a line for every ask and every bid
                var bidsAndAsks   = CalculateSynthOrderBookLines(assetPairSynthOrderBooks);
                var bidsAndAsksMs = watch.ElapsedMilliseconds;

                if (!bidsAndAsks.HasValue)
                {
                    return(Task.FromResult(newArbitrages));
                }

                var totalItarations    = 0;
                var possibleArbitrages = 0;
                var bids      = bidsAndAsks.Value.bids;
                var bidsCount = bids.Count;
                var asks      = bidsAndAsks.Value.asks;
                var asksCount = asks.Count;
                // Calculate arbitrage for every ask and every higher bid
                for (var b = 0; b < bidsCount; b++)
                {
                    var bid      = bids[b];
                    var bidPrice = bid.Price;

                    for (var a = 0; a < asksCount; a++)
                    {
                        totalItarations++;

                        var ask      = asks[a];
                        var askPrice = ask.Price;

                        if (askPrice >= bidPrice)
                        {
                            continue;
                        }

                        possibleArbitrages++;

                        // Filtering by spread
                        var spread = Arbitrage.GetSpread(bidPrice, askPrice);
                        if (settings.MinSpread < 0 && spread < settings.MinSpread)
                        {
                            continue;
                        }

                        var bidVolume = bid.Volume;
                        var askVolume = ask.Volume;
                        var volume    = askVolume < bidVolume ? askVolume : bidVolume;
                        // Filtering by PnL
                        var pnL = Arbitrage.GetPnL(bidPrice, askPrice, volume);
                        if (settings.MinimumPnL > 0 && pnL < settings.MinimumPnL)
                        {
                            continue;
                        }

                        var key = Arbitrage.FormatConversionPath(bid.SynthOrderBook.ConversionPath, ask.SynthOrderBook.ConversionPath);
                        if (newArbitrages.TryGetValue(key, out var existed))
                        {
                            var newpnL = Arbitrage.GetPnL(bidPrice, askPrice, volume);
                            // Best PnL
                            if (newpnL <= existed.PnL)
                            {
                                continue;
                            }

                            var arbitrage = new Arbitrage(assetPair, bid.SynthOrderBook, new VolumePrice(bid.Price, bid.Volume), ask.SynthOrderBook, new VolumePrice(ask.Price, ask.Volume));
                            newArbitrages[key] = arbitrage;
                        }
                        else
                        {
                            var arbitrage = new Arbitrage(assetPair, bid.SynthOrderBook, new VolumePrice(bid.Price, bid.Volume), ask.SynthOrderBook, new VolumePrice(ask.Price, ask.Volume));
                            newArbitrages.Add(key, arbitrage);
                        }
                    }
                }

                watch.Stop();
                if (watch.ElapsedMilliseconds > 1000)
                {
                    _log.Info($"{watch.ElapsedMilliseconds} ms, {newArbitrages.Count} arbitrages, {actualSynthOrderBooks.Count} actual synthetic order books, {bidsAndAsksMs} ms for bids and asks, {bids.Count} bids, {asks.Count} asks, {totalItarations} iterations, {possibleArbitrages} possible arbitrages.");
                }
            }

            return(Task.FromResult(newArbitrages));
        }