Skip to content

Commit 75de87b

Browse files
authored
Implemented journaling and snapshot functionalities
- Added journaling feature to log order book changes. - Implemented snapshot feature to save the current state of the order book. - Introduced `enabledJournaling`, `journal` and `snapshot` options in the Orderbook constructor to enable journaling and initialize with saved logs. BRAKING CHANGES - The order book now exposes a new `snapshot` function to take a snapshot of the order book and a new `lastOp` getter to retrieve the ID of the last operation performed. - New `log` property to the responses of each operation when the `enableJournaling` option is enabled - Every order now have a new `origSize` property
2 parents 2a89dc1 + 483eef9 commit 75de87b

File tree

9 files changed

+867
-95
lines changed

9 files changed

+867
-95
lines changed

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,61 @@ bids: 90 -> 5 90 -> 5
252252
80 -> 1 80 -> 1
253253
```
254254

255+
## Options
256+
257+
The orderbook can be initialized with the following options by passing them to the constructor:
258+
259+
### Snapshot
260+
A `snapshot` represents the state of the order book at a specific point in time. It includes the following properties:
261+
262+
- `asks`: An array of ask orders, where each order contains a price and a list of orders associated with that price.
263+
- `bids`: An array of bid orders, where each order contains a price and a list of orders associated with that price.
264+
- `ts`: A timestamp indicating when the snapshot was taken, in Unix timestamp format.
265+
- `lastOp`: The id of the last operation included in the snapshot
266+
267+
Snapshots are crucial for restoring the order book to a previous state. The system can restore from a snapshot before processing any journal logs, ensuring consistency and accuracy.
268+
After taking the snapshot, you can safely remove all logs preceding the `lastOp` id.
269+
270+
```js
271+
const lob = new OrderBook({ enableJournaling: true});
272+
273+
// after every order save the log to the database
274+
const order = lob.limit("sell", "uniqueID", 55, 100)
275+
await saveLog(order.log)
276+
277+
// ... after some time take a snapshot of the order book and save it on the database
278+
279+
const snapshot = lob.snapshot();
280+
await saveSnapshot(snapshot)
281+
282+
// If you want you can safely remove all logs preceding the `lastOp` id of the snapshot, and continue to save each subsequent log to the database
283+
await removePreviousLogs(snapshot.lastOp)
284+
285+
// On server restart get the snapshot from the database and initialize the order book
286+
const logs = await getLogs()
287+
const lob = new OrderBook({ snapshot, journal: log enableJournaling: true });
288+
```
289+
290+
### Journal Logs
291+
The `journal` feature allows for the logging of changes and activities within the orderbook and contains all the orders operations. This is useful for recovering the state of orderbook after unexpected events.
292+
```js
293+
// Assuming 'logs' is an array of log entries retrieved from the database
294+
295+
const logs = await getLogs();
296+
const lob = new OrderBook({ journal: logs, enableJournalLog: true });
297+
```
298+
By combining snapshots with journaling, the system can effectively restore and audit the state of the order book, ensuring data integrity and providing a reliable mechanism for state recovery.
299+
300+
### Enable Journaling
301+
`enabledJournaling` is a configuration setting that determines whether journaling is enabled or disabled. When enabled, all changes to the order book and related activities are logged into a journal. This helps in tracking and auditing the state of the order book over time.
302+
```js
303+
const lob = new OrderBook({ enableJournaling: true }); // false by default
304+
305+
// after every order save the log to the database
306+
const order = lob.limit("sell", "uniqueID", 55, 100)
307+
await saveLog(order.log)
308+
```
309+
255310
## Development
256311

257312
### Build

src/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum ERROR {
1111
ErrLimitFOKNotFillable = 'orderbook: limit FOK order not fillable',
1212
ErrOrderExists = 'orderbook: order already exists',
1313
ErrOrderNotFound = 'orderbook: order not found',
14+
ErrJournalLog = 'journal: invalid journal log format',
1415
}
1516

1617
export const CustomError = (error?: ERROR | string): Error => {
@@ -37,6 +38,8 @@ export const CustomError = (error?: ERROR | string): Error => {
3738
return new Error(ERROR.ErrInvalidTimeInForce)
3839
case ERROR.ErrLimitFOKNotFillable:
3940
return new Error(ERROR.ErrLimitFOKNotFillable)
41+
case ERROR.ErrJournalLog:
42+
return new Error(ERROR.ErrJournalLog)
4043
default:
4144
error = error === undefined || error === '' ? '' : `: ${error}`
4245
return new Error(`${ERROR.Default}${error}`)

src/order.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
import { Side } from './side'
2-
3-
interface IOrder {
4-
id: string
5-
side: Side
6-
size: number
7-
price: number
8-
time: number
9-
isMaker: boolean
10-
}
11-
12-
export interface OrderUpdatePrice { price: number, size?: number }
13-
export interface OrderUpdateSize { price?: number, size: number }
2+
import { IOrder } from './types'
143

154
export enum OrderType {
165
LIMIT = 'limit',
@@ -27,6 +16,7 @@ export class Order {
2716
private readonly _id: string
2817
private readonly _side: Side
2918
private _size: number
19+
private readonly _origSize: number
3020
private _price: number
3121
private _time: number
3222
private readonly _isMaker: boolean
@@ -36,12 +26,14 @@ export class Order {
3626
size: number,
3727
price: number,
3828
time?: number,
39-
isMaker?: boolean
29+
isMaker?: boolean,
30+
origSize?: number
4031
) {
4132
this._id = orderId
4233
this._side = side
4334
this._price = price
4435
this._size = size
36+
this._origSize = origSize ?? size
4537
this._time = time ?? Date.now()
4638
this._isMaker = isMaker ?? false
4739
}
@@ -76,6 +68,11 @@ export class Order {
7668
this._size = size
7769
}
7870

71+
// Getter for the original size of the order
72+
get origSize (): number {
73+
return this._origSize
74+
}
75+
7976
// Getter for order timestamp
8077
get time (): number {
8178
return this._time
@@ -92,29 +89,24 @@ export class Order {
9289
}
9390

9491
// This method returns a string representation of the order
95-
toString = (): string => (
92+
toString = (): string =>
9693
`${this._id}:
9794
side: ${this._side}
95+
origSize: ${this._origSize.toString()}
9896
size: ${this._size.toString()}
9997
price: ${this._price}
10098
time: ${this._time}
10199
isMaker: ${this._isMaker as unknown as string}`
102-
)
103100

104101
// This method returns a JSON string representation of the order
105-
toJSON = (): string => JSON.stringify({
106-
id: this._id,
107-
side: this._side,
108-
size: this._size,
109-
price: this._price,
110-
time: this._time,
111-
isMaker: this._isMaker
112-
})
102+
toJSON = (): string =>
103+
JSON.stringify(this.toObject())
113104

114105
// This method returns an object representation of the order
115106
toObject = (): IOrder => ({
116107
id: this._id,
117108
side: this._side,
109+
origSize: this._origSize,
118110
size: this._size,
119111
price: this._price,
120112
time: this._time,

0 commit comments

Comments
 (0)