@0xmonaco/core 1.1.5-develop.129b2c6 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/applications/api.d.ts +5 -5
- package/dist/api/applications/api.js +5 -5
- package/dist/api/buildercodes/api.d.ts +11 -18
- package/dist/api/buildercodes/api.js +15 -31
- package/dist/api/index.d.ts +0 -1
- package/dist/api/index.js +0 -1
- package/dist/api/orderbook/api.d.ts +1 -4
- package/dist/api/orderbook/api.js +0 -2
- package/dist/api/perp/routes.d.ts +0 -55
- package/dist/api/perp/routes.js +0 -18
- package/dist/api/trading/api.d.ts +2 -2
- package/dist/api/trading/api.js +6 -6
- package/dist/api/websocket/websocket.js +38 -601
- package/dist/coverage.d.ts +0 -16
- package/dist/coverage.js +0 -16
- package/dist/sdk.d.ts +1 -2
- package/dist/sdk.js +0 -4
- package/package.json +3 -3
- package/dist/api/analytics/api.d.ts +0 -40
- package/dist/api/analytics/api.js +0 -107
- package/dist/api/analytics/index.d.ts +0 -1
- package/dist/api/analytics/index.js +0 -1
- package/dist/api/websocket/local-book.d.ts +0 -112
- package/dist/api/websocket/local-book.js +0 -423
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local depth book and BBO state for the market-data `depth` / `bbo`
|
|
3
|
-
* WebSocket channels (0XM-3551).
|
|
4
|
-
*
|
|
5
|
-
* Handoff rules (the server enforces them; the client checks defensively):
|
|
6
|
-
* - A Snapshot replaces the local state, including mid-stream.
|
|
7
|
-
* - A frame with `book_seq <=` the current sequence is stale or a duplicate
|
|
8
|
-
* and is dropped.
|
|
9
|
-
* - Every kept frame must have `prev_book_seq <=` the current sequence (the
|
|
10
|
-
* snapshot's for the first frame, else the last applied frame's): a delta
|
|
11
|
-
* restates every level changed since its link, so it may link below the
|
|
12
|
-
* previous frame. A frame that links above it means a lost frame: rebuild
|
|
13
|
-
* from a new snapshot.
|
|
14
|
-
* - A BBO frame carries the whole top of book, so any frame newer than the
|
|
15
|
-
* state applies without a link check.
|
|
16
|
-
* - Frames that arrive before the snapshot are buffered and replayed through
|
|
17
|
-
* the same rules once it lands.
|
|
18
|
-
* - On a side whose snapshot was not complete, levels beyond its deepest
|
|
19
|
-
* returned level are unknown, so the book keeps only levels at or better
|
|
20
|
-
* than that boundary.
|
|
21
|
-
*
|
|
22
|
-
* Sequences are u64 durable-log positions, held as decimal strings and
|
|
23
|
-
* compared as `BigInt`.
|
|
24
|
-
*/
|
|
25
|
-
/** Frames buffered before a snapshot; past it the oldest are dropped (the link check catches the hole). */
|
|
26
|
-
const MAX_BUFFERED_FRAMES = 10_000;
|
|
27
|
-
const SEQUENCE_KEYS = ["book_seq", "prev_book_seq"];
|
|
28
|
-
// A whole integer only: a fractional or exponent value is left as it is, so
|
|
29
|
-
// `JSON.parse` still reads it and the lenient readers leave it unset.
|
|
30
|
-
const SEQUENCE_KEY_PATTERN = new RegExp(`"(${SEQUENCE_KEYS.join("|")})"\\s*:\\s*(\\d+)(?![\\d.eE])`, "g");
|
|
31
|
-
/**
|
|
32
|
-
* Quote the market-data sequence numbers in a raw frame before `JSON.parse`,
|
|
33
|
-
* so a u64 above `Number.MAX_SAFE_INTEGER` survives exactly. Only the
|
|
34
|
-
* unescaped `"book_seq":` / `"prev_book_seq":` keys followed by an integer are
|
|
35
|
-
* touched; inside a JSON string those quotes would be escaped.
|
|
36
|
-
*/
|
|
37
|
-
export function quoteSequenceNumbers(raw) {
|
|
38
|
-
if (!raw.includes("book_seq"))
|
|
39
|
-
return raw;
|
|
40
|
-
return raw.replace(SEQUENCE_KEY_PATTERN, '"$1":"$2"');
|
|
41
|
-
}
|
|
42
|
-
function asRecord(value, what) {
|
|
43
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
44
|
-
throw new Error(`Invalid ${what}: must be an object`);
|
|
45
|
-
}
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
/** A u64 sequence as a decimal string: a quoted integer, or a JSON number that is still exact. */
|
|
49
|
-
function readSequence(data, key) {
|
|
50
|
-
const sequence = tryReadSequence(data, key);
|
|
51
|
-
if (sequence === undefined)
|
|
52
|
-
throw new Error(`Invalid ${key}: must be a non-negative integer`);
|
|
53
|
-
return sequence;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* {@link readSequence} that leaves an absent or malformed value `undefined`
|
|
57
|
-
* instead of throwing. A JSON number counts only while it is still exact (a
|
|
58
|
-
* quoted integer always does), so a rounded u64 never passes for a precise one.
|
|
59
|
-
*/
|
|
60
|
-
export function tryReadSequence(data, key) {
|
|
61
|
-
const value = data[key];
|
|
62
|
-
if (typeof value === "string" && /^\d+$/.test(value))
|
|
63
|
-
return value;
|
|
64
|
-
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0)
|
|
65
|
-
return String(value);
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
function readString(data, key) {
|
|
69
|
-
const value = data[key];
|
|
70
|
-
if (typeof value !== "string")
|
|
71
|
-
throw new Error(`Invalid ${key}: must be a string`);
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
/*
|
|
75
|
-
* Value fields parse leniently (packages/core/AGENTS.md: an over-strict value
|
|
76
|
-
* check drops the whole frame, and here a dropped frame costs a rebuild):
|
|
77
|
-
* numbers and numeric strings are interchangeable, an absent flag takes its
|
|
78
|
-
* safe default. Identity and sequence fields stay strict.
|
|
79
|
-
*/
|
|
80
|
-
/**
|
|
81
|
-
* A non-negative number as plain decimal digits: `String(1e-7)` is exponent
|
|
82
|
-
* notation, which the decimal compare cannot order, so the exponent is
|
|
83
|
-
* expanded into the digits (the number's shortest round-trip form).
|
|
84
|
-
*/
|
|
85
|
-
function plainDecimal(value) {
|
|
86
|
-
const text = String(value);
|
|
87
|
-
const match = /^(\d+)(?:\.(\d+))?e([+-]\d+)$/i.exec(text);
|
|
88
|
-
if (!match)
|
|
89
|
-
return text;
|
|
90
|
-
const [, whole = "0", fraction = "", exponentText = "0"] = match;
|
|
91
|
-
const digits = whole + fraction;
|
|
92
|
-
const point = whole.length + Number(exponentText);
|
|
93
|
-
if (point <= 0)
|
|
94
|
-
return `0.${"0".repeat(-point)}${digits}`.replace(/\.?0+$/, "") || "0";
|
|
95
|
-
if (point >= digits.length)
|
|
96
|
-
return digits + "0".repeat(point - digits.length);
|
|
97
|
-
return `${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
98
|
-
}
|
|
99
|
-
/** A decimal value as a string: a string as sent, a finite number as plain digits. */
|
|
100
|
-
function readDecimal(data, key) {
|
|
101
|
-
const value = data[key];
|
|
102
|
-
if (typeof value === "string")
|
|
103
|
-
return value;
|
|
104
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0)
|
|
105
|
-
return plainDecimal(value);
|
|
106
|
-
throw new Error(`Invalid ${key}: must be a decimal`);
|
|
107
|
-
}
|
|
108
|
-
function readNullableDecimal(data, key) {
|
|
109
|
-
const value = data[key];
|
|
110
|
-
if (value === null || value === undefined)
|
|
111
|
-
return null;
|
|
112
|
-
return readDecimal(data, key);
|
|
113
|
-
}
|
|
114
|
-
/** A boolean, or `fallback` when absent or not a boolean. */
|
|
115
|
-
function readFlag(data, key, fallback) {
|
|
116
|
-
const value = data[key];
|
|
117
|
-
return typeof value === "boolean" ? value : fallback;
|
|
118
|
-
}
|
|
119
|
-
/** A number, or a numeric string; `fallback` otherwise. */
|
|
120
|
-
function readLenientNumber(data, key, fallback) {
|
|
121
|
-
const value = data[key];
|
|
122
|
-
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
123
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
124
|
-
}
|
|
125
|
-
/** A level's order count: a number or numeric string, required (it decides removals). */
|
|
126
|
-
function readOrderCount(level) {
|
|
127
|
-
const parsed = readLenientNumber(level, "order_count", Number.NaN);
|
|
128
|
-
if (!Number.isFinite(parsed) || parsed < 0)
|
|
129
|
-
throw new Error("Invalid order_count: must be a non-negative number");
|
|
130
|
-
return Math.trunc(parsed);
|
|
131
|
-
}
|
|
132
|
-
function readLevels(data, key) {
|
|
133
|
-
const value = data[key];
|
|
134
|
-
if (value === undefined || value === null)
|
|
135
|
-
return [];
|
|
136
|
-
if (!Array.isArray(value))
|
|
137
|
-
throw new Error(`Invalid ${key}: must be an array`);
|
|
138
|
-
return value.map((raw) => {
|
|
139
|
-
const level = asRecord(raw, `${key} level`);
|
|
140
|
-
return {
|
|
141
|
-
price: readDecimal(level, "price"),
|
|
142
|
-
quantity: readDecimal(level, "quantity"),
|
|
143
|
-
orderCount: readOrderCount(level),
|
|
144
|
-
};
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
export function parseDepthSnapshot(raw) {
|
|
148
|
-
const data = asRecord(raw, "depth snapshot");
|
|
149
|
-
return {
|
|
150
|
-
tradingPairId: readString(data, "trading_pair_id"),
|
|
151
|
-
tradingMode: readString(data, "trading_mode"),
|
|
152
|
-
bookSeq: readSequence(data, "book_seq"),
|
|
153
|
-
bids: readLevels(data, "bids"),
|
|
154
|
-
asks: readLevels(data, "asks"),
|
|
155
|
-
// Absent means unknown: treat the side as incomplete (the safe reading).
|
|
156
|
-
bidsComplete: readFlag(data, "bids_complete", false),
|
|
157
|
-
asksComplete: readFlag(data, "asks_complete", false),
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
export function parseDepthDelta(raw) {
|
|
161
|
-
const data = asRecord(raw, "depth frame");
|
|
162
|
-
return {
|
|
163
|
-
tradingPairId: readString(data, "trading_pair_id"),
|
|
164
|
-
tradingMode: readString(data, "trading_mode"),
|
|
165
|
-
bookSeq: readSequence(data, "book_seq"),
|
|
166
|
-
prevBookSeq: readSequence(data, "prev_book_seq"),
|
|
167
|
-
timestampMs: readLenientNumber(data, "timestamp_ms", 0),
|
|
168
|
-
bids: readLevels(data, "bids"),
|
|
169
|
-
asks: readLevels(data, "asks"),
|
|
170
|
-
stale: readFlag(data, "stale", false),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
export function parseBboSnapshot(raw) {
|
|
174
|
-
const data = asRecord(raw, "bbo snapshot");
|
|
175
|
-
return {
|
|
176
|
-
tradingPairId: readString(data, "trading_pair_id"),
|
|
177
|
-
tradingMode: readString(data, "trading_mode"),
|
|
178
|
-
bookSeq: readSequence(data, "book_seq"),
|
|
179
|
-
bestBid: readNullableDecimal(data, "best_bid"),
|
|
180
|
-
bidQty: readNullableDecimal(data, "bid_qty"),
|
|
181
|
-
bestAsk: readNullableDecimal(data, "best_ask"),
|
|
182
|
-
askQty: readNullableDecimal(data, "ask_qty"),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
export function parseBboEvent(raw) {
|
|
186
|
-
const data = asRecord(raw, "bbo frame");
|
|
187
|
-
return {
|
|
188
|
-
...parseBboSnapshot(data),
|
|
189
|
-
prevBookSeq: readSequence(data, "prev_book_seq"),
|
|
190
|
-
timestampMs: readLenientNumber(data, "timestamp_ms", 0),
|
|
191
|
-
stale: readFlag(data, "stale", false),
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
/** Canonical form of a decimal price, so "64250.50" and "64250.5" are one level. */
|
|
195
|
-
function priceKey(price) {
|
|
196
|
-
const trimmed = price.trim();
|
|
197
|
-
const [integer = "0", fraction = ""] = trimmed.split(".");
|
|
198
|
-
const int = integer.replace(/^0+(?=\d)/, "") || "0";
|
|
199
|
-
const frac = fraction.replace(/0+$/, "");
|
|
200
|
-
return frac ? `${int}.${frac}` : int;
|
|
201
|
-
}
|
|
202
|
-
/** Compare two canonical (`priceKey`) non-negative decimals numerically. */
|
|
203
|
-
function compareCanonical(a, b) {
|
|
204
|
-
const [ai = "0", af = ""] = a.split(".");
|
|
205
|
-
const [bi = "0", bf = ""] = b.split(".");
|
|
206
|
-
if (ai.length !== bi.length)
|
|
207
|
-
return ai.length - bi.length;
|
|
208
|
-
if (ai !== bi)
|
|
209
|
-
return ai < bi ? -1 : 1;
|
|
210
|
-
const width = Math.max(af.length, bf.length);
|
|
211
|
-
const ap = af.padEnd(width, "0");
|
|
212
|
-
const bp = bf.padEnd(width, "0");
|
|
213
|
-
if (ap === bp)
|
|
214
|
-
return 0;
|
|
215
|
-
return ap < bp ? -1 : 1;
|
|
216
|
-
}
|
|
217
|
-
/** Compare two non-negative decimal strings numerically. */
|
|
218
|
-
export function compareDecimal(a, b) {
|
|
219
|
-
return compareCanonical(priceKey(a), priceKey(b));
|
|
220
|
-
}
|
|
221
|
-
/** A zero quantity, or zero orders, removes the level (the server sends both together). */
|
|
222
|
-
function isRemoval(level) {
|
|
223
|
-
return level.orderCount === 0 || compareDecimal(level.quantity, "0") === 0;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* An incomplete side is refreshed once its known levels fall below this
|
|
227
|
-
* fraction of what its snapshot held: deltas never bring back the levels
|
|
228
|
-
* beyond its boundary, so a thinned side would otherwise understate the book.
|
|
229
|
-
*/
|
|
230
|
-
const THIN_SIDE_FRACTION = 0.5;
|
|
231
|
-
/** The deepest a side is kept, matching the server's book mirror and snapshots. */
|
|
232
|
-
const MAX_LEVELS_PER_SIDE = 1000;
|
|
233
|
-
/** One side of the book: levels keyed by canonical price, with an optional boundary. */
|
|
234
|
-
class BookSide {
|
|
235
|
-
bestFirst;
|
|
236
|
-
levels = new Map();
|
|
237
|
-
/** Canonical price of the deepest known level on an incomplete side; levels beyond it are unknown. */
|
|
238
|
-
boundary = null;
|
|
239
|
-
/** Whether the side's snapshot was incomplete. */
|
|
240
|
-
incomplete = false;
|
|
241
|
-
/** Known levels below which an incomplete side needs a fresh snapshot. */
|
|
242
|
-
thinBelow = 0;
|
|
243
|
-
/** Whether every level of the side is known. */
|
|
244
|
-
get complete() {
|
|
245
|
-
return !this.incomplete;
|
|
246
|
-
}
|
|
247
|
-
/** `bestFirst` orders canonical prices best first (negative when `a` is better). */
|
|
248
|
-
constructor(bestFirst) {
|
|
249
|
-
this.bestFirst = bestFirst;
|
|
250
|
-
}
|
|
251
|
-
reset(levels, complete) {
|
|
252
|
-
this.levels.clear();
|
|
253
|
-
for (const level of levels) {
|
|
254
|
-
if (!isRemoval(level))
|
|
255
|
-
this.levels.set(priceKey(level.price), level);
|
|
256
|
-
}
|
|
257
|
-
this.incomplete = !complete;
|
|
258
|
-
const known = [...this.levels.keys()].sort(this.bestFirst);
|
|
259
|
-
this.boundary = complete ? null : (known.at(-1) ?? null);
|
|
260
|
-
this.thinBelow = complete ? 0 : Math.max(1, Math.ceil(known.length * THIN_SIDE_FRACTION));
|
|
261
|
-
}
|
|
262
|
-
apply(levels) {
|
|
263
|
-
for (const level of levels) {
|
|
264
|
-
const key = priceKey(level.price);
|
|
265
|
-
// Beyond the boundary of an incomplete side nothing is known.
|
|
266
|
-
if (this.boundary !== null && this.bestFirst(key, this.boundary) > 0)
|
|
267
|
-
continue;
|
|
268
|
-
if (isRemoval(level)) {
|
|
269
|
-
this.levels.delete(key);
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
this.levels.set(key, level);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Like the server's mirror, keep at most MAX_LEVELS_PER_SIDE after each
|
|
276
|
-
// frame: the worst levels go, and the side becomes incomplete at the new
|
|
277
|
-
// deepest level.
|
|
278
|
-
if (this.levels.size > MAX_LEVELS_PER_SIDE) {
|
|
279
|
-
const kept = [...this.levels.keys()].sort(this.bestFirst);
|
|
280
|
-
for (const key of kept.slice(MAX_LEVELS_PER_SIDE))
|
|
281
|
-
this.levels.delete(key);
|
|
282
|
-
this.boundary = kept[MAX_LEVELS_PER_SIDE - 1] ?? null;
|
|
283
|
-
if (!this.incomplete) {
|
|
284
|
-
this.incomplete = true;
|
|
285
|
-
this.thinBelow = Math.ceil(MAX_LEVELS_PER_SIDE * THIN_SIDE_FRACTION);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* An incomplete side that has thinned below its refresh threshold (or came
|
|
291
|
-
* back with no level at all): what lies beyond its boundary is unknown and
|
|
292
|
-
* may now be near the top, so it needs a fresh snapshot.
|
|
293
|
-
*/
|
|
294
|
-
exhausted() {
|
|
295
|
-
return this.incomplete && this.levels.size < this.thinBelow;
|
|
296
|
-
}
|
|
297
|
-
/** The side, best first, sorted once on the canonical keys. */
|
|
298
|
-
view() {
|
|
299
|
-
return [...this.levels.entries()].sort(([a], [b]) => this.bestFirst(a, b)).map(([, level]) => level);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* The sequence guard shared by `depth` and `bbo`: snapshot replaces, stale
|
|
304
|
-
* frames drop, a frame linking above the current sequence is a gap, frames
|
|
305
|
-
* before the snapshot buffer.
|
|
306
|
-
*/
|
|
307
|
-
class SequencedState {
|
|
308
|
-
current = null;
|
|
309
|
-
buffered = [];
|
|
310
|
-
/**
|
|
311
|
-
* Whether a frame must link to the current state. Depth deltas carry only
|
|
312
|
-
* changed levels, so a frame past a lost one is a gap; a BBO frame carries
|
|
313
|
-
* the whole top of book, so any newer one is complete on its own.
|
|
314
|
-
*/
|
|
315
|
-
checksLinks = true;
|
|
316
|
-
/** Whether a snapshot has been applied since the last reset. */
|
|
317
|
-
get ready() {
|
|
318
|
-
return this.current !== null;
|
|
319
|
-
}
|
|
320
|
-
/** Whether the state can no longer be trusted past its known levels (see `"truncated"`). */
|
|
321
|
-
truncated() {
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
/** Replace the state with `snapshot`, then replay buffered frames. Returns `"gap"` if a buffered frame does not link. */
|
|
325
|
-
applySnapshot(snapshot) {
|
|
326
|
-
this.resetFrom(snapshot);
|
|
327
|
-
this.current = BigInt(snapshot.bookSeq);
|
|
328
|
-
const pending = this.buffered;
|
|
329
|
-
this.buffered = [];
|
|
330
|
-
for (const frame of pending) {
|
|
331
|
-
const outcome = this.offer(frame);
|
|
332
|
-
if (outcome === "gap" || outcome === "truncated")
|
|
333
|
-
return outcome;
|
|
334
|
-
}
|
|
335
|
-
// An incomplete side that came back with no level is unusable as sent.
|
|
336
|
-
return this.truncated() ? "truncated" : "applied";
|
|
337
|
-
}
|
|
338
|
-
/** Offer a live frame. */
|
|
339
|
-
offer(frame) {
|
|
340
|
-
if (this.current === null) {
|
|
341
|
-
this.buffered.push(frame);
|
|
342
|
-
if (this.buffered.length > MAX_BUFFERED_FRAMES)
|
|
343
|
-
this.buffered.shift();
|
|
344
|
-
return "buffered";
|
|
345
|
-
}
|
|
346
|
-
const seq = BigInt(frame.bookSeq);
|
|
347
|
-
if (seq <= this.current)
|
|
348
|
-
return "dropped";
|
|
349
|
-
if (this.checksLinks && BigInt(frame.prevBookSeq) > this.current)
|
|
350
|
-
return "gap";
|
|
351
|
-
this.applyFrame(frame);
|
|
352
|
-
this.current = seq;
|
|
353
|
-
return this.truncated() ? "truncated" : "applied";
|
|
354
|
-
}
|
|
355
|
-
/** Discard everything; the next snapshot starts over. */
|
|
356
|
-
reset() {
|
|
357
|
-
this.current = null;
|
|
358
|
-
this.buffered = [];
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
/** The local depth book for one `depth` channel. */
|
|
362
|
-
export class LocalDepthBook extends SequencedState {
|
|
363
|
-
bids = new BookSide((a, b) => compareCanonical(b, a));
|
|
364
|
-
asks = new BookSide((a, b) => compareCanonical(a, b));
|
|
365
|
-
header = null;
|
|
366
|
-
resetFrom(snapshot) {
|
|
367
|
-
this.bids.reset(snapshot.bids, snapshot.bidsComplete);
|
|
368
|
-
this.asks.reset(snapshot.asks, snapshot.asksComplete);
|
|
369
|
-
this.header = {
|
|
370
|
-
tradingPairId: snapshot.tradingPairId,
|
|
371
|
-
tradingMode: snapshot.tradingMode,
|
|
372
|
-
bookSeq: snapshot.bookSeq,
|
|
373
|
-
bidsComplete: snapshot.bidsComplete,
|
|
374
|
-
asksComplete: snapshot.asksComplete,
|
|
375
|
-
stale: false,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
applyFrame(frame) {
|
|
379
|
-
this.bids.apply(frame.bids);
|
|
380
|
-
this.asks.apply(frame.asks);
|
|
381
|
-
if (this.header) {
|
|
382
|
-
this.header = { ...this.header, bookSeq: frame.bookSeq, stale: frame.stale, timestampMs: frame.timestampMs };
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
truncated() {
|
|
386
|
-
return this.bids.exhausted() || this.asks.exhausted();
|
|
387
|
-
}
|
|
388
|
-
reset() {
|
|
389
|
-
super.reset();
|
|
390
|
-
this.header = null;
|
|
391
|
-
}
|
|
392
|
-
/** The current book, or `null` before a snapshot. */
|
|
393
|
-
view() {
|
|
394
|
-
if (!this.ready || !this.header)
|
|
395
|
-
return null;
|
|
396
|
-
return {
|
|
397
|
-
...this.header,
|
|
398
|
-
bids: this.bids.view(),
|
|
399
|
-
asks: this.asks.view(),
|
|
400
|
-
bidsComplete: this.bids.complete,
|
|
401
|
-
asksComplete: this.asks.complete,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
/** The local top of book for one `bbo` channel. */
|
|
406
|
-
export class LocalBbo extends SequencedState {
|
|
407
|
-
checksLinks = false;
|
|
408
|
-
state = null;
|
|
409
|
-
resetFrom(snapshot) {
|
|
410
|
-
this.state = { ...snapshot, stale: false };
|
|
411
|
-
}
|
|
412
|
-
applyFrame(frame) {
|
|
413
|
-
const { prevBookSeq: _prev, ...rest } = frame;
|
|
414
|
-
this.state = { ...rest };
|
|
415
|
-
}
|
|
416
|
-
reset() {
|
|
417
|
-
super.reset();
|
|
418
|
-
this.state = null;
|
|
419
|
-
}
|
|
420
|
-
view() {
|
|
421
|
-
return this.ready && this.state ? { ...this.state } : null;
|
|
422
|
-
}
|
|
423
|
-
}
|