Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/api-doc-config.generated.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_generated": "Auto-generated by extract-jsdoc.js on 2026-06-08T09:54:03.295Z. Do not edit manually.",
"_generated": "Auto-generated by extract-jsdoc.js on 2026-06-08T09:56:18.619Z. Do not edit manually.",
"methods": {
"has": {
"summary": "HTTP verb for the endpoint (e.g. GET, POST). */",
Expand Down
13 changes: 10 additions & 3 deletions core/src/exchanges/gemini-titan/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,19 @@ export class GeminiNormalizer implements IExchangeNormalizer<GeminiRawEvent, Gem
// Extract prices
const bestBid = contract.prices?.bestBid ? parseFloat(contract.prices.bestBid) : 0.5;
const bestAsk = contract.prices?.bestAsk ? parseFloat(contract.prices.bestAsk) : 0.5;
const buyYes = contract.prices?.buy?.yes ? parseFloat(contract.prices.buy.yes) : undefined;
const sellYes = contract.prices?.sell?.yes ? parseFloat(contract.prices.sell.yes) : undefined;
const buyNo = contract.prices?.buy?.no ? parseFloat(contract.prices.buy.no) : undefined;
const sellNo = contract.prices?.sell?.no ? parseFloat(contract.prices.sell.no) : undefined;
const lastPrice = contract.prices?.lastTradePrice
? parseFloat(contract.prices.lastTradePrice)
: (bestBid + bestAsk) / 2;

const yesPrice = roundPrice(Math.max(0, Math.min(1, lastPrice)));
const noPrice = roundPrice(Math.max(0, Math.min(1, 1 - yesPrice)));
const yesPriceSource = buyYes ?? sellYes ?? lastPrice;
const noPriceSource = buyNo ?? sellNo ?? (1 - yesPriceSource);

const yesPrice = roundPrice(Math.max(0, Math.min(1, yesPriceSource)));
const noPrice = roundPrice(Math.max(0, Math.min(1, noPriceSource)));

const outcomes: MarketOutcome[] = [
{
Expand Down Expand Up @@ -258,7 +265,7 @@ export class GeminiNormalizer implements IExchangeNormalizer<GeminiRawEvent, Gem
outcomes,
resolutionDate,
volume24h: 0,
liquidity: event.liquidity ? parseFloat(event.liquidity) : 0,
liquidity: event.liquidity ? parseFloat(event.liquidity) : (event.volume24h ? parseFloat(event.volume24h) : (event.volume ? parseFloat(event.volume) : 0)),
url: buildExchangeUrl(event.ticker),
category: event.category,
tags,
Expand Down
2 changes: 2 additions & 0 deletions core/src/exchanges/hyperliquid/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,13 @@ export class HyperliquidNormalizer implements IExchangeNormalizer<HyperliquidRaw
const bids = rawBids.map(level => ({
price: parseFloat(level.px),
size: parseFloat(level.sz),
orderCount: level.n,
}));

const asks = rawAsks.map(level => ({
price: parseFloat(level.px),
size: parseFloat(level.sz),
orderCount: level.n,
}));

return {
Expand Down
2 changes: 2 additions & 0 deletions core/src/exchanges/kalshi/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface KalshiRawMarket {
last_price_dollars?: string;
yes_ask_dollars?: string;
yes_bid_dollars?: string;
yes_ask_size_fp?: string;
yes_bid_size_fp?: string;
rules_primary?: string;
rules_secondary?: string;
expiration_time: string;
Expand Down
7 changes: 6 additions & 1 deletion core/src/exchanges/kalshi/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ export class KalshiNormalizer implements IExchangeNormalizer<KalshiRawEvent, Kal
sourceMetadata: buildSourceMetadata(
market as unknown as Record<string, unknown>,
KALSHI_PROMOTED_MARKET_KEYS,
{ series_ticker: event.series_ticker, series_title: event.series_title },
{
yes_ask_size_fp: market.yes_ask_size_fp,
yes_bid_size_fp: market.yes_bid_size_fp,
series_ticker: event.series_ticker,
series_title: event.series_title,
},
),
} as UnifiedMarket;

Expand Down
4 changes: 4 additions & 0 deletions core/src/exchanges/limitless/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export interface LimitlessRawMarket {
description?: string;
tokens?: Record<string, string>;
prices?: number[];
tradePrices?: {
buy?: { market?: number[]; limit?: number[] };
sell?: { market?: number[]; limit?: number[] };
};
expirationTimestamp?: string;
volumeFormatted?: number;
volume?: number;
Expand Down
6 changes: 3 additions & 3 deletions core/src/exchanges/limitless/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ export function mapMarketToUnified(market: any, context: LimitlessMarketContext
throw new Error(`[limitless] Market "${market.slug}" is missing token addresses`);
}

const prices = Array.isArray(market.prices) ? market.prices : [];
const yesPrice = prices[0] || 0;
const noPrice = prices[1] || 0;
const legacyPrices = Array.isArray(market.prices) ? market.prices : [];
const yesPrice = market.tradePrices?.buy?.market?.[0] ?? legacyPrices[0] ?? 0.5;
const noPrice = market.tradePrices?.sell?.market?.[0] ?? legacyPrices[1] ?? Math.max(0, Math.min(1, 1 - yesPrice));
const yesLabel = hasParentContext ? rawTitle : 'Yes';
const noLabel = hasParentContext ? `Not ${rawTitle}` : 'No';

Expand Down
2 changes: 1 addition & 1 deletion core/src/exchanges/limitless/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export class LimitlessWebSocket {
await this.client.connect();
}

await this.client.subscribe('subscribe_order_events');
await this.client.subscribe('subscribe_order_events' as any);
await this.client.subscribe('subscribe_positions' as any);
this.client.on('positions', callback);
}
Expand Down
2 changes: 2 additions & 0 deletions core/src/server/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3199,6 +3199,8 @@ components:
size:
type: number
description: contracts/shares
orderCount:
type: number
required:
- price
- size
Expand Down
1 change: 1 addition & 0 deletions core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export interface PriceCandle {
export interface OrderLevel {
price: number; // 0.0 to 1.0 (probability)
size: number; // contracts/shares
orderCount?: number;
}

export interface OrderBook {
Expand Down
6 changes: 4 additions & 2 deletions core/test/normalizers/exchange-normalizers-3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,19 +878,21 @@ describe('HyperliquidNormalizer', () => {
expect(book.asks[0].price).toBeLessThanOrEqual(book.asks[1].price);
});

test('bid prices and sizes are numbers', () => {
test('bid prices, sizes, and order counts are numbers', () => {
const book = normalizer.normalizeOrderBook(rawBook, 'hl-outcome-42');
for (const b of book.bids) {
expect(typeof b.price).toBe('number');
expect(typeof b.size).toBe('number');
expect(typeof b.orderCount).toBe('number');
}
});

test('ask prices and sizes are numbers', () => {
test('ask prices, sizes, and order counts are numbers', () => {
const book = normalizer.normalizeOrderBook(rawBook, 'hl-outcome-42');
for (const a of book.asks) {
expect(typeof a.price).toBe('number');
expect(typeof a.size).toBe('number');
expect(typeof a.orderCount).toBe('number');
}
});

Expand Down
19 changes: 15 additions & 4 deletions core/test/normalizers/exchange-normalizers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,8 @@ describe('KalshiNormalizer', () => {
last_price_dollars: '0.4540',
yes_ask_dollars: '0.4600',
yes_bid_dollars: '0.4500',
yes_ask_size_fp: '125.00',
yes_bid_size_fp: '90.00',
previous_price_dollars: '0.4200',
rules_primary: 'This market resolves YES if the Fed Funds Rate is above 4.75% on Jan 29, 2025.',
rules_secondary: 'Data source: Federal Reserve',
Expand Down Expand Up @@ -509,6 +511,11 @@ describe('KalshiNormalizer', () => {
expect(market.liquidity).toBe(5000);
});

it('surfaces top-of-book depth sizes in sourceMetadata', () => {
expect((market.sourceMetadata as any).yes_ask_size_fp).toBe('125.00');
expect((market.sourceMetadata as any).yes_bid_size_fp).toBe('90.00');
});

it('maps url to kalshi events URL', () => {
expect(market.url).toBe('https://kalshi.com/events/FED-25JAN29');
});
Expand Down Expand Up @@ -800,6 +807,10 @@ describe('LimitlessNormalizer', () => {
no: 'token-eth-flip-no',
},
prices: [0.08, 0.92],
tradePrices: {
buy: { market: [0.11] },
sell: { market: [0.89] },
},
expirationTimestamp: '2025-12-31T23:59:59Z',
volumeFormatted: 75000,
volume: 1500000,
Expand Down Expand Up @@ -863,14 +874,14 @@ describe('LimitlessNormalizer', () => {
expect(market.outcomes[1].label).toBe('Not Will ETH flip BTC in 2025?');
});

it('maps yes price from prices[0] as a number', () => {
it('maps yes price from directional tradePrices when available', () => {
expect(typeof market.outcomes[0].price).toBe('number');
expect(market.outcomes[0].price).toBeCloseTo(0.08);
expect(market.outcomes[0].price).toBeCloseTo(0.11);
});

it('maps no price from prices[1] as a number', () => {
it('maps no price from directional tradePrices when available', () => {
expect(typeof market.outcomes[1].price).toBe('number');
expect(market.outcomes[1].price).toBeCloseTo(0.92);
expect(market.outcomes[1].price).toBeCloseTo(0.89);
});

it('prices sum to 1.0', () => {
Expand Down
66 changes: 66 additions & 0 deletions core/test/normalizers/gemini-titan-normalizer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { GeminiNormalizer } from '../../src/exchanges/gemini-titan/normalizer';

describe('GeminiTitanNormalizer', () => {
const normalizer = new GeminiNormalizer();

const rawEvent: any = {
id: 'evt-1',
title: 'Gemini prediction market event',
slug: 'gemini-prediction-market-event',
description: 'Event-level description fallback',
ticker: 'GEM-1',
status: 'active',
type: 'prediction-market',
liquidity: undefined,
volume24h: '1234.5',
volume: '9999.9',
contracts: [
{
id: 'contract-1',
label: 'BTC above $100k',
description: {
nodeType: 'document',
content: [
{
nodeType: 'paragraph',
content: [
{ nodeType: 'text', value: 'Contract rich-text description' },
],
},
],
},
ticker: 'GEM-1-A',
instrumentSymbol: 'GEM-1-A',
status: 'active',
marketState: 'open',
prices: {
buy: { yes: '0.71', no: '0.29' },
sell: { yes: '0.69', no: '0.31' },
bestBid: '0.60',
bestAsk: '0.40',
lastTradePrice: '0.55',
},
expiryDate: '2026-06-30T23:59:00Z',
},
],
};

test('extracts rich-text contract descriptions', () => {
const market = normalizer.normalizeMarket(rawEvent);
expect(market).not.toBeNull();
expect(market!.description).toBe('Contract rich-text description');
});

test('prefers directional buy/sell prices when present', () => {
const market = normalizer.normalizeMarket(rawEvent);
expect(market).not.toBeNull();
expect(market!.outcomes[0].price).toBeCloseTo(0.71);
expect(market!.outcomes[1].price).toBeCloseTo(0.29);
});

test('falls back to volume fields when liquidity is absent', () => {
const market = normalizer.normalizeMarket(rawEvent);
expect(market).not.toBeNull();
expect(market!.liquidity).toBeCloseTo(1234.5);
});
});
3 changes: 3 additions & 0 deletions docs/api-reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9790,6 +9790,9 @@
"size": {
"type": "number",
"description": "contracts/shares"
},
"orderCount": {
"type": "number"
}
},
"required": [
Expand Down
1 change: 1 addition & 0 deletions sdks/python/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,7 @@ datetime: str # ISO 8601 datetime string of the snapshot (CCXT-compatible).
class OrderLevel:
price: float # 0.0 to 1.0 (probability)
size: float # contracts/shares
order_count: float #
```

---
Expand Down
1 change: 1 addition & 0 deletions sdks/typescript/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,7 @@ datetime: string; // ISO 8601 datetime string of the snapshot (CCXT-compatible).
interface OrderLevel {
price: number; // 0.0 to 1.0 (probability)
size: number; // contracts/shares
orderCount: number; //
}
```

Expand Down
Loading