All Downloads are FREE. Search and download functionalities are using the official Maven repository.

info.bitrich.xchangestream.bybit.BybitStreamingMarketDataService Maven / Gradle / Ivy

The newest version!
package info.bitrich.xchangestream.bybit;

import static org.knowm.xchange.bybit.BybitAdapters.convertToBybitSymbol;

import com.fasterxml.jackson.databind.ObjectMapper;
import dto.marketdata.BybitOrderbook;
import dto.marketdata.BybitPublicOrder;
import dto.trade.BybitTrade;
import info.bitrich.xchangestream.core.StreamingMarketDataService;
import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.PublishSubject;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.knowm.xchange.dto.Order;
import org.knowm.xchange.dto.marketdata.OrderBook;
import org.knowm.xchange.dto.marketdata.OrderBookUpdate;
import org.knowm.xchange.dto.marketdata.Trade;
import org.knowm.xchange.instrument.Instrument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BybitStreamingMarketDataService implements StreamingMarketDataService {

  private final Logger LOG = LoggerFactory.getLogger(BybitStreamingMarketDataService.class);
  private final BybitStreamingService streamingService;
  private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper();
  public static final String TRADE = "publicTrade.";
  public static final String ORDERBOOK = "orderbook.";
  public static final String TICKER = "tickers.";

  private final Map orderBookMap = new HashMap<>();
  private final Map>>
      orderBookUpdatesSubscriptions;

  public BybitStreamingMarketDataService(BybitStreamingService streamingService) {
    this.streamingService = streamingService;
    this.orderBookUpdatesSubscriptions = new ConcurrentHashMap<>();
  }

  /**
   * Linear & inverse: Level 1 data, push frequency: 10ms Level 50 data, push frequency: 20ms Level
   * 200 data, push frequency: 100ms Level 500 data, push frequency: 100ms Spot: Level 1 data, push
   * frequency: 10ms Level 50 data, push frequency: 20ms Level 200 data, push frequency: 200ms
   *
   * @param args - orderbook depth
   */
  @Override
  public Observable getOrderBook(Instrument instrument, Object... args) {
    String depth = "50";
    AtomicLong orderBookUpdateIdPrev = new AtomicLong();
    if (args.length > 0 && args[0] != null) {
      depth = args[0].toString();
    }
    String channelUniqueId = ORDERBOOK + depth + "." + convertToBybitSymbol(instrument);
    return streamingService
        .subscribeChannel(channelUniqueId)
        .flatMap(
            jsonNode -> {
              BybitOrderbook bybitOrderbooks = mapper.treeToValue(jsonNode, BybitOrderbook.class);
              String type = bybitOrderbooks.getDataType();
              if (type.equalsIgnoreCase("snapshot")) {
                OrderBook orderBook =
                    BybitStreamAdapters.adaptOrderBook(bybitOrderbooks, instrument);
                orderBookUpdateIdPrev.set(bybitOrderbooks.getData().getU());
                orderBookMap.put(channelUniqueId, orderBook);
                return Observable.just(orderBook);
              } else if (type.equalsIgnoreCase("delta")) {
                return applyDeltaSnapshot(
                    channelUniqueId, instrument, bybitOrderbooks, orderBookUpdateIdPrev);
              }
              return Observable.fromIterable(new LinkedList<>());
            });
  }

  private Observable applyDeltaSnapshot(
      String channelUniqueId,
      Instrument instrument,
      BybitOrderbook bybitOrderBookUpdate,
      AtomicLong orderBookUpdateIdPrev) {
    OrderBook orderBook = orderBookMap.getOrDefault(channelUniqueId, null);
    if (orderBook == null) {
      LOG.error("Failed to get orderBook, channelUniqueId= {}", channelUniqueId);
      return Observable.fromIterable(new LinkedList<>());
    }
    if (orderBookUpdateIdPrev.incrementAndGet() == bybitOrderBookUpdate.getData().getU()) {
      LOG.debug(
          "orderBookUpdate id {}, seq {} ",
          bybitOrderBookUpdate.getData().getU(),
          bybitOrderBookUpdate.getData().getSeq());
      List asks = bybitOrderBookUpdate.getData().getAsk();
      List bids = bybitOrderBookUpdate.getData().getBid();
      Date timestamp = new Date(Long.parseLong(bybitOrderBookUpdate.getTs()));
      asks.forEach(
          bybitPublicOrder ->
              orderBook.update(
                  BybitStreamAdapters.adaptOrderBookOrder(
                      bybitPublicOrder, instrument, Order.OrderType.ASK, timestamp)));
      bids.forEach(
          bybitPublicOrder ->
              orderBook.update(
                  BybitStreamAdapters.adaptOrderBookOrder(
                      bybitPublicOrder, instrument, Order.OrderType.BID, timestamp)));
      if (orderBookUpdatesSubscriptions.get(instrument) != null) {
        orderBookUpdatesSubscriptions(instrument, asks, bids, timestamp);
      }
      return Observable.just(orderBook);
    } else {
      LOG.error(
          "orderBookUpdate id sequence failed, expected {}, in fact {}",
          orderBookUpdateIdPrev,
          bybitOrderBookUpdate.getData().getU());
      // resubscribe or what here?
      return Observable.fromIterable(new LinkedList<>());
    }
  }

  @Override
  public Observable> getOrderBookUpdates(
      Instrument instrument, Object... args) {
    return orderBookUpdatesSubscriptions.computeIfAbsent(instrument, v -> PublishSubject.create());
  }

  private void orderBookUpdatesSubscriptions(
      Instrument instrument, List asks, List bids, Date date) {
    List orderBookUpdates = new ArrayList<>();
    for (BybitPublicOrder ask : asks) {
      OrderBookUpdate o =
          new OrderBookUpdate(
              Order.OrderType.ASK,
              new BigDecimal(ask.getSize()),
              instrument,
              new BigDecimal(ask.getPrice()),
              date,
              new BigDecimal(ask.getSize()));
      orderBookUpdates.add(o);
    }
    for (BybitPublicOrder bid : bids) {
      OrderBookUpdate o =
          new OrderBookUpdate(
              Order.OrderType.BID,
              new BigDecimal(bid.getSize()),
              instrument,
              new BigDecimal(bid.getPrice()),
              date,
              new BigDecimal(bid.getSize()));
      orderBookUpdates.add(o);
    }
    orderBookUpdatesSubscriptions.get(instrument).onNext(orderBookUpdates);
  }

  @Override
  public Observable getTrades(Instrument instrument, Object... args) {
    String channelUniqueId = TRADE + convertToBybitSymbol(instrument);

    return streamingService
        .subscribeChannel(channelUniqueId)
        .filter(message -> message.has("data"))
        .flatMap(
            jsonNode -> {
              List bybitTradeList =
                  mapper.treeToValue(
                      jsonNode.get("data"),
                      mapper
                          .getTypeFactory()
                          .constructCollectionType(List.class, BybitTrade.class));
              return Observable.fromIterable(
                  BybitStreamAdapters.adaptTrades(bybitTradeList, instrument).getTrades());
            });
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy