1.0.1 • Published 4 months ago

orderbook-match-engine v1.0.1

Weekly downloads
-
License
ISC
Repository
github
Last release
4 months ago

Orderbook Matching Engine

go to npm

  • install
$ npm install orderbook-match-engine

provider

  • Import
import Engine, { 
  Orderbook, 
  Order, 
  TRADE_SIDE, 
  IDataSourceHook, 
  Trade, 
  STRING_NUMBER 
} from 'orderbook-match-engine';
  • require

In a CommonJS environment, you can use it like this:

const Engine = require('orderbook-match-engine').default;
const {   
  Orderbook, 
  Order, 
  TRADE_SIDE, 
  IDataSourceHook, 
  Trade, 
  STRING_NUMBER  
} = require('orderbook-match-engine');
  • interface
interface IDataSourceHook {
  beforeAddHook: (orderId: number, side: TRADE_SIDE, price: STRING_NUMBER, quantity: STRING_NUMBER) => Promise<boolean>;
  afterAddHook: (order: Order, trades: Array<Trade>) => Promise<boolean>;
  beforeCancelHook: (orderId: number) => Promise<boolean>;
  afterCancelHook: (order: Order) => Promise<boolean>;
}

type EngineOptions = {
  orderbook: Orderbook,
  dataSourceHook?: IDataSourceHook,
}

type OrderbookOptions = {
  limit?: number, // (default: 15)
}
  • create instance
class DataSourceHook implements IDataSourceHook {
  async beforeAddHook (orderId: number, side: TRADE_SIDE, price: STRING_NUMBER, quantity: STRING_NUMBER): Promise<boolean> {
    console.log(`[before order registry] orderId: ${orderId}, side: ${side}, price: ${price}, quantity: ${quantity}`);
    return true;
  }
  async afterAddHook(order: Order, trades: Trade[]): Promise<boolean> {
    console.log(`[after order registry] regitried orderId: ${order.orderId}, traded orderIds: ${trades.length}`);
    return true;
  }
  async beforeCancelHook(orderId: number): Promise<boolean> {
    return true;
  }
  async afterCancelHook(order: Order): Promise<boolean> {
    return true;
  }
}

const orderbook = new Orderbook({limit: 15});
const dataSourceHook = new DataSourceHook();

const engine = new Engine({
  orderbook,
  dataSourceHook,
});

In a javascript environment, you can use it like this:

class DataSourceHook {
  async beforeAddHook (orderId, side, price, quantity) {
    console.log(`[before order registry] orderId: ${orderId}, side: ${side}, price: ${price}, quantity: ${quantity}`);
    return true;
  }
  async afterAddHook(order, trades) {
    console.log(`[after order registry] regitried orderId: ${order.orderId}, traded orderIds: ${trades.length}`);
    return true;
  }
  async beforeCancelHook(orderId) {
    return true;
  }
  async afterCancelHook(order) {
    return true;
  }
}

const orderbook = new Orderbook();
const dataSourceHook = new DataSourceHook();
const engine = new Engine({
  orderbook,
  dataSourceHook,
});

if hooks don't need, possible only use orderbook

create order

  • TRADE_SIDE

ASK: sell order

BID: buy order

export declare enum TRADE_SIDE {
    ASK = 0,
    BID = 1
}
  • interface
add(
  orderId: number, 
  side: TRADE_SIDE, 
  price: STRING_NUMBER, 
  quantity: STRING_NUMBER
): {
    order: Order;
    trades: Trade[];
  }

only orderbook

  • used: bid -> ask

More test cases are in ./test/asks

import Engine, { 
  Orderbook, 
  Order, 
  Trade, 
  IDataSourceHook, 
  TRADE_SIDE, 
  STRING_NUMBER 
} from 'orderbook-match-engine';

const orderbook = new Orderbook();

orderbook.add(1, TRADE_SIDE.BID, 90, 10);
orderbook.add(2, TRADE_SIDE.BID, 100, 10);
orderbook.add(3, TRADE_SIDE.BID, 80, 10);
orderbook.add(4, TRADE_SIDE.BID, 100, 10);

/*
asks: 
---------------  
bids: 100 -> 20
       90 -> 10
       80 -> 10
*/

const order0 = orderbook.add(5, TRADE_SIDE.ASK, 90, 10);

console.log(order0.order.price.toString() === '90', order0.order.leaveQuantity.toString() === '0')
console.log(order0.trades[0].tradePrice.toString() === '100', order0.trades[0].tradeQuantity.toString() === '10')

