diff --git a/core/src/exchanges/baozi/price.test.ts b/core/src/exchanges/baozi/price.test.ts new file mode 100644 index 0000000..50940fa --- /dev/null +++ b/core/src/exchanges/baozi/price.test.ts @@ -0,0 +1,38 @@ +import { clampBaoziPrice, normalizeBaoziOutcomes } from "./price"; +import { MarketOutcome } from "../../types"; + +describe("clampBaoziPrice", () => { + test("clamps values below 0 to 0", () => { + expect(clampBaoziPrice(-0.1)).toBe(0); + }); + + test("clamps values above 1 to 1", () => { + expect(clampBaoziPrice(1.2)).toBe(1); + }); + + test("leaves values in range unchanged", () => { + expect(clampBaoziPrice(0.3)).toBe(0.3); + expect(clampBaoziPrice(0)).toBe(0); + expect(clampBaoziPrice(1)).toBe(1); + }); +}); + +describe("normalizeBaoziOutcomes", () => { + function makeOutcome(price: number): MarketOutcome { + return { outcomeId: "x", marketId: "m", label: "X", price }; + } + + test("normalizes prices to sum to 1", () => { + const outcomes = [makeOutcome(2), makeOutcome(3)]; + normalizeBaoziOutcomes(outcomes); + expect(outcomes[0].price).toBeCloseTo(0.4); + expect(outcomes[1].price).toBeCloseTo(0.6); + }); + + test("does nothing when sum is zero", () => { + const outcomes = [makeOutcome(0), makeOutcome(0)]; + normalizeBaoziOutcomes(outcomes); + expect(outcomes[0].price).toBe(0); + expect(outcomes[1].price).toBe(0); + }); +}); diff --git a/core/src/exchanges/baozi/price.ts b/core/src/exchanges/baozi/price.ts new file mode 100644 index 0000000..41f0022 --- /dev/null +++ b/core/src/exchanges/baozi/price.ts @@ -0,0 +1,16 @@ +import { MarketOutcome } from "../../types"; + +export function clampBaoziPrice(value: number): number { + return Math.min(Math.max(value, 0), 1); +} + +export function normalizeBaoziOutcomes(outcomes: MarketOutcome[]): void { + const sum = outcomes.reduce((acc, item) => acc + item.price, 0); + if (sum <= 0) { + return; + } + + for (const outcome of outcomes) { + outcome.price = outcome.price / sum; + } +} diff --git a/core/src/exchanges/baozi/utils.ts b/core/src/exchanges/baozi/utils.ts index a55acaf..30a78ed 100644 --- a/core/src/exchanges/baozi/utils.ts +++ b/core/src/exchanges/baozi/utils.ts @@ -3,6 +3,7 @@ import bs58 from 'bs58'; import { createHash } from 'crypto'; import { UnifiedMarket, MarketOutcome } from '../../types'; import { addBinaryOutcomes } from '../../utils/market-utils'; +import { clampBaoziPrice, normalizeBaoziOutcomes } from './price'; // --------------------------------------------------------------------------- // Constants @@ -387,13 +388,13 @@ export function mapBooleanToUnified(market: BaoziMarket, pubkey: string): Unifie outcomeId: `${pubkey}-YES`, marketId: pubkey, label: 'Yes', - price: yesPrice, + price: clampBaoziPrice(yesPrice), }, { outcomeId: `${pubkey}-NO`, marketId: pubkey, label: 'No', - price: noPrice, + price: clampBaoziPrice(noPrice), }, ]; @@ -431,17 +432,12 @@ export function mapRaceToUnified(market: BaoziRaceMarket, pubkey: string): Unifi outcomeId: `${pubkey}-${i}`, marketId: pubkey, label: market.outcomeLabels[i] || `Outcome ${i + 1}`, - price: Math.min(Math.max(price, 0), 1), + price: clampBaoziPrice(price), }); } // Normalize prices to sum to 1 - const priceSum = outcomes.reduce((s, o) => s + o.price, 0); - if (priceSum > 0) { - for (const o of outcomes) { - o.price = o.price / priceSum; - } - } + normalizeBaoziOutcomes(outcomes); const um: UnifiedMarket = { marketId: pubkey, diff --git a/core/src/exchanges/kalshi/fetchOHLCV.ts b/core/src/exchanges/kalshi/fetchOHLCV.ts index 4e8cdd3..1a2f88f 100644 --- a/core/src/exchanges/kalshi/fetchOHLCV.ts +++ b/core/src/exchanges/kalshi/fetchOHLCV.ts @@ -3,6 +3,7 @@ import { PriceCandle } from "../../types"; import { mapIntervalToKalshi } from "./utils"; import { validateIdFormat } from "../../utils/validation"; import { kalshiErrorMapper } from "./errors"; +import { fromKalshiCents } from "./price"; export async function fetchOHLCV( id: string, @@ -98,10 +99,10 @@ export async function fetchOHLCV( return { timestamp: c.end_period_ts * 1000, - open: getVal("open") / 100, - high: getVal("high") / 100, - low: getVal("low") / 100, - close: getVal("close") / 100, + open: fromKalshiCents(getVal("open")), + high: fromKalshiCents(getVal("high")), + low: fromKalshiCents(getVal("low")), + close: fromKalshiCents(getVal("close")), volume: c.volume || 0, }; }); diff --git a/core/src/exchanges/kalshi/fetchOrderBook.ts b/core/src/exchanges/kalshi/fetchOrderBook.ts index 2fd5881..25cc24f 100644 --- a/core/src/exchanges/kalshi/fetchOrderBook.ts +++ b/core/src/exchanges/kalshi/fetchOrderBook.ts @@ -3,6 +3,7 @@ import { OrderBook } from "../../types"; import { validateIdFormat } from "../../utils/validation"; import { kalshiErrorMapper } from "./errors"; import { getMarketsUrl } from "./config"; +import { fromKalshiCents, invertKalshiCents } from "./price"; export async function fetchOrderBook( baseUrl: string, @@ -31,12 +32,12 @@ export async function fetchOrderBook( // - Bids: people buying NO (use data.no directly) // - Asks: people selling NO = people buying YES (invert data.yes) bids = (data.no || []).map((level: number[]) => ({ - price: level[0] / 100, + price: fromKalshiCents(level[0]), size: level[1], })); asks = (data.yes || []).map((level: number[]) => ({ - price: 1 - level[0] / 100, // Invert YES price to get NO ask price + price: invertKalshiCents(level[0]), // Invert YES price to get NO ask price size: level[1], })); } else { @@ -44,12 +45,12 @@ export async function fetchOrderBook( // - Bids: people buying YES (use data.yes directly) // - Asks: people selling YES = people buying NO (invert data.no) bids = (data.yes || []).map((level: number[]) => ({ - price: level[0] / 100, + price: fromKalshiCents(level[0]), size: level[1], })); asks = (data.no || []).map((level: number[]) => ({ - price: 1 - level[0] / 100, // Invert NO price to get YES ask price + price: invertKalshiCents(level[0]), // Invert NO price to get YES ask price size: level[1], })); } diff --git a/core/src/exchanges/kalshi/fetchTrades.ts b/core/src/exchanges/kalshi/fetchTrades.ts index ef1fd44..335eec8 100644 --- a/core/src/exchanges/kalshi/fetchTrades.ts +++ b/core/src/exchanges/kalshi/fetchTrades.ts @@ -3,6 +3,7 @@ import { HistoryFilterParams, TradesParams } from "../../BaseExchange"; import { Trade } from "../../types"; import { kalshiErrorMapper } from "./errors"; import { getMarketsUrl } from "./config"; +import { fromKalshiCents } from "./price"; export async function fetchTrades( baseUrl: string, @@ -23,7 +24,7 @@ export async function fetchTrades( return trades.map((t: any) => ({ id: t.trade_id, timestamp: new Date(t.created_time).getTime(), - price: t.yes_price / 100, + price: fromKalshiCents(t.yes_price), amount: t.count, side: t.taker_side === "yes" ? "buy" : "sell", })); diff --git a/core/src/exchanges/kalshi/index.ts b/core/src/exchanges/kalshi/index.ts index f6ae1a4..5951e8a 100644 --- a/core/src/exchanges/kalshi/index.ts +++ b/core/src/exchanges/kalshi/index.ts @@ -32,6 +32,7 @@ import { AuthenticationError } from "../../errors"; import { parseOpenApiSpec } from "../../utils/openapi"; import { kalshiApiSpec } from "./api"; import { getKalshiConfig, KalshiApiConfig, KALSHI_PATHS } from "./config"; +import { fromKalshiCents, invertKalshiCents } from "./price"; // Re-export for external use export type { KalshiWebSocketConfig }; @@ -176,20 +177,20 @@ export class KalshiExchange extends PredictionMarketExchange { if (isNoOutcome) { bids = (data.no || []).map((level: number[]) => ({ - price: level[0] / 100, + price: fromKalshiCents(level[0]), size: level[1], })); asks = (data.yes || []).map((level: number[]) => ({ - price: 1 - level[0] / 100, + price: invertKalshiCents(level[0]), size: level[1], })); } else { bids = (data.yes || []).map((level: number[]) => ({ - price: level[0] / 100, + price: fromKalshiCents(level[0]), size: level[1], })); asks = (data.no || []).map((level: number[]) => ({ - price: 1 - level[0] / 100, + price: invertKalshiCents(level[0]), size: level[1], })); } @@ -219,7 +220,7 @@ export class KalshiExchange extends PredictionMarketExchange { return trades.map((t: any) => ({ id: t.trade_id, timestamp: new Date(t.created_time).getTime(), - price: t.yes_price / 100, + price: fromKalshiCents(t.yes_price), amount: t.count, side: t.taker_side === "yes" ? "buy" : "sell", })); @@ -326,7 +327,7 @@ export class KalshiExchange extends PredictionMarketExchange { return (data.fills || []).map((f: any) => ({ id: f.fill_id, timestamp: new Date(f.created_time).getTime(), - price: f.yes_price / 100, + price: fromKalshiCents(f.yes_price), amount: f.count, side: f.side === "yes" ? ("buy" as const) : ("sell" as const), orderId: f.order_id, diff --git a/core/src/exchanges/kalshi/price.test.ts b/core/src/exchanges/kalshi/price.test.ts new file mode 100644 index 0000000..e24f350 --- /dev/null +++ b/core/src/exchanges/kalshi/price.test.ts @@ -0,0 +1,25 @@ +import { fromKalshiCents, invertKalshiCents, invertKalshiUnified } from "./price"; + +describe("fromKalshiCents", () => { + test("converts cents to a decimal probability", () => { + expect(fromKalshiCents(55)).toBe(0.55); + expect(fromKalshiCents(0)).toBe(0); + expect(fromKalshiCents(100)).toBe(1); + }); +}); + +describe("invertKalshiCents", () => { + test("returns the complement of a cent value", () => { + expect(invertKalshiCents(45)).toBeCloseTo(0.55); + expect(invertKalshiCents(0)).toBe(1); + expect(invertKalshiCents(100)).toBe(0); + }); +}); + +describe("invertKalshiUnified", () => { + test("returns the complement of a normalized price", () => { + expect(invertKalshiUnified(0.45)).toBeCloseTo(0.55); + expect(invertKalshiUnified(0)).toBe(1); + expect(invertKalshiUnified(1)).toBe(0); + }); +}); diff --git a/core/src/exchanges/kalshi/price.ts b/core/src/exchanges/kalshi/price.ts new file mode 100644 index 0000000..3797172 --- /dev/null +++ b/core/src/exchanges/kalshi/price.ts @@ -0,0 +1,11 @@ +export function fromKalshiCents(priceInCents: number): number { + return priceInCents / 100; +} + +export function invertKalshiCents(priceInCents: number): number { + return 1 - priceInCents / 100; +} + +export function invertKalshiUnified(price: number): number { + return 1 - price; +} diff --git a/core/src/exchanges/kalshi/utils.ts b/core/src/exchanges/kalshi/utils.ts index e0e589c..4c38958 100644 --- a/core/src/exchanges/kalshi/utils.ts +++ b/core/src/exchanges/kalshi/utils.ts @@ -1,5 +1,6 @@ import { UnifiedMarket, MarketOutcome, CandleInterval } from "../../types"; import { addBinaryOutcomes } from "../../utils/market-utils"; +import { fromKalshiCents, invertKalshiUnified } from "./price"; export function mapMarketToUnified( event: any, @@ -10,11 +11,11 @@ export function mapMarketToUnified( // Calculate price let price = 0.5; if (market.last_price) { - price = market.last_price / 100; + price = fromKalshiCents(market.last_price); } else if (market.yes_ask && market.yes_bid) { - price = (market.yes_ask + market.yes_bid) / 200; + price = (fromKalshiCents(market.yes_ask) + fromKalshiCents(market.yes_bid)) / 2; } else if (market.yes_ask) { - price = market.yes_ask / 100; + price = fromKalshiCents(market.yes_ask); } // Extract candidate name @@ -44,7 +45,7 @@ export function mapMarketToUnified( outcomeId: `${market.ticker}-NO`, marketId: market.ticker, label: candidateName ? `Not ${candidateName}` : "No", - price: 1 - price, + price: invertKalshiUnified(price), priceChange24h: -priceChange, // Inverse change for No? simplified assumption }, ]; diff --git a/core/src/exchanges/myriad/index.ts b/core/src/exchanges/myriad/index.ts index b16f912..6b9a82f 100644 --- a/core/src/exchanges/myriad/index.ts +++ b/core/src/exchanges/myriad/index.ts @@ -11,6 +11,7 @@ import { AuthenticationError } from '../../errors'; import { BASE_URL } from './utils'; import { parseOpenApiSpec } from '../../utils/openapi'; import { myriadApiSpec } from './api'; +import { resolveMyriadPrice } from './price'; export class MyriadExchange extends PredictionMarketExchange { override readonly has = { @@ -140,7 +141,7 @@ export class MyriadExchange extends PredictionMarketExchange { return filtered.map((t: any, index: number) => ({ id: `${t.blockNumber || t.timestamp}-${index}`, timestamp: (t.timestamp || 0) * 1000, - price: t.shares > 0 ? Number(t.value) / Number(t.shares) : 0, + price: resolveMyriadPrice(t), amount: Number(t.shares || 0), side: t.action === 'buy' ? 'buy' as const : 'sell' as const, })); @@ -169,7 +170,7 @@ export class MyriadExchange extends PredictionMarketExchange { return tradeEvents.map((t: any, i: number) => ({ id: `${t.blockNumber || t.timestamp}-${i}`, timestamp: (t.timestamp || 0) * 1000, - price: t.shares > 0 ? Number(t.value) / Number(t.shares) : 0, + price: resolveMyriadPrice(t), amount: Number(t.shares || 0), side: t.action === 'buy' ? 'buy' as const : 'sell' as const, })); @@ -256,7 +257,7 @@ export class MyriadExchange extends PredictionMarketExchange { outcomeLabel: pos.outcomeTitle || `Outcome ${pos.outcomeId}`, size: Number(pos.shares || 0), entryPrice: Number(pos.price || 0), - currentPrice: Number(pos.value || 0) / Math.max(Number(pos.shares || 1), 1), + currentPrice: resolveMyriadPrice(pos), unrealizedPnL: Number(pos.profit || 0), })); } diff --git a/core/src/exchanges/myriad/price.test.ts b/core/src/exchanges/myriad/price.test.ts new file mode 100644 index 0000000..85b31be --- /dev/null +++ b/core/src/exchanges/myriad/price.test.ts @@ -0,0 +1,19 @@ +import { resolveMyriadPrice } from "./price"; + +describe("resolveMyriadPrice", () => { + test("divides value by shares", () => { + expect(resolveMyriadPrice({ value: 100, shares: 4 })).toBe(25); + }); + + test("treats missing shares as 1", () => { + expect(resolveMyriadPrice({ value: 50 })).toBe(50); + }); + + test("treats zero shares as 1 to avoid division by zero", () => { + expect(resolveMyriadPrice({ value: 80, shares: 0 })).toBe(80); + }); + + test("treats missing value as 0", () => { + expect(resolveMyriadPrice({ shares: 5 })).toBe(0); + }); +}); diff --git a/core/src/exchanges/myriad/price.ts b/core/src/exchanges/myriad/price.ts new file mode 100644 index 0000000..676e440 --- /dev/null +++ b/core/src/exchanges/myriad/price.ts @@ -0,0 +1,4 @@ +export function resolveMyriadPrice(event: any): number { + const shares = Math.max(Number(event.shares || 1), 1); + return Number(event.value || 0) / shares; +} diff --git a/examples/test-load-markets.ts b/examples/test-load-markets.ts deleted file mode 100644 index 1f8c8e4..0000000 --- a/examples/test-load-markets.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Polymarket, Kalshi, Limitless, Myriad, Probable, Baozi } from '../core/src/index'; -import * as dotenv from 'dotenv'; -import path from 'path'; - -// Load .env -dotenv.config({ path: path.join(__dirname, '../.env') }); - -const exchanges: any[] = []; - -// Initialize exchanges we have keys for (or just public if keys not needed for fetchMarkets) - -// 1. Polymarket -exchanges.push(new Polymarket({ - privateKey: process.env.POLYMARKET_PRIVATE_KEY -})); - -// 2. Kalshi -exchanges.push(new Kalshi({ - apiKey: process.env.KALSHI_API_KEY, - privateKey: process.env.KALSHI_PRIVATE_KEY -})); - -// 3. Limitless -exchanges.push(new Limitless({ - apiKey: process.env.LIMITLESS_API_KEY, - privateKey: process.env.LIMITLESS_PRIVATE_KEY -})); - -// 4. Myriad -exchanges.push(new Myriad({ - apiKey: process.env.MYRIAD_PROD // Assuming prod key -})); - -// 5. Probable -exchanges.push(new Probable()); - -// 6. Baozi -exchanges.push(new Baozi({ - privateKey: process.env.BAOZI_PRIVATE_KEY -})); - - -async function testLoadMarkets() { - console.log('Testing loadMarkets on all exchanges...'); - - for (const exchange of exchanges) { - console.log(`\n---------------------------------------------------------`); - console.log(`Testing ${exchange.name}...`); - - try { - console.log(`[${exchange.name}] .loadedMarkets before: ${exchange.loadedMarkets}`); - const start = Date.now(); - - // 1. Load Markets - const markets = await exchange.loadMarkets(); - const duration = Date.now() - start; - - const count = Object.keys(markets).length; - const slugCount = Object.keys(exchange.marketsBySlug).length; - - console.log(`[${exchange.name}] Loaded ${count} markets in ${duration}ms`); - console.log(`[${exchange.name}] .loadedMarkets after: ${exchange.loadedMarkets}`); - console.log(`[${exchange.name}] Cached by Slug: ${slugCount}`); - - if (count > 0) { - // Verify structure - const firstId = Object.keys(markets)[0]; - const firstMarket = markets[firstId]; - console.log(`[${exchange.name}] First Market: ${firstMarket.title} (ID: ${firstMarket.marketId})`); - - // 2. Test Cached fetchMarket (by ID) - const cachedStart = Date.now(); - const cachedMarket = await exchange.fetchMarket({ marketId: firstId }); - const cachedDuration = Date.now() - cachedStart; - - if (cachedMarket.marketId === firstId && cachedDuration < 10) { - console.log(`[${exchange.name}] ✅ Cached fetchMarket(ID) working (${cachedDuration}ms)`); - } else { - console.warn(`[${exchange.name}] ⚠️ Cached fetchMarket(ID) took ${cachedDuration}ms (expected <10ms)`); - } - - // 3. Test Cached fetchMarket (by Slug) - if supported - if (firstMarket.slug && slugCount > 0) { - // Note: Some exchanges like Polymarket might not populate slugs consistently or use them for lookup - // But if it's in the cache, it should work. - try { - const slugStart = Date.now(); - const slugMarket = await exchange.fetchMarket({ slug: firstMarket.slug }); - const slugDuration = Date.now() - slugStart; - console.log(`[${exchange.name}] ✅ Cached fetchMarket(Slug) working (${slugDuration}ms)`); - } catch (e) { - console.warn(`[${exchange.name}] ⚠️ fetchMarket(Slug) failed: ${e.message}`); - } - } - } else { - console.warn(`[${exchange.name}] ⚠️ Returns 0 markets. Is the API down or keys invalid?`); - } - - } catch (error: any) { - console.error(`[${exchange.name}] ❌ FAILED: ${error.message}`); - } - } -} - -testLoadMarkets(); diff --git a/sdks/python/pmxt/client.py b/sdks/python/pmxt/client.py index de3244e..6db3848 100644 --- a/sdks/python/pmxt/client.py +++ b/sdks/python/pmxt/client.py @@ -1452,7 +1452,7 @@ def fetch_my_trades( self, outcome_id: Optional[str] = None, market_id: Optional[str] = None, - since: Optional[Any] = None, + since: Optional[Union[datetime, int, float, str]] = None, limit: Optional[int] = None, cursor: Optional[str] = None, ) -> List[UserTrade]: @@ -1489,8 +1489,8 @@ def fetch_my_trades( def fetch_closed_orders( self, market_id: Optional[str] = None, - since: Optional[Any] = None, - until: Optional[Any] = None, + since: Optional[Union[datetime, int, float, str]] = None, + until: Optional[Union[datetime, int, float, str]] = None, limit: Optional[int] = None, ) -> List[Order]: """ @@ -1523,8 +1523,8 @@ def fetch_closed_orders( def fetch_all_orders( self, market_id: Optional[str] = None, - since: Optional[Any] = None, - until: Optional[Any] = None, + since: Optional[Union[datetime, int, float, str]] = None, + until: Optional[Union[datetime, int, float, str]] = None, limit: Optional[int] = None, ) -> List[Order]: """ diff --git a/sdks/typescript/pmxt/args.ts b/sdks/typescript/pmxt/args.ts new file mode 100644 index 0000000..cd56982 --- /dev/null +++ b/sdks/typescript/pmxt/args.ts @@ -0,0 +1,3 @@ +export function buildArgsWithOptionalOptions(primary?: any): any[] { + return primary !== undefined ? [primary] : []; +} diff --git a/sdks/typescript/pmxt/client.ts b/sdks/typescript/pmxt/client.ts index df892f6..ffa987d 100644 --- a/sdks/typescript/pmxt/client.ts +++ b/sdks/typescript/pmxt/client.ts @@ -37,6 +37,7 @@ import { } from "./models.js"; import { ServerManager } from "./server-manager.js"; +import { buildArgsWithOptionalOptions } from "./args.js"; // Converter functions function convertMarket(raw: any): UnifiedMarket { @@ -382,8 +383,7 @@ export abstract class Exchange { async fetchMarkets(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchMarkets`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -404,8 +404,7 @@ export abstract class Exchange { async fetchMarketsPaginated(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchMarketsPaginated`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -430,8 +429,7 @@ export abstract class Exchange { async fetchEvents(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchEvents`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -452,8 +450,7 @@ export abstract class Exchange { async fetchMarket(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchMarket`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -474,8 +471,7 @@ export abstract class Exchange { async fetchEvent(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchEvent`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -562,8 +558,7 @@ export abstract class Exchange { async fetchOpenOrders(marketId?: string): Promise { await this.initPromise; try { - const args: any[] = []; - if (marketId !== undefined) args.push(marketId); + const args = buildArgsWithOptionalOptions(marketId); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchOpenOrders`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -584,8 +579,7 @@ export abstract class Exchange { async fetchMyTrades(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchMyTrades`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -606,8 +600,7 @@ export abstract class Exchange { async fetchClosedOrders(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchClosedOrders`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, @@ -628,8 +621,7 @@ export abstract class Exchange { async fetchAllOrders(params?: any): Promise { await this.initPromise; try { - const args: any[] = []; - if (params !== undefined) args.push(params); + const args = buildArgsWithOptionalOptions(params); const response = await fetch(`${this.config.basePath}/api/${this.exchangeName}/fetchAllOrders`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.config.headers }, diff --git a/sdks/typescript/tests/client-args.test.ts b/sdks/typescript/tests/client-args.test.ts new file mode 100644 index 0000000..08df6c0 --- /dev/null +++ b/sdks/typescript/tests/client-args.test.ts @@ -0,0 +1,28 @@ +import { describe, test, expect } from "vitest"; +import { buildArgsWithOptionalOptions } from "../pmxt/args.js"; + +describe("buildArgsWithOptionalOptions", () => { + test("returns empty array when primary is undefined", () => { + expect(buildArgsWithOptionalOptions(undefined)).toEqual([]); + }); + + test("returns empty array when called with no arguments", () => { + expect(buildArgsWithOptionalOptions()).toEqual([]); + }); + + test("wraps a defined primary value in an array", () => { + expect(buildArgsWithOptionalOptions({ limit: 10 })).toEqual([{ limit: 10 }]); + }); + + test("treats null as a defined value", () => { + expect(buildArgsWithOptionalOptions(null)).toEqual([null]); + }); + + test("treats 0 as a defined value", () => { + expect(buildArgsWithOptionalOptions(0)).toEqual([0]); + }); + + test("treats empty string as a defined value", () => { + expect(buildArgsWithOptionalOptions("")).toEqual([""]); + }); +});