/// <summary> /// Update market depth /// </summary> public void UpdateDepth(IEnumerable <TradeResponse> asks, IEnumerable <TradeResponse> 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 <TradeResponse> updates, IDictionary <decimal, decimal> orders) { if (updates != null) { foreach (TradeResponse 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)); } }
/// <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.IsFullPair) { 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:F3}%. 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 // set max volume : (volumeSpread < _marketStrategyConfig.MinOrderVolume ? _marketStrategyConfig.MinOrderVolume // set min volume : volumeSpread); // return new price-volume pair quote = new Quote(orderPrice, orderVolume, OrderSide.Buy); } return(quote); }
public MarketBestPairChangedEventArgs(MarketDepthPair marketBestPair) { MarketBestPair = marketBestPair ?? throw new ArgumentNullException(nameof(marketBestPair)); }