Skip to content
Closed
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-07-10T06:40:05.097Z. Do not edit manually.",
"_generated": "Auto-generated by extract-jsdoc.js on 2026-07-14T15:53:42.233Z. Do not edit manually.",
"methods": {
"has": {
"summary": "HTTP verb for the endpoint (e.g. GET, POST). */",
Expand Down
7 changes: 6 additions & 1 deletion sdks/python/pmxt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""

from typing import Any, Dict, List

from .router import Router, SqlResult, SqlMeta, SqlColumn
from .client import Exchange
from .constants import ENV, ENV_BASE_URL, ENV_API_KEY
from ._exchanges import Polymarket, Limitless, Kalshi, KalshiDemo, Probable, Baozi, Myriad, Opinion, Metaculus, Smarkets, PolymarketUS, Polymarket_us, Hyperliquid, GeminiTitan, SuiBets, Suibets, Rain, Hunch, Mock, Router
Expand Down Expand Up @@ -274,4 +274,9 @@ def restart_server() -> None:
"OrderSide",
"OrderType",
"CandleInterval",

'Router',
'SqlResult',
'SqlMeta',
'SqlColumn',
]
46 changes: 45 additions & 1 deletion sdks/python/pmxt/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from datetime import date, datetime
from typing import Any, Dict, List, Optional, Union

from .client import Exchange, _convert_market, _convert_event
from .client import Exchange, _convert_market, _convert_event, _convert_order_book
from .models import (
MatchResult,
EventMatchResult,
Expand Down Expand Up @@ -236,6 +236,50 @@ def fetch_matches(
include_prices=include_prices,
)


def fetch_order_book(
self,
outcome_id: str,
limit: Optional[int] = None,
params: Optional[Dict[str, Any]] = None,
) -> OrderBook:
"""
Fetch a merged order book across all matched venues for a given outcome.

Finds identity matches for the outcome across all configured exchanges,
fetches each venue's order book in parallel, and merges bid/ask levels
by summing size at each price point.

Args:
outcome_id: The outcome ID to fetch order books for
limit: Maximum number of price levels per side (default: 20)
params: Additional parameters (e.g., exchange filters)

Returns:
OrderBook: A merged OrderBook with bids sorted descending, asks sorted ascending

Example:
```python
router = Router(pmxt_api_key="...")
order_book = router.fetch_order_book("outcome-123", 10)
print("Best bid:", order_book.bids[0])
print("Best ask:", order_book.asks[0])
```
"""
args = [outcome_id]
if limit is not None:
args.append(limit)
if params is not None:
args.append(params)

raw = self._call_method("fetchOrderBook", args)
if raw is None:
return OrderBook(bids=[], asks=[])

return _convert_order_book(raw)



def fetch_event_matches(
self,
event: Optional[UnifiedEvent] = None,
Expand Down
10 changes: 10 additions & 0 deletions sdks/python/tests/test_router_fetch_orderbook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import pytest
from pmxt import Router

def test_fetch_order_book():
router = Router(base_url='http://localhost:3847')
result = router.fetch_order_book('test-outcome-123', 10)

assert result is not None
assert hasattr(result, 'bids')
assert hasattr(result, 'asks')
2 changes: 1 addition & 1 deletion sdks/typescript/pmxt/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ function convertCandle(raw: any): PriceCandle {
return { ...raw };
}

function convertOrderBook(raw: any): OrderBook {
export function convertOrderBook(raw: any): OrderBook {
return {
...raw,
bids: (raw.bids || []).map((b: any) => ({ ...b })),
Expand Down
122 changes: 121 additions & 1 deletion sdks/typescript/pmxt/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* every venue PMXT supports. Only requires a PMXT API key.
*/

import { Exchange, ExchangeOptions } from "./client.js";
import { Exchange, ExchangeOptions,
convertOrderBook, } from "./client.js";
import { logger } from "./logger.js";
import {
MatchResult,
Expand All @@ -20,8 +21,25 @@ import {
ArbitrageOpportunity,
UnifiedMarket,
UnifiedEvent,
OrderBook
} from "./models.js";

export interface SqlColumn {
name: string;
type: string;
}

export interface SqlMeta {
columns: SqlColumn[];
rows: number;
}

export interface SqlResult {
data: Record<string, unknown>[];
meta: SqlMeta;
}


function withQuestionAlias<T extends UnifiedMarket>(market: T): T {
Object.defineProperty(market, 'question', {
get() { return this.title; },
Expand Down Expand Up @@ -376,6 +394,46 @@ export class Router extends Exchange {
}
}

/**
* Fetch a merged order book across all matched venues for a given outcome.
*
* Finds identity matches for the outcome across all configured exchanges,
* fetches each venue's order book in parallel, and merges bid/ask levels
* by summing size at each price point.
*
* @param outcomeId - The outcome ID to fetch order books for
* @param limit - Maximum number of price levels per side (default: 20)
* @param params - Additional parameters (e.g., exchange filters)
* @returns A merged OrderBook with bids sorted descending, asks sorted ascending
*
* @example
* ```typescript
* const router = new Router({ pmxtApiKey: "..." });
* const orderBook = await router.fetchOrderBook("outcome-123", 10);
* console.log("Best bid:", orderBook.bids[0]);
* ```
*/
async fetchOrderBook(
outcomeId: string,
limit?: number,
params?: Record<string, any>
): Promise<OrderBook> {
await this.initPromise;

try {
const query: Record<string, unknown> = { outcomeId };
if (limit !== undefined) query.limit = limit;
if (params !== undefined) query.params = params;

const json = await this.sidecarReadRequest('fetchOrderBook', query, [outcomeId, limit, params]);
const data = this.handleResponse(json);
return convertOrderBook(data);
} catch (error) {
if (error instanceof Error) throw error;
throw new Error(`Failed to fetchOrderBook: ${error}`);
}
}

/**
* Fetch connected clusters of semantically matched markets across venues.
*
Expand Down Expand Up @@ -442,6 +500,68 @@ export class Router extends Exchange {
}
}


// ------------------------------------------------------------------
// SQL Query
// ------------------------------------------------------------------

/**
* Execute a SQL query against the ClickHouse database.
*
* @param query - SQL query string
* @returns Query results with data and metadata
*
* @example
* ```typescript
* const router = new Router({ pmxtApiKey: "..." });
* const result = await router.sql('SELECT * FROM markets LIMIT 10');
* console.log(result.data);
* ```
*/
async sql(query: string): Promise<SqlResult> {
await this.initPromise;

try {
const url = `${this.resolveBaseUrl()}/v0/sql`;
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
...this.getAuthHeaders(),
};

const response = await this.fetchWithRetry(
url,
{
method: 'POST',
headers,
body: JSON.stringify({ query }),
}
);

if (!response.ok) {
const error = await response.text();
throw new Error(`SQL query failed: ${error}`);
}

const data = await response.json();

const resultData = data.data || data;
const resultMeta = data.meta || { columns: [], rows: 0 };

return {
data: Array.isArray(resultData) ? resultData : [],
meta: {
columns: resultMeta.columns || [],
rows: resultMeta.rows || (Array.isArray(resultData) ? resultData.length : 0)
}
};
} catch (error) {
if (error instanceof Error) throw error;
throw new Error(`SQL query failed: ${error}`);
}
}


// ------------------------------------------------------------------
// Price comparison
// ------------------------------------------------------------------
Expand Down
15 changes: 15 additions & 0 deletions sdks/typescript/tests/router-fetch-orderbook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Router } from '../pmxt/router';
import { OrderBook } from '../pmxt/models';

describe('Router.fetchOrderBook', () => {
it('should fetch a merged order book', async () => {
const router = new Router({ baseUrl: 'http://localhost:3847' });
const result = await router.fetchOrderBook('test-outcome-123', 10);

expect(result).toBeDefined();
expect(result.bids).toBeDefined();
expect(result.asks).toBeDefined();
expect(Array.isArray(result.bids)).toBe(true);
expect(Array.isArray(result.asks)).toBe(true);
});
});
Loading