/*
asks: 90 -> 10        asks:     
---------------    -> ---------------       
bids: 100 -> 20       bids: 100 -> 10     
       90 -> 10              90 -> 10       
       80 -> 10              80 -> 10      
*/

const order1 = orderbook.add(6, TRADE_SIDE.ASK, 100, 10);

console.log(order1.order.price.toString() === '100', order1.order.leaveQuantity.toString() === '0')
console.log(order1.trades[0].tradePrice.toString() === '100', order1.trades[0].tradeQuantity.toString() === '10')

/*
asks: 100 -> 10       asks:     
---------------    -> ---------------       
bids: 100 -> 10       bids:  90 -> 10     
       90 -> 10              80 -> 10       
       80 -> 10              
*/

const order2 =  orderbook.add(7, TRADE_SIDE.ASK, 90, 10);

console.log(order2.order.price.toString() === '90', order2.order.leaveQuantity.toString() === '0')
console.log(order2.trades[0].tradePrice.toString() === '90', order2.trades[0].tradeQuantity.toString() === '10')

/*
asks:  90 -> 10        asks:   
---------------    ->  ---------------       
bids:  90 -> 10        bids: 80 -> 10
       80 -> 10                     
*/

const { asks, bids } = orderbook.getOrderbook()

console.log( asks.length === 0 )
console.log( bids.length === 1 )

console.log(bids[0].price === '80', bids[0].quantity === '10')
  • used: ask -> bid

More test cases are in ./test/bids

import Engine, { 
  Orderbook, 
  Order, 
  Trade, 
  IDataSourceHook, 
  TRADE_SIDE, 
  STRING_NUMBER  
} from 'orderbook-match-engine';

const orderbook = new Orderbook();

orderbook.add(1, TRADE_SIDE.ASK, 100, 10);
orderbook.add(2, TRADE_SIDE.ASK, 110, 10);
orderbook.add(3, TRADE_SIDE.ASK, 100, 10);
orderbook.add(4, TRADE_SIDE.ASK, 120, 20);

/*
      120 -> 20
      110 -> 10
asks: 100 -> 20
---------------  
bids: 
*/

const order0 = orderbook.add(5, TRADE_SIDE.BID, 110, 11);

/*
      120 -> 20             120 -> 20 
      110 -> 10             110 -> 10   
asks: 100 -> 20       asks: 100 -> 9     
---------------   ->  ---------------    
bids: 110 -> 11       bids:    
*/

console.log(order0.order.leaveQuantity.toString() === '0')
console.log(order0.trades[0].tradePrice.toString() === '100', order0.trades[0].tradeQuantity.toString() === '10')
console.log(order0.trades[1].tradePrice.toString() === '100', order0.trades[1].tradeQuantity.toString() === '1')

const order1 = orderbook.add(6, TRADE_SIDE.BID, 120, 18);

/*
       120 -> 20             
       110 -> 10             120 -> 20   
 asks: 100 -> 9        asks: 110 -> 1     
 ---------------   ->  ---------------    
 bids: 120 -> 18       bids:    
*/

console.log(order1.order.leaveQuantity.toString() === '0')
console.log(order1.trades[0].tradePrice.toString() === '100', order1.trades[0].tradeQuantity.toString() === '9')
console.log(order1.trades[1].tradePrice.toString() === '110', order1.trades[1].tradeQuantity.toString() === '9')


const order2 = orderbook.add(6, TRADE_SIDE.BID, 105, 10);

/*
                  
       120 -> 20   
 asks: 110 -> 1     
 ---------------    
 bids: 105 -> 10  
*/

console.log(order2.order.leaveQuantity.toString() === '10')
console.log(order2.trades.length === 0)

const { asks, bids } = orderbook.getOrderbook();
console.log(asks.length === 2)
console.log(bids.length === 1)

console.log(asks[0].price === '110', asks[0].quantity === '1')
console.log(asks[1].price === '120', asks[1].quantity === '20')

console.log(bids[0].price === '105', bids[0].quantity === '10')
  • used: cancel

More test cases are in ./test/cancel

import Engine, { 
  Orderbook, 
  Order, 
  Trade, 
  IDataSourceHook, 
  TRADE_SIDE, 
  STRING_NUMBER 
} from 'orderbook-match-engine';

const orderbook = new Orderbook();

orderbook.add(1, TRADE_SIDE.ASK, 100, 10);
orderbook.add(2, TRADE_SIDE.ASK, 110, 10);
orderbook.add(3, TRADE_SIDE.ASK, 100, 10);
orderbook.add(4, TRADE_SIDE.ASK, 120, 20);

