Skip to content

Commit f52f865

Browse files
authored
test: add MockExchange unit tests (#107)
Adds 8 unit tests for MockExchange covering determinism, order lifecycle, balance accounting, resting/partial fills, cancel semantics, and reset. The MockExchange implementation was developed independently on main; this PR's test suite is the unique contribution and is merged with full author credit. Closes #19
1 parent aed173f commit f52f865

19 files changed

Lines changed: 297 additions & 27 deletions

File tree

core/src/BaseExchange.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -826,17 +826,24 @@ export abstract class PredictionMarketExchange {
826826
}
827827

828828
/**
829-
* Fetch the current order book (bids/asks) for a specific outcome.
830-
* Essential for calculating spread, depth, and execution prices.
829+
* Fetch the order book (bids/asks) for a specific outcome.
831830
*
832831
* @param outcomeId - The Outcome ID (outcomeId) or market slug
833-
* @param side - Optional 'yes' or 'no' to explicitly indicate the
834-
* outcome side. Required for exchanges where the API returns a
835-
* single orderbook per market (e.g. Limitless) and the caller
836-
* passes a slug instead of a token ID.
837-
* @returns Current order book with bids and asks
832+
* @param limit - Max number of bid/ask levels to return (CCXT-style).
833+
* For range queries, limits the number of snapshots returned.
834+
* @param params - Optional parameters:
835+
* - `side`: 'yes' or 'no' — explicitly indicate the outcome side
836+
* (required for exchanges like Limitless where the API returns a
837+
* single orderbook per market).
838+
* - `since`: Unix timestamp (ms) — fetch a historical snapshot from
839+
* the archive at or before this time (hosted API only).
840+
* - `until`: Unix timestamp (ms) — when combined with `since`,
841+
* returns an array of OrderBook snapshots between `since` and
842+
* `until` (hosted API only).
843+
* @returns Order book with bids and asks. Returns OrderBook[] when
844+
* both `since` and `until` are provided.
838845
*/
839-
async fetchOrderBook(outcomeId: string, side?: 'yes' | 'no'): Promise<OrderBook> {
846+
async fetchOrderBook(outcomeId: string, limit?: number, params?: Record<string, any>): Promise<OrderBook> {
840847
throw new Error("Method fetchOrderBook not implemented.");
841848
}
842849

core/src/exchanges/baozi/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export class BaoziExchange extends PredictionMarketExchange {
123123
return [];
124124
}
125125

126-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
126+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
127127
const rawMarket = await this.fetcher.fetchRawOrderBook(outcomeId);
128128
return this.normalizer.normalizeOrderBook(rawMarket, outcomeId);
129129
}

core/src/exchanges/gemini-titan/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class GeminiTitanExchange extends PredictionMarketExchange {
106106
.filter((e): e is UnifiedEvent => e !== null);
107107
}
108108

109-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
109+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
110110
const { instrumentSymbol } = fromOutcomeId(outcomeId);
111111
const raw = await this.fetcher.fetchRawOrderBook(instrumentSymbol);
112112
if (!raw) {

core/src/exchanges/hyperliquid/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class HyperliquidExchange extends PredictionMarketExchange {
128128
.filter((e): e is UnifiedEvent => e !== null);
129129
}
130130

131-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
131+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
132132
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
133133
return this.normalizer.normalizeOrderBook(raw, outcomeId);
134134
}

core/src/exchanges/kalshi/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export class KalshiExchange extends PredictionMarketExchange {
193193
return this.normalizer.normalizeOHLCV(rawCandles, params);
194194
}
195195

196-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
196+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
197197
validateIdFormat(outcomeId, "OrderBook");
198198
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
199199
return this.normalizer.normalizeOrderBook(raw, outcomeId);

core/src/exchanges/limitless/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,15 @@ export class LimitlessExchange extends PredictionMarketExchange {
200200
return this.normalizer.normalizeOHLCV!(rawPrices as any, params);
201201
}
202202

203-
async fetchOrderBook(outcomeId: string, side?: 'yes' | 'no'): Promise<OrderBook> {
203+
async fetchOrderBook(outcomeId: string, limit?: number, params?: Record<string, any>): Promise<OrderBook> {
204204
const slug = await this.resolveSlug(outcomeId);
205205
const rawOrderBook = await this.fetcher.fetchRawOrderBook!(slug);
206206
const orderBook = this.normalizer.normalizeOrderBook!(rawOrderBook as any, outcomeId);
207207

208208
// The Limitless API always returns the Yes-side order book regardless
209209
// of which token is queried. If the caller asked for the No token,
210210
// flip: noBid = 1 - yesAsk, noAsk = 1 - yesBid.
211+
const side = params?.side;
211212
const isNoToken = side === 'no' || (!side && await this.isNoOutcome(outcomeId, slug));
212213
if (isNoToken) {
213214
return {

core/src/exchanges/mock/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ export class MockExchange extends PredictionMarketExchange {
317317
return limit !== undefined ? events.slice(offset, offset + limit) : events.slice(offset);
318318
}
319319

320-
override async fetchOrderBook(id: string): Promise<OrderBook> {
320+
override async fetchOrderBook(id: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
321321
const f = new SeededRng(id);
322322
const midPrice = round(f.float(0.1, 0.9), 3);
323323
const spread = round(f.float(0.005, 0.03), 3);

core/src/exchanges/myriad/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export class MyriadExchange extends PredictionMarketExchange {
107107
return this.normalizer.normalizeOHLCV(rawMarket, params, parsedOutcomeId);
108108
}
109109

110-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
110+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
111111
const parts = outcomeId.split(':');
112112
if (parts.length >= 3) {
113113
const [networkId, marketId, oid] = parts;

core/src/exchanges/opinion/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export class OpinionExchange extends PredictionMarketExchange {
207207
return this.normalizer.normalizeOHLCV({ history: rawPoints }, params);
208208
}
209209

210-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
210+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
211211
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);
212212
return this.normalizer.normalizeOrderBook(raw, outcomeId);
213213
}

core/src/exchanges/polymarket/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class PolymarketExchange extends PredictionMarketExchange {
159159
return this.normalizer.normalizeOHLCV(raw, params);
160160
}
161161

162-
async fetchOrderBook(outcomeId: string): Promise<OrderBook> {
162+
async fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook> {
163163
validateIdFormat(outcomeId, 'OrderBook');
164164
validateOutcomeId(outcomeId, 'OrderBook');
165165
const raw = await this.fetcher.fetchRawOrderBook(outcomeId);

0 commit comments

Comments
 (0)