public OrderBook DeepClone(decimal fee = 0)
        {
            var result = (OrderBook)MemberwiseClone();

            if (fee == 0)
            {
                Bids = new List <VolumePrice>(Bids);
                Asks = new List <VolumePrice>(Asks);
            }
            else
            {
                var bids = new List <VolumePrice>();
                foreach (var bid in Bids)
                {
                    var newVolumePrice = new VolumePrice(bid.Price - (bid.Price / 100 * fee), bid.Volume);
                    bids.Add(newVolumePrice);
                }
                result.Bids = bids;

                var asks = new List <VolumePrice>();
                foreach (var ask in Asks)
                {
                    var newVolumePrice = new VolumePrice(ask.Price + (ask.Price / 100 * fee), ask.Volume);
                    asks.Add(newVolumePrice);
                }
                result.Asks = asks;
            }

            return(result);
        }
        private static WhichOrder[] GetWithMinVolumeInBaseAsset(VolumePrice left, VolumePrice middle, VolumePrice right)
        {
            var middleVolumeInBaseAsset = middle.Volume / left.Price;

            var interimBidPrice        = left.Price * middle.Price;
            var rightVolumeInBaseAsset = right.Volume / interimBidPrice;

            var minVolume = Math.Min(Math.Min(left.Volume, middleVolumeInBaseAsset), rightVolumeInBaseAsset);

            var result = new List <WhichOrder>();

            if (left.Volume == minVolume)
            {
                result.Add(WhichOrder.Left);
            }

            if (middleVolumeInBaseAsset == minVolume)
            {
                result.Add(WhichOrder.Middle);
            }

            if (rightVolumeInBaseAsset == minVolume)
            {
                result.Add(WhichOrder.Right);
            }

            return(result.ToArray());
        }
        private static VolumePrice SynthVolumePrice(VolumePrice left, VolumePrice right)
        {
            var newPrice = left.Price * right.Price;

            var rightVolumeInBaseAsset = right.Volume / left.Price;
            var minVolume = Math.Min(left.Volume, rightVolumeInBaseAsset);

            var result = new VolumePrice(newPrice, minVolume);

            return(result);
        }
        public Arbitrage(AssetPair assetPair, SynthOrderBook bidSynth, VolumePrice bid, SynthOrderBook askSynth, VolumePrice ask)
        {
            Debug.Assert(bidSynth != null);
            Debug.Assert(askSynth != null);

            AssetPair      = assetPair;
            BidSynth       = bidSynth;
            AskSynth       = askSynth;
            Bid            = bid;
            Ask            = ask;
            Spread         = GetSpread(Bid.Price, Ask.Price);
            Volume         = Ask.Volume < Bid.Volume ? Ask.Volume : Bid.Volume;
            PnL            = GetPnL(Bid.Price, Ask.Price, Volume);
            ConversionPath = FormatConversionPath(BidSynth.ConversionPath, AskSynth.ConversionPath);
            StartedAt      = DateTime.UtcNow;
        }
        public static (decimal Volume, decimal PnL)? GetArbitrageVolumePnL(IEnumerable <VolumePrice> orderedBids, IEnumerable <VolumePrice> orderedAsks)
        {
            Debug.Assert(orderedBids != null);
            Debug.Assert(orderedAsks != null);

            var orderedBidsEnumerator = orderedBids.GetEnumerator();
            var orderedAsksEnumerator = orderedAsks.GetEnumerator();

            if (!orderedBidsEnumerator.MoveNext() || !orderedAsksEnumerator.MoveNext())
            {
                return(null);
            }

            // Clone bids and asks
            decimal volume = 0;
            decimal pnl    = 0;
            var     bid    = new VolumePrice(orderedBidsEnumerator.Current.Price, orderedBidsEnumerator.Current.Volume);
            var     ask    = new VolumePrice(orderedAsksEnumerator.Current.Price, orderedAsksEnumerator.Current.Volume);

            while (true)
            {
                if (bid.Price <= ask.Price)
                {
                    break;
                }

                var tradeBidPrice = bid.Price;
                var tradeAskPrice = ask.Price;
                if (bid.Volume < ask.Volume)
                {
                    volume += bid.Volume;
                    pnl    += bid.Volume * (tradeBidPrice - tradeAskPrice);
                    ask.SubtractVolume(bid.Volume);

                    if (!orderedBidsEnumerator.MoveNext())
                    {
                        break;
                    }
                    bid = orderedBidsEnumerator.Current;
                }
                else if (bid.Volume > ask.Volume)
                {
                    volume += ask.Volume;
                    pnl    += ask.Volume * (tradeBidPrice - tradeAskPrice);
                    bid.SubtractVolume(ask.Volume);

                    if (!orderedAsksEnumerator.MoveNext())
                    {
                        break;
                    }
                    ask = orderedAsksEnumerator.Current;
                }
                else if (bid.Volume == ask.Volume)
                {
                    volume += bid.Volume;
                    pnl    += bid.Volume * (tradeBidPrice - tradeAskPrice);

                    if (!orderedBidsEnumerator.MoveNext())
                    {
                        break;
                    }
                    bid = orderedBidsEnumerator.Current;
                    if (!orderedAsksEnumerator.MoveNext())
                    {
                        break;
                    }
                    ask = orderedAsksEnumerator.Current;
                }
            }

            orderedBidsEnumerator.Dispose();
            orderedAsksEnumerator.Dispose();

            return(volume == 0 ? ((decimal, decimal)?)null : (volume, Math.Round(pnl, 8)));
        }