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

io.scalecube.services.examples.orderbook.service.engine.OrderBook Maven / Gradle / Ivy

package io.scalecube.services.examples.orderbook.service.engine;

import io.scalecube.services.examples.orderbook.service.engine.events.AddOrder;
import io.scalecube.services.examples.orderbook.service.engine.events.CancelOrder;
import io.scalecube.services.examples.orderbook.service.engine.events.MatchOrder;
import io.scalecube.services.examples.orderbook.service.engine.events.Side;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
import it.unimi.dsi.fastutil.longs.LongComparators;
import java.util.Set;
import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Flux;

/** An order book. */
public class OrderBook {

  private String instrument;

  private Long2ObjectRBTreeMap bids;
  private Long2ObjectRBTreeMap asks;

  private Long2ObjectOpenHashMap orders;

  EmitterProcessor matchListener = EmitterProcessor.create();
  EmitterProcessor addListener = EmitterProcessor.create();
  EmitterProcessor cancelListener = EmitterProcessor.create();

  /** Create an order book. */
  public OrderBook() {
    this.bids = new Long2ObjectRBTreeMap<>(LongComparators.OPPOSITE_COMPARATOR);
    this.asks = new Long2ObjectRBTreeMap<>(LongComparators.NATURAL_COMPARATOR);
    this.orders = new Long2ObjectOpenHashMap<>();
  }

  /**
   * Enter an order to this order book.
   *
   * 

The incoming order is first matched against resting orders in this order book. This * operation results in zero or more Match events. * *

If the remaining quantity is not zero after the matching operation, the remaining quantity * is added to this order book and an Add event is triggered. * *

If the order identifier is known, do nothing. * * @param orderId an order identifier * @param side the side * @param price the limit price * @param size the size */ public void enter(long orderId, Side side, long price, long size) { if (orders.containsKey(orderId)) { return; } if (side == Side.BUY) { buy(orderId, price, size); } else { sell(orderId, price, size); } } private void buy(long orderId, long price, long size) { long remainingQuantity = size; PriceLevel bestLevel = getBestLevel(asks); while (remainingQuantity > 0 && bestLevel != null && bestLevel.price() <= price) { remainingQuantity = bestLevel.match(orderId, Side.BUY, remainingQuantity, matchListener); if (bestLevel.isEmpty()) { asks.remove(bestLevel.price()); } bestLevel = getBestLevel(asks); } if (remainingQuantity > 0) { orders.put(orderId, add(bids, orderId, Side.BUY, price, remainingQuantity)); addListener.onNext(new AddOrder(orderId, Side.BUY, price, remainingQuantity)); } } private void sell(long orderId, long price, long size) { long remainingQuantity = size; PriceLevel bestLevel = getBestLevel(bids); while (remainingQuantity > 0 && bestLevel != null && bestLevel.price() >= price) { remainingQuantity = bestLevel.match(orderId, Side.SELL, remainingQuantity, matchListener); if (bestLevel.isEmpty()) { bids.remove(bestLevel.price()); } bestLevel = getBestLevel(bids); } if (remainingQuantity > 0) { orders.put(orderId, add(asks, orderId, Side.SELL, price, remainingQuantity)); addListener.onNext(new AddOrder(orderId, Side.SELL, price, remainingQuantity)); } } /** * Cancel a quantity of an order in this order book. The size refers to the new order size. If the * new order size is set to zero, the order is deleted from this order book. * *

A Cancel event is triggered. * *

If the order identifier is unknown, do nothing. * * @param orderId the order identifier * @param size the new size */ public void cancel(long orderId, long size) { Order order = orders.get(orderId); if (order == null) { return; } long remainingQuantity = order.size(); if (size >= remainingQuantity) { return; } if (size > 0) { order.resize(size); } else { delete(order); orders.remove(orderId); } cancelListener.onNext(new CancelOrder(orderId, remainingQuantity - size, size)); } private PriceLevel getBestLevel(Long2ObjectRBTreeMap levels) { if (levels.isEmpty()) { return null; } return levels.get(levels.firstLongKey()); } private Order add( Long2ObjectRBTreeMap levels, long orderId, Side side, long price, long size) { PriceLevel level = levels.get(price); if (level == null) { level = new PriceLevel(side, price); levels.put(price, level); } return level.add(orderId, size); } private void delete(Order order) { PriceLevel level = order.level(); level.delete(order); if (level.isEmpty()) { delete(level); } } private void delete(PriceLevel level) { switch (level.side()) { case BUY: bids.remove(level.price()); break; case SELL: asks.remove(level.price()); break; default: // noop; } } @Override public String toString() { return "OrderBook [instrument=" + instrument + ", bids=" + bids + ", asks=" + asks + "]"; } public Flux addListener() { return this.addListener; } public Flux matchListener() { return matchListener; } public Set getAskPrices() { return this.asks.keySet(); } public Set getBidPrices() { return this.bids.keySet(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy