private bool CheckIfSpreadChangedOrItsParts(MarketDepthPair bestPair) { var result = bestPair.Bid.Price != _lastQuoterSpreadBuyPart || bestPair.Ask.Price != _lastQuoterSpreadSellPart; if (result) { _lastQuoterSpreadBuyPart = bestPair.Bid.Price; _lastQuoterSpreadSellPart = bestPair.Ask.Price; } return(result); }
/// <summary> /// Process new best <see cref="MarketDepthPair"/> /// </summary> /// <param name="marketPair">Best ask-bid pair</param> /// <returns>Recommended price-volume pair or <see langword="null"/></returns> public Quote Process(MarketDepthPair marketPair) { if (marketPair == null) { throw new ArgumentNullException(nameof(marketPair)); } if (!marketPair.IsFull) { return(null); } Quote quote = null; _logger.Info($"Best ask / bid: {marketPair.Ask.Price} / {marketPair.Bid.Price}. Update Id: {marketPair.UpdateTime}."); // get price spreads (in percent) decimal actualSpread = marketPair.PriceSpread !.Value / marketPair.MediumPrice !.Value * 100; // spread_relative = spread_absolute/price * 100 decimal expectedSpread = _marketStrategyConfig.TradeWhenSpreadGreaterThan; _logger.Info($"Spread absolute (relative): {marketPair.PriceSpread} ({actualSpread/100:P}). Update Id: {marketPair.UpdateTime}."); if (actualSpread >= expectedSpread) { // compute new order price decimal extra = marketPair.MediumPrice.Value * (actualSpread - expectedSpread) / 100; // extra = medium_price * (spread_actual - spread_expected) decimal orderPrice = marketPair.Bid.Price + extra; // new_price = best_bid + extra // compute order volume decimal volumeSpread = marketPair.VolumeSpread !.Value; decimal orderVolume = volumeSpread > _marketStrategyConfig.MaxOrderVolume ? _marketStrategyConfig.MaxOrderVolume : // max volume restriction (volumeSpread < _marketStrategyConfig.MinOrderVolume ? _marketStrategyConfig.MinOrderVolume : volumeSpread); // min volume restriction // return new price-volume pair quote = new Quote(orderPrice, orderVolume, OrderSide.Buy); } return(quote); }
/// <summary> /// Update market depth /// </summary> /// <remarks> /// How to manage a local order book correctly [1]: /// 1. Open a stream to wss://stream.binance.com:9443/ws/bnbbtc@depth /// 2. Buffer the events you receive from the stream /// 3. Get a depth snapshot from https://www.binance.com/api/v1/depth?symbol=BNBBTC&limit=1000 /// -> 4. Drop any event where u is less or equal lastUpdateId in the snapshot /// 5. The first processed should have U less or equal lastUpdateId+1 AND u equal or greater lastUpdateId+1 /// -> 6. While listening to the stream, each new event's U should be equal to the previous event's u+1 /// -> 7. The data in each event is the absolute quantity for a price level /// -> 8. If the quantity is 0, remove the price level /// 9. Receiving an event that removes a price level that is not in your local order book can happen and is normal. /// Reference: /// 1. https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-streams.md#how-to-manage-a-local-order-book-correctly /// </remarks> public void UpdateDepth(IEnumerable <BinanceOrderBookEntry> asks, IEnumerable <BinanceOrderBookEntry> bids, long updateTime) { if (updateTime <= 0) { throw new ArgumentOutOfRangeException(nameof(updateTime)); } // if nothing was changed then return if (updateTime <= LastUpdateTime) { return; } if (asks == null && bids == null) { return; } void UpdateOrderBook(IEnumerable <BinanceOrderBookEntry> updates, IDictionary <decimal, decimal> orders) { if (orders == null) { throw new ArgumentNullException(nameof(orders)); } if (updates == null) { return; } // WARN: clean orders in cases when connector received orderbook snapshots instead of orderbook updates // orders.Clear(); // update order book foreach (BinanceOrderBookEntry t in updates) { if (t.Quantity > IgnoreVolumeValue) { orders[t.Price] = t.Quantity; } else if (orders.ContainsKey(t.Price)) { orders.Remove(t.Price); } } } // save prev BestPair to OnMarketBestPairChanged raise event MarketDepthPair prevBestPair = BestPair; // update asks market depth UpdateOrderBook(asks, _asks); UpdateOrderBook(bids, _bids); // set new update time LastUpdateTime = updateTime; // raise events OnMarketDepthChanged(new MarketDepthChangedEventArgs(Asks, Bids, LastUpdateTime.Value)); if (!BestPair.Equals(prevBestPair)) { OnMarketBestPairChanged(new MarketBestPairChangedEventArgs(BestPair)); } }