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

info.bitrich.xchangestream.gemini.GeminiStreamingMarketDataService Maven / Gradle / Ivy

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

import static org.knowm.xchange.gemini.v1.GeminiAdapters.adaptTrades;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.MoreObjects;
import info.bitrich.xchangestream.core.StreamingMarketDataService;
import info.bitrich.xchangestream.gemini.dto.GeminiLimitOrder;
import info.bitrich.xchangestream.gemini.dto.GeminiOrderbook;
import info.bitrich.xchangestream.gemini.dto.GeminiWebSocketTransaction;
import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.PublishSubject;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.marketdata.OrderBook;
import org.knowm.xchange.dto.marketdata.Ticker;
import org.knowm.xchange.dto.marketdata.Trade;
import org.knowm.xchange.dto.trade.LimitOrder;
import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException;
import org.knowm.xchange.gemini.v1.dto.marketdata.GeminiTrade;

/** Created by Lukas Zaoralek on 15.11.17. */
public class GeminiStreamingMarketDataService implements StreamingMarketDataService {
  private final GeminiStreamingService service;
  private final Map orderbooks = new HashMap<>();

  private final ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper();

  public GeminiStreamingMarketDataService(GeminiStreamingService service) {
    this.service = service;
  }

  private boolean filterEventsByReason(JsonNode message, String type, String reason) {
    boolean hasEvents = false;
    if (message.has("events")) {
      for (JsonNode jsonEvent : message.get("events")) {
        boolean reasonResult =
            reason == null
                || (jsonEvent.has("reason") && jsonEvent.get("reason").asText().equals(reason));
        if (jsonEvent.get("type").asText().equals(type) && reasonResult) {
          hasEvents = true;
          break;
        }
      }
    }

    return hasEvents;
  }

  @Override
  public Observable getOrderBook(CurrencyPair currencyPair, Object... args) {

    int maxDepth = (int) MoreObjects.firstNonNull(args.length > 0 ? args[0] : null, 1);

    Observable subscribedOrderbookSnapshot =
        service
            .subscribeChannel(currencyPair, maxDepth, maxDepth)
            .filter(
                s ->
                    filterEventsByReason(s, "change", "initial")
                        || filterEventsByReason(s, "change", "place")
                        || filterEventsByReason(s, "change", "cancel")
                        || filterEventsByReason(s, "change", "trade"))
            .filter(
                s -> // filter out updates that arrive before initial book
                orderbooks.get(currencyPair) != null
                        || filterEventsByReason(s, "change", "initial"))
            .map(
                (JsonNode s) -> {
                  if (filterEventsByReason(s, "change", "initial")) {
                    GeminiWebSocketTransaction transaction =
                        mapper.treeToValue(s, GeminiWebSocketTransaction.class);
                    GeminiOrderbook orderbook = transaction.toGeminiOrderbook(currencyPair);
                    orderbooks.put(currencyPair, orderbook);
                    return orderbook;
                  }

                  if (filterEventsByReason(s, "change", "place")
                      || filterEventsByReason(s, "change", "cancel")
                      || filterEventsByReason(s, "change", "trade")) {

                    GeminiWebSocketTransaction transaction =
                        mapper.treeToValue(s, GeminiWebSocketTransaction.class);
                    GeminiLimitOrder[] levels = transaction.toGeminiLimitOrdersUpdate();
                    GeminiOrderbook orderbook = orderbooks.get(currencyPair);
                    orderbook.updateLevels(levels);
                    return orderbook;
                  }

                  throw new NotYetImplementedForExchangeException(
                      " Unknown message type, even after filtering: " + s.toString());
                });

    return subscribedOrderbookSnapshot.map(
        geminiOrderbook -> GeminiAdaptersX.toOrderbook(geminiOrderbook, maxDepth, new Date()));
  }

  @Override
  public Observable getTicker(CurrencyPair currencyPair, Object... args) {
    return PublishSubject.create(
        emitter ->
            getOrderBook(currencyPair, args)
                .subscribe(
                    orderBook -> {
                      LimitOrder firstBid = orderBook.getBids().iterator().next();
                      LimitOrder firstAsk = orderBook.getAsks().iterator().next();
                      emitter.onNext(
                          new Ticker.Builder()
                              .instrument(currencyPair)
                              .bid(firstBid.getLimitPrice())
                              .bidSize(firstBid.getOriginalAmount())
                              .ask(firstAsk.getLimitPrice())
                              .askSize(firstAsk.getOriginalAmount())
                              .timestamp(
                                  firstBid.getTimestamp().after(firstAsk.getTimestamp())
                                      ? firstBid.getTimestamp()
                                      : firstAsk.getTimestamp())
                              .build());
                    },
                    emitter::onError));
  }

  @Override
  public Observable getTrades(CurrencyPair currencyPair, Object... args) {
    Observable subscribedTrades =
        service
            .subscribeChannel(currencyPair, args)
            .filter(s -> filterEventsByReason(s, "trade", null))
            .map(
                (JsonNode s) -> {
                  GeminiWebSocketTransaction transaction =
                      mapper.treeToValue(s, GeminiWebSocketTransaction.class);
                  return transaction.toGeminiTrades();
                });

    return subscribedTrades.flatMapIterable(s -> adaptTrades(s, currencyPair).getTrades());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy