Skip to content

Commit

Permalink
Merge pull request #418 from fasenderos/v6-oco
Browse files Browse the repository at this point in the history
feat: add support for OCO orders
  • Loading branch information
fasenderos authored Jul 28, 2024
2 parents 41ef939 + 5b19318 commit ae5f931
Show file tree
Hide file tree
Showing 14 changed files with 1,380 additions and 172 deletions.
46 changes: 40 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Ultra-fast matching engine written in TypeScript

- Standard price-time priority
- Supports both market and limit orders
- Supports conditional orders (Stop Market and Stop Limit)
- Supports conditional orders (Stop Market, Stop Limit and OCO)
- Supports time in force GTC, FOK and IOC
- Supports order cancelling
- Supports order price and/or size updating
Expand Down Expand Up @@ -57,14 +57,16 @@ const lob = new OrderBook()
Then you'll be able to use next primary functions:

```js
lob.createOrder({ type: 'limit' | 'market' | 'stop_limit' | 'stop_market', side: 'buy' | 'sell', size: number, price?: number, id?: string, stopPrice?: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })
lob.createOrder({ type: 'limit' | 'market' | 'stop_limit' | 'stop_market' | 'oco', side: 'buy' | 'sell', size: number, price?: number, id?: string, stopPrice?: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.limit({ id: string, side: 'buy' | 'sell', size: number, price: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.market({ side: 'buy' | 'sell', size: number })

lob.stopLimit({ id: string, side: 'buy' | 'sell', size: number, price: number, stopPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.oco({ id: string, side: 'buy' | 'sell', size: number, price: number, stopPrice: number, stopLimitPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC', stopLimitTimeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.stopMarket({ side: 'buy' | 'sell', size: number, stopPrice: number })

lob.modify(orderID: string, { side: 'buy' | 'sell', size: number, price: number })
Expand All @@ -79,17 +81,20 @@ To add an order to the order book you can call the general `createOrder()` funct
### Create Order

```js
// Create a limit order
// Create limit order
createOrder({ type: 'limit', side: 'buy' | 'sell', size: number, price: number, id: string, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

// Create a market order
// Create market order
createOrder({ type: 'market', side: 'buy' | 'sell', size: number })

// Create a stop limit order
// Create stop limit order
createOrder({ type: 'stop_limit', side: 'buy' | 'sell', size: number, price: number, id: string, stopPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

// Create a stop market order
// Create stop market order
createOrder({ type: 'stop_market', side: 'buy' | 'sell', size: number, stopPrice: number })

// Create OCO order
createOrder({ type: 'oco', side: 'buy' | 'sell', size: number, stopPrice: number, stopLimitPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC', stopLimitTimeInForce?: 'GTC' | 'FOK' | 'IOC' })
```

### Create Limit Order
Expand Down Expand Up @@ -228,6 +233,35 @@ stopLimit({ side: 'buy' | 'sell', id: string, size: number, price: number, stopP
stopMarket({ side: 'buy' | 'sell', size: number, stopPrice: number })
```

### Create OCO (One-Cancels-the-Other) Order

```js
/**
* Create an OCO (One-Cancels-the-Other) order.
* OCO order combines a `stop_limit` order and a `limit` order, where if stop price
* is triggered or limit order is fully or partially fulfilled, the other is canceled.
* Both orders have the same `side` and `size`. If you cancel one of the orders, the
* entire OCO order pair will be canceled.
*
* For BUY orders the `stopPrice` must be above the current price and the `price` below the current price
* For SELL orders the `stopPrice` must be below the current price and the `price` above the current price
*
* See {@link OCOOrderOptions} for details.
*
* @param options
* @param options.side - `sell` or `buy`
* @param options.id - Unique order ID
* @param options.size - How much of currency you want to trade in units of base currency
* @param options.price - The price of the `limit` order at which the order is to be fullfilled, in units of the quote currency
* @param options.stopPrice - The price at which the `stop_limit` order will be triggered.
* @param options.stopLimitPrice - The price of the `stop_limit` order at which the order is to be fullfilled, in units of the quote currency.
* @param options.timeInForce - Time-in-force of the `limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
* @param options.stopLimitTimeInForce - Time-in-force of the `stop_limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
* @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
*/
oco({ side: 'buy' | 'sell', id: string, size: number, price: number, stopPrice: number, stopLimitPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC', stopLimitTimeInForce?: 'GTC' | 'FOK' | 'IOC' })
```

### Modify an existing order

```js
Expand Down
6 changes: 3 additions & 3 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export enum ERROR {
Default = 'Something wrong',
ErrInsufficientQuantity = 'orderbook: insufficient quantity to calculate price',
ErrInvalidConditionalOrder = 'orderbook: Stop-Limit Order (BUY: marketPrice < stopPrice <= price, SELL: marketPrice > stopPrice >= price). Stop-Market Order (BUY: marketPrice < stopPrice, SELL: marketPrice > stopPrice). OCO order (BUY: price < marketPrice < stopPrice, SELL: price > marketPrice > stopPrice)',
ErrInvalidOrderType = "orderbook: supported order type are 'limit' and 'market'",
ErrInvalidPrice = 'orderbook: invalid order price',
ErrInvalidPriceLevel = 'orderbook: invalid order price level',
ErrInvalidPriceOrQuantity = 'orderbook: invalid order price or quantity',
ErrInvalidQuantity = 'orderbook: invalid order quantity',
ErrInvalidSide = "orderbook: given neither 'bid' nor 'ask'",
ErrInvalidStopPrice = 'orderbook: Invalid Stop Price. For Stop-Limit Order (BUY: marketPrice < stopPrice <= price, SELL: marketPrice > stopPrice >= price). For Stop-Market Order (BUY: marketPrice < stopPrice, SELL: marketPrice > stopPrice)',
ErrInvalidTimeInForce = "orderbook: supported time in force are 'GTC', 'IOC' and 'FOK'",
ErrLimitFOKNotFillable = 'orderbook: limit FOK order not fillable',
ErrOrderExists = 'orderbook: order already exists',
Expand All @@ -33,8 +33,8 @@ export const CustomError = (error?: ERROR | string): Error => {
return new Error(ERROR.ErrOrderNotFound)
case ERROR.ErrInvalidSide:
return new Error(ERROR.ErrInvalidSide)
case ERROR.ErrInvalidStopPrice:
return new Error(ERROR.ErrInvalidStopPrice)
case ERROR.ErrInvalidConditionalOrder:
return new Error(ERROR.ErrInvalidConditionalOrder)
case ERROR.ErrInvalidOrderType:
return new Error(ERROR.ErrInvalidOrderType)
case ERROR.ErrInvalidTimeInForce:
Expand Down
21 changes: 18 additions & 3 deletions src/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ export class LimitOrder extends BaseOrder {
private _price: number
private readonly _timeInForce: TimeInForce
private readonly _isMaker: boolean
// Refers to the linked Stop Limit order stopPrice
private readonly _ocoStopPrice?: number
constructor (options: InternalLimitOrderOptions) {
super(options)
this._type = options.type
this._price = options.price
this._timeInForce = options.timeInForce
this._isMaker = options.isMaker
this._ocoStopPrice = options.ocoStopPrice
}

// Getter for order type
Expand Down Expand Up @@ -108,6 +111,10 @@ export class LimitOrder extends BaseOrder {
return this._isMaker
}

get ocoStopPrice (): number | undefined {
return this._ocoStopPrice
}

toString = (): string =>
`${this._id}:
type: ${this.type}
Expand Down Expand Up @@ -170,7 +177,7 @@ export class StopMarketOrder extends BaseOrder {
side: this._side,
size: this._size,
origSize: this._origSize,
stopPrice: this.stopPrice,
stopPrice: this._stopPrice,
time: this._time
})
}
Expand All @@ -181,13 +188,16 @@ export class StopLimitOrder extends BaseOrder {
private readonly _stopPrice: number
private readonly _timeInForce: TimeInForce
private readonly _isMaker: boolean
// It's true when there is a linked Limit Order
private readonly _isOCO: boolean
constructor (options: InternalStopLimitOrderOptions) {
super(options)
this._type = options.type
this._price = options.price
this._stopPrice = options.stopPrice
this._timeInForce = options.timeInForce
this._isMaker = options.isMaker
this._isOCO = options.isOCO ?? false
}

// Getter for order type
Expand Down Expand Up @@ -220,6 +230,11 @@ export class StopLimitOrder extends BaseOrder {
return this._isMaker
}

// Getter for order isOCO
get isOCO (): boolean {
return this._isOCO
}

toString = (): string =>
`${this._id}:
type: ${this.type}
Expand All @@ -241,8 +256,8 @@ export class StopLimitOrder extends BaseOrder {
size: this._size,
origSize: this._origSize,
price: this._price,
stopPrice: this.stopPrice,
timeInForce: this.timeInForce,
stopPrice: this._stopPrice,
timeInForce: this._timeInForce,
time: this._time,
isMaker: this._isMaker
})
Expand Down
Loading

0 comments on commit ae5f931

Please sign in to comment.