/*
      120 -> 20
      110 -> 10
asks: 100 -> 20
---------------  
bids: 
*/

const cancelOrder0 = orderbook.cancel(1);

/*
      120 -> 20
      110 -> 10
asks: 100 -> 10
---------------  
bids: 
*/
const ob0 = orderbook.getOrderbook();
console.log(ob0.asks.length === 3)
console.log(ob0.bids.length === 0)

console.log(ob0.asks[0].price === '100', ob0.asks[0].quantity === '10')
console.log(ob0.asks[1].price === '110', ob0.asks[1].quantity === '10')
console.log(ob0.asks[2].price === '120', ob0.asks[2].quantity === '20')

engine

  • used
import Engine, { 
  Orderbook, 
  Order, 
  Trade, 
  IDataSourceHook, 
  TRADE_SIDE, 
  STRING_NUMBER 
} from 'orderbook-match-engine'

class DataSourceHook implements IDataSourceHook {
  async beforeAddHook (orderId: number, side: TRADE_SIDE, price: STRING_NUMBER, quantity: STRING_NUMBER): Promise<boolean> {
    console.log(`[before order registry] orderId: ${orderId}, side: ${side}, price: ${price}, quantity: ${quantity}`);
    return true;
  }
  async afterAddHook(order: Order, trades: Trade[]): Promise<boolean> {
    console.log(`[after order registry] regitried orderId: ${order.orderId}, traded orderIds: ${trades.length}`);
    return true;
  }
  async beforeCancelHook(orderId: number): Promise<boolean> {
    return true;
  }
  async afterCancelHook(order: Order): Promise<boolean> {
    return true;
  }
}

const orderbook = new Orderbook({limit: 15});
const dataSourceHook = new DataSourceHook();

const engine = new Engine({
  orderbook,
  dataSourceHook,
});

async function main() {

  await engine.add(1, TRADE_SIDE.BID, 90, 10);
  await engine.add(2, TRADE_SIDE.BID, 100, 10);
  await engine.add(3, TRADE_SIDE.BID, 80, 10);
  await engine.add(4, TRADE_SIDE.BID, 100, 10);
  /*
  asks: 
  ---------------  
  bids: 100 -> 20
         90 -> 10
         80 -> 10
  */
  
  const order0 = await engine.add(5, TRADE_SIDE.ASK, 90, 10);
  /*
  asks: 90 -> 10        asks:     
  ---------------    -> ---------------       
  bids: 100 -> 20       bids: 100 -> 10     
         90 -> 10              90 -> 10       
         80 -> 10              80 -> 10      
  */
  
  const order1 = await engine.add(6, TRADE_SIDE.ASK, 100, 10);
  /*
  asks: 100 -> 10       asks:     
  ---------------    -> ---------------       
  bids: 100 -> 10       bids:  90 -> 10     
         90 -> 10              80 -> 10       
         80 -> 10              
  */
  
  const order2 = await engine.add(7, TRADE_SIDE.ASK, 90, 10);
  /*
  asks:  90 -> 10        asks:   
  ---------------    ->  ---------------       
  bids:  90 -> 10        bids: 80 -> 10
         80 -> 10                     
  */
  
  const { asks, bids } = await engine.getOrderbook()

  // 주문취소
  const createdOrder = await engine.cancel(3);
}

main();

get order

  • interface
getOrderbook(): {
    asks: Array<{ price: string, quantity: string }>;
    bids: Array<{ price: string, quantity: string }>;
};
getAsks(): Array<{ price: string, quantity: string }>;
getBids(): Array<{ price: string, quantity: string }>;
declare class Order {
  orderId: number;
  side: TRADE_SIDE;
  price: Decimal;
  quantity: Decimal;
  leaveQuantity: Decimal;
}
  • get sell order
const asks = orderbook.getAsks();

[
  { price: '110', quantity: '10' },
  { price: '120', quantity: '20' },
  { price: '100', quantity: '20' }
]
  • get buy order
const bids = orderbook.getBids();

[
  { price: '100', quantity: '20' },
  { price: '90',  quantity: '10' },
  { price: '80',  quantity: '10' }
]
  • get orderbook
const { asks, bids } = orderbook.getOrderbook();
// or
const { asks, bids } = await engine.getOrderbook();

cancel order

  • interface
const askOrder1 = orderbook.add(1, TRADE_SIDE.ASK, 100, 10);


orderbook.cancel(1);
// or 
await engine.cancel(1);
1.0.1

4 months ago

1.0.0

1 year ago

0.2.1

1 year ago

0.2.0

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago