pmxtjs 2.43.25 → 2.44.1
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/esm/pmxt/client.d.ts +8 -0
- package/dist/esm/pmxt/client.js +23 -0
- package/dist/esm/pmxt/models.d.ts +90 -0
- package/dist/esm/pmxt/router.d.ts +15 -1
- package/dist/esm/pmxt/router.js +102 -0
- package/dist/pmxt/client.d.ts +8 -0
- package/dist/pmxt/client.js +23 -0
- package/dist/pmxt/models.d.ts +90 -0
- package/dist/pmxt/router.d.ts +15 -1
- package/dist/pmxt/router.js +102 -0
- package/generated/package.json +1 -1
- package/package.json +2 -2
- package/pmxt/client.ts +24 -0
- package/pmxt/models.ts +118 -0
- package/pmxt/router.ts +147 -0
|
@@ -157,6 +157,14 @@ export declare abstract class Exchange {
|
|
|
157
157
|
* never use the GET read path.
|
|
158
158
|
*/
|
|
159
159
|
protected sidecarPostRequest(methodName: string, args: unknown[]): Promise<any>;
|
|
160
|
+
/**
|
|
161
|
+
* Read a hosted catalog endpoint directly.
|
|
162
|
+
*
|
|
163
|
+
* Hosted-only Router APIs such as matched clusters are not part of the
|
|
164
|
+
* core sidecar method namespace. They live under /v0 and return their own
|
|
165
|
+
* response envelopes, so callers intentionally receive the raw JSON body.
|
|
166
|
+
*/
|
|
167
|
+
protected catalogReadRequest(path: string, query?: Record<string, unknown>): Promise<any>;
|
|
160
168
|
loadMarkets(reload?: boolean): Promise<Record<string, UnifiedMarket>>;
|
|
161
169
|
fetchMarkets(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
|
|
162
170
|
fetchMarketsPaginated(params?: any): Promise<PaginatedMarketsResult>;
|
package/dist/esm/pmxt/client.js
CHANGED
|
@@ -484,6 +484,29 @@ export class Exchange {
|
|
|
484
484
|
}
|
|
485
485
|
return response.json();
|
|
486
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Read a hosted catalog endpoint directly.
|
|
489
|
+
*
|
|
490
|
+
* Hosted-only Router APIs such as matched clusters are not part of the
|
|
491
|
+
* core sidecar method namespace. They live under /v0 and return their own
|
|
492
|
+
* response envelopes, so callers intentionally receive the raw JSON body.
|
|
493
|
+
*/
|
|
494
|
+
async catalogReadRequest(path, query = {}) {
|
|
495
|
+
const qs = buildSidecarQueryString(query);
|
|
496
|
+
const url = `${this.resolveBaseUrl()}${path}${qs ? `?${qs}` : ''}`;
|
|
497
|
+
const response = await this.fetchWithRetry(url, {
|
|
498
|
+
method: 'GET',
|
|
499
|
+
headers: this.getAuthHeaders(),
|
|
500
|
+
});
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
const body = await response.json().catch(() => ({}));
|
|
503
|
+
if (body.error && typeof body.error === "object") {
|
|
504
|
+
throw fromServerError(body.error);
|
|
505
|
+
}
|
|
506
|
+
throw new PmxtError(body.error?.message || response.statusText);
|
|
507
|
+
}
|
|
508
|
+
return response.json();
|
|
509
|
+
}
|
|
487
510
|
// BEGIN GENERATED METHODS
|
|
488
511
|
async loadMarkets(reload = false) {
|
|
489
512
|
await this.initPromise;
|
|
@@ -639,6 +639,96 @@ export interface EventMatchResult extends Readonly<UnifiedEvent> {
|
|
|
639
639
|
/** Cross-venue market matches within this event. */
|
|
640
640
|
marketMatches: MatchResult[];
|
|
641
641
|
}
|
|
642
|
+
export type MatchedClusterSort = 'volume' | 'confidence';
|
|
643
|
+
/** Shared filters for matched market/event cluster discovery. */
|
|
644
|
+
export interface MatchedClusterFilterParams {
|
|
645
|
+
/** Text search across cluster titles. */
|
|
646
|
+
query?: string;
|
|
647
|
+
/** Filter both sides of matched edges by category. */
|
|
648
|
+
category?: string;
|
|
649
|
+
/** Comma-separated relation filter. */
|
|
650
|
+
relations?: MatchRelation | MatchRelation[] | string;
|
|
651
|
+
/** Single relation filter. Alias for relations. */
|
|
652
|
+
relation?: MatchRelation;
|
|
653
|
+
/** Minimum match confidence score (0.0 to 1.0). */
|
|
654
|
+
minConfidence?: number;
|
|
655
|
+
/** Venue allow-list. */
|
|
656
|
+
venues?: string | string[];
|
|
657
|
+
/** Venue deny-list. */
|
|
658
|
+
excludeVenues?: string | string[];
|
|
659
|
+
/** Minimum number of venues required in a cluster. */
|
|
660
|
+
minVenues?: number;
|
|
661
|
+
/** Require live orderbook coverage on matched edges. */
|
|
662
|
+
withOrderbook?: boolean;
|
|
663
|
+
/** Only include matches updated after this timestamp. */
|
|
664
|
+
updatedSince?: string | Date;
|
|
665
|
+
/** Include the pairwise match edges used to build each cluster. */
|
|
666
|
+
includeRawMatches?: boolean;
|
|
667
|
+
/** Cluster sort order. */
|
|
668
|
+
sort?: MatchedClusterSort;
|
|
669
|
+
/** Maximum clusters to return. */
|
|
670
|
+
limit?: number;
|
|
671
|
+
/** Pagination offset. */
|
|
672
|
+
offset?: number;
|
|
673
|
+
/** Maximum pairwise edges to scan before clustering. */
|
|
674
|
+
edgeLimit?: number;
|
|
675
|
+
}
|
|
676
|
+
/** Parameters for fetching matched market clusters. */
|
|
677
|
+
export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterParams {
|
|
678
|
+
/** Pass a UnifiedMarket directly instead of marketId/slug/url. */
|
|
679
|
+
market?: UnifiedMarket;
|
|
680
|
+
/** Anchor the response to a specific market ID. */
|
|
681
|
+
marketId?: string;
|
|
682
|
+
/** Anchor the response to a specific market slug. */
|
|
683
|
+
slug?: string;
|
|
684
|
+
/** Anchor the response to a specific market URL. */
|
|
685
|
+
url?: string;
|
|
686
|
+
}
|
|
687
|
+
/** Parameters for fetching matched event clusters. */
|
|
688
|
+
export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
|
|
689
|
+
/** Pass a UnifiedEvent directly instead of eventId/slug/url. */
|
|
690
|
+
event?: UnifiedEvent;
|
|
691
|
+
/** Anchor the response to a specific event ID. */
|
|
692
|
+
eventId?: string;
|
|
693
|
+
/** Anchor the response to a specific event slug. */
|
|
694
|
+
slug?: string;
|
|
695
|
+
/** Anchor the response to a specific event URL. */
|
|
696
|
+
url?: string;
|
|
697
|
+
}
|
|
698
|
+
/** Pairwise edge used to build a matched market cluster. */
|
|
699
|
+
export interface MatchedMarketClusterEdge {
|
|
700
|
+
marketAId: string;
|
|
701
|
+
marketBId: string;
|
|
702
|
+
relation: MatchRelation;
|
|
703
|
+
confidence: number;
|
|
704
|
+
}
|
|
705
|
+
/** Pairwise edge used to build a matched event cluster. */
|
|
706
|
+
export interface MatchedEventClusterEdge extends MatchedMarketClusterEdge {
|
|
707
|
+
eventAId: string;
|
|
708
|
+
eventBId: string;
|
|
709
|
+
}
|
|
710
|
+
/** Connected cluster of semantically matched markets across venues. */
|
|
711
|
+
export interface MatchedMarketCluster {
|
|
712
|
+
clusterId: string;
|
|
713
|
+
canonicalTitle: string | null;
|
|
714
|
+
category?: string | null;
|
|
715
|
+
relations: MatchRelation[];
|
|
716
|
+
confidence: number;
|
|
717
|
+
volume24h: number;
|
|
718
|
+
markets: UnifiedMarket[];
|
|
719
|
+
rawMatches?: MatchedMarketClusterEdge[];
|
|
720
|
+
}
|
|
721
|
+
/** Connected cluster of semantically matched events across venues. */
|
|
722
|
+
export interface MatchedEventCluster {
|
|
723
|
+
clusterId: string;
|
|
724
|
+
canonicalTitle: string | null;
|
|
725
|
+
category?: string | null;
|
|
726
|
+
relations: MatchRelation[];
|
|
727
|
+
confidence: number;
|
|
728
|
+
volume24h: number;
|
|
729
|
+
events: UnifiedEvent[];
|
|
730
|
+
rawMatches?: MatchedEventClusterEdge[];
|
|
731
|
+
}
|
|
642
732
|
/** Side-by-side price comparison for a matched market. */
|
|
643
733
|
export interface PriceComparison {
|
|
644
734
|
/** The matched market. */
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* every venue PMXT supports. Only requires a PMXT API key.
|
|
6
6
|
*/
|
|
7
7
|
import { Exchange } from "./client.js";
|
|
8
|
-
import { MatchResult, MatchRelation, EventMatchResult, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
|
|
8
|
+
import { MatchResult, MatchRelation, EventMatchResult, FetchMatchedEventClustersParams, FetchMatchedMarketClustersParams, MatchedEventCluster, MatchedMarketCluster, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
|
|
9
9
|
/** Options for creating a Router. */
|
|
10
10
|
export interface RouterOptions {
|
|
11
11
|
/** PMXT API key (required for hosted mode). */
|
|
@@ -78,6 +78,20 @@ export declare class Router extends Exchange {
|
|
|
78
78
|
limit?: number;
|
|
79
79
|
includePrices?: boolean;
|
|
80
80
|
}): Promise<EventMatchResult[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Fetch connected clusters of semantically matched markets across venues.
|
|
83
|
+
*
|
|
84
|
+
* @param marketOrParams - A UnifiedMarket, or an options object.
|
|
85
|
+
*/
|
|
86
|
+
fetchMatchedMarketClusters(market: UnifiedMarket): Promise<MatchedMarketCluster[]>;
|
|
87
|
+
fetchMatchedMarketClusters(params?: FetchMatchedMarketClustersParams): Promise<MatchedMarketCluster[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Fetch connected clusters of semantically matched events across venues.
|
|
90
|
+
*
|
|
91
|
+
* @param eventOrParams - A UnifiedEvent, or an options object.
|
|
92
|
+
*/
|
|
93
|
+
fetchMatchedEventClusters(event: UnifiedEvent): Promise<MatchedEventCluster[]>;
|
|
94
|
+
fetchMatchedEventClusters(params?: FetchMatchedEventClustersParams): Promise<MatchedEventCluster[]>;
|
|
81
95
|
/**
|
|
82
96
|
* Compare prices for the same market across venues.
|
|
83
97
|
*
|
package/dist/esm/pmxt/router.js
CHANGED
|
@@ -13,6 +13,8 @@ function convertMarket(raw) {
|
|
|
13
13
|
label: o.label,
|
|
14
14
|
price: o.price,
|
|
15
15
|
priceChange24h: o.priceChange24h,
|
|
16
|
+
bestBid: o.bestBid,
|
|
17
|
+
bestAsk: o.bestAsk,
|
|
16
18
|
metadata: o.metadata,
|
|
17
19
|
}));
|
|
18
20
|
const convertOutcome = (o) => o ? ({
|
|
@@ -21,6 +23,8 @@ function convertMarket(raw) {
|
|
|
21
23
|
label: o.label,
|
|
22
24
|
price: o.price,
|
|
23
25
|
priceChange24h: o.priceChange24h,
|
|
26
|
+
bestBid: o.bestBid,
|
|
27
|
+
bestAsk: o.bestAsk,
|
|
24
28
|
metadata: o.metadata,
|
|
25
29
|
}) : undefined;
|
|
26
30
|
return {
|
|
@@ -79,6 +83,58 @@ function parseMatchResult(raw) {
|
|
|
79
83
|
sourceMarket: raw.sourceMarket ? convertMarket(raw.sourceMarket) : undefined,
|
|
80
84
|
};
|
|
81
85
|
}
|
|
86
|
+
function normalizeQueryValue(value) {
|
|
87
|
+
if (value instanceof Date)
|
|
88
|
+
return value.toISOString();
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function appendQuery(query, key, value) {
|
|
92
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
93
|
+
query[key] = normalizeQueryValue(value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function addClusterFilters(query, params) {
|
|
97
|
+
appendQuery(query, 'query', params.query);
|
|
98
|
+
appendQuery(query, 'category', params.category);
|
|
99
|
+
appendQuery(query, 'relations', params.relations);
|
|
100
|
+
appendQuery(query, 'relation', params.relation);
|
|
101
|
+
appendQuery(query, 'minConfidence', params.minConfidence);
|
|
102
|
+
appendQuery(query, 'venues', params.venues);
|
|
103
|
+
appendQuery(query, 'excludeVenues', params.excludeVenues);
|
|
104
|
+
appendQuery(query, 'minVenues', params.minVenues);
|
|
105
|
+
appendQuery(query, 'withOrderbook', params.withOrderbook);
|
|
106
|
+
appendQuery(query, 'updatedSince', params.updatedSince);
|
|
107
|
+
appendQuery(query, 'includeRawMatches', params.includeRawMatches);
|
|
108
|
+
appendQuery(query, 'sort', params.sort);
|
|
109
|
+
appendQuery(query, 'limit', params.limit);
|
|
110
|
+
appendQuery(query, 'offset', params.offset);
|
|
111
|
+
appendQuery(query, 'edgeLimit', params.edgeLimit);
|
|
112
|
+
}
|
|
113
|
+
function extractCatalogArray(raw) {
|
|
114
|
+
if (Array.isArray(raw))
|
|
115
|
+
return raw;
|
|
116
|
+
if (Array.isArray(raw?.data))
|
|
117
|
+
return raw.data;
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function isUnifiedMarket(value) {
|
|
121
|
+
return Boolean(value && 'marketId' in value && 'title' in value);
|
|
122
|
+
}
|
|
123
|
+
function isUnifiedEvent(value) {
|
|
124
|
+
return Boolean(value && 'id' in value && 'title' in value && 'markets' in value);
|
|
125
|
+
}
|
|
126
|
+
function parseMatchedMarketCluster(raw) {
|
|
127
|
+
return {
|
|
128
|
+
...raw,
|
|
129
|
+
markets: (raw.markets || []).map(convertMarket),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function parseMatchedEventCluster(raw) {
|
|
133
|
+
return {
|
|
134
|
+
...raw,
|
|
135
|
+
events: (raw.events || []).map(convertEvent),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
82
138
|
/**
|
|
83
139
|
* Cross-venue intelligence layer.
|
|
84
140
|
*
|
|
@@ -172,6 +228,52 @@ export class Router extends Exchange {
|
|
|
172
228
|
throw new Error(`Failed to fetchEventMatches: ${error}`);
|
|
173
229
|
}
|
|
174
230
|
}
|
|
231
|
+
async fetchMatchedMarketClusters(marketOrParams = {}) {
|
|
232
|
+
const params = isUnifiedMarket(marketOrParams)
|
|
233
|
+
? { market: marketOrParams }
|
|
234
|
+
: marketOrParams;
|
|
235
|
+
await this.initPromise;
|
|
236
|
+
const query = {};
|
|
237
|
+
const marketId = params.marketId ?? params.market?.marketId;
|
|
238
|
+
const slug = params.slug ?? (!marketId ? params.market?.slug : undefined);
|
|
239
|
+
const url = params.url ?? (!marketId && !slug ? params.market?.url : undefined);
|
|
240
|
+
appendQuery(query, 'marketId', marketId);
|
|
241
|
+
appendQuery(query, 'slug', slug);
|
|
242
|
+
appendQuery(query, 'url', url);
|
|
243
|
+
addClusterFilters(query, params);
|
|
244
|
+
try {
|
|
245
|
+
const raw = await this.catalogReadRequest('/v0/matched-market-clusters', query);
|
|
246
|
+
return extractCatalogArray(raw).map(parseMatchedMarketCluster);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof Error)
|
|
250
|
+
throw error;
|
|
251
|
+
throw new Error(`Failed to fetchMatchedMarketClusters: ${error}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async fetchMatchedEventClusters(eventOrParams = {}) {
|
|
255
|
+
const params = isUnifiedEvent(eventOrParams)
|
|
256
|
+
? { event: eventOrParams }
|
|
257
|
+
: eventOrParams;
|
|
258
|
+
await this.initPromise;
|
|
259
|
+
const query = {};
|
|
260
|
+
const eventId = params.eventId ?? params.event?.id;
|
|
261
|
+
const slug = params.slug ?? (!eventId ? params.event?.slug : undefined);
|
|
262
|
+
const url = params.url ?? (!eventId && !slug ? params.event?.url : undefined);
|
|
263
|
+
appendQuery(query, 'eventId', eventId);
|
|
264
|
+
appendQuery(query, 'slug', slug);
|
|
265
|
+
appendQuery(query, 'url', url);
|
|
266
|
+
addClusterFilters(query, params);
|
|
267
|
+
try {
|
|
268
|
+
const raw = await this.catalogReadRequest('/v0/matched-event-clusters', query);
|
|
269
|
+
return extractCatalogArray(raw).map(parseMatchedEventCluster);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (error instanceof Error)
|
|
273
|
+
throw error;
|
|
274
|
+
throw new Error(`Failed to fetchMatchedEventClusters: ${error}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
175
277
|
async compareMarketPrices(marketOrParams = {}) {
|
|
176
278
|
const params = 'title' in marketOrParams ? { market: marketOrParams } : marketOrParams;
|
|
177
279
|
await this.initPromise;
|
package/dist/pmxt/client.d.ts
CHANGED
|
@@ -157,6 +157,14 @@ export declare abstract class Exchange {
|
|
|
157
157
|
* never use the GET read path.
|
|
158
158
|
*/
|
|
159
159
|
protected sidecarPostRequest(methodName: string, args: unknown[]): Promise<any>;
|
|
160
|
+
/**
|
|
161
|
+
* Read a hosted catalog endpoint directly.
|
|
162
|
+
*
|
|
163
|
+
* Hosted-only Router APIs such as matched clusters are not part of the
|
|
164
|
+
* core sidecar method namespace. They live under /v0 and return their own
|
|
165
|
+
* response envelopes, so callers intentionally receive the raw JSON body.
|
|
166
|
+
*/
|
|
167
|
+
protected catalogReadRequest(path: string, query?: Record<string, unknown>): Promise<any>;
|
|
160
168
|
loadMarkets(reload?: boolean): Promise<Record<string, UnifiedMarket>>;
|
|
161
169
|
fetchMarkets(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
|
|
162
170
|
fetchMarketsPaginated(params?: any): Promise<PaginatedMarketsResult>;
|
package/dist/pmxt/client.js
CHANGED
|
@@ -487,6 +487,29 @@ class Exchange {
|
|
|
487
487
|
}
|
|
488
488
|
return response.json();
|
|
489
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Read a hosted catalog endpoint directly.
|
|
492
|
+
*
|
|
493
|
+
* Hosted-only Router APIs such as matched clusters are not part of the
|
|
494
|
+
* core sidecar method namespace. They live under /v0 and return their own
|
|
495
|
+
* response envelopes, so callers intentionally receive the raw JSON body.
|
|
496
|
+
*/
|
|
497
|
+
async catalogReadRequest(path, query = {}) {
|
|
498
|
+
const qs = buildSidecarQueryString(query);
|
|
499
|
+
const url = `${this.resolveBaseUrl()}${path}${qs ? `?${qs}` : ''}`;
|
|
500
|
+
const response = await this.fetchWithRetry(url, {
|
|
501
|
+
method: 'GET',
|
|
502
|
+
headers: this.getAuthHeaders(),
|
|
503
|
+
});
|
|
504
|
+
if (!response.ok) {
|
|
505
|
+
const body = await response.json().catch(() => ({}));
|
|
506
|
+
if (body.error && typeof body.error === "object") {
|
|
507
|
+
throw (0, errors_js_1.fromServerError)(body.error);
|
|
508
|
+
}
|
|
509
|
+
throw new errors_js_1.PmxtError(body.error?.message || response.statusText);
|
|
510
|
+
}
|
|
511
|
+
return response.json();
|
|
512
|
+
}
|
|
490
513
|
// BEGIN GENERATED METHODS
|
|
491
514
|
async loadMarkets(reload = false) {
|
|
492
515
|
await this.initPromise;
|
package/dist/pmxt/models.d.ts
CHANGED
|
@@ -639,6 +639,96 @@ export interface EventMatchResult extends Readonly<UnifiedEvent> {
|
|
|
639
639
|
/** Cross-venue market matches within this event. */
|
|
640
640
|
marketMatches: MatchResult[];
|
|
641
641
|
}
|
|
642
|
+
export type MatchedClusterSort = 'volume' | 'confidence';
|
|
643
|
+
/** Shared filters for matched market/event cluster discovery. */
|
|
644
|
+
export interface MatchedClusterFilterParams {
|
|
645
|
+
/** Text search across cluster titles. */
|
|
646
|
+
query?: string;
|
|
647
|
+
/** Filter both sides of matched edges by category. */
|
|
648
|
+
category?: string;
|
|
649
|
+
/** Comma-separated relation filter. */
|
|
650
|
+
relations?: MatchRelation | MatchRelation[] | string;
|
|
651
|
+
/** Single relation filter. Alias for relations. */
|
|
652
|
+
relation?: MatchRelation;
|
|
653
|
+
/** Minimum match confidence score (0.0 to 1.0). */
|
|
654
|
+
minConfidence?: number;
|
|
655
|
+
/** Venue allow-list. */
|
|
656
|
+
venues?: string | string[];
|
|
657
|
+
/** Venue deny-list. */
|
|
658
|
+
excludeVenues?: string | string[];
|
|
659
|
+
/** Minimum number of venues required in a cluster. */
|
|
660
|
+
minVenues?: number;
|
|
661
|
+
/** Require live orderbook coverage on matched edges. */
|
|
662
|
+
withOrderbook?: boolean;
|
|
663
|
+
/** Only include matches updated after this timestamp. */
|
|
664
|
+
updatedSince?: string | Date;
|
|
665
|
+
/** Include the pairwise match edges used to build each cluster. */
|
|
666
|
+
includeRawMatches?: boolean;
|
|
667
|
+
/** Cluster sort order. */
|
|
668
|
+
sort?: MatchedClusterSort;
|
|
669
|
+
/** Maximum clusters to return. */
|
|
670
|
+
limit?: number;
|
|
671
|
+
/** Pagination offset. */
|
|
672
|
+
offset?: number;
|
|
673
|
+
/** Maximum pairwise edges to scan before clustering. */
|
|
674
|
+
edgeLimit?: number;
|
|
675
|
+
}
|
|
676
|
+
/** Parameters for fetching matched market clusters. */
|
|
677
|
+
export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterParams {
|
|
678
|
+
/** Pass a UnifiedMarket directly instead of marketId/slug/url. */
|
|
679
|
+
market?: UnifiedMarket;
|
|
680
|
+
/** Anchor the response to a specific market ID. */
|
|
681
|
+
marketId?: string;
|
|
682
|
+
/** Anchor the response to a specific market slug. */
|
|
683
|
+
slug?: string;
|
|
684
|
+
/** Anchor the response to a specific market URL. */
|
|
685
|
+
url?: string;
|
|
686
|
+
}
|
|
687
|
+
/** Parameters for fetching matched event clusters. */
|
|
688
|
+
export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
|
|
689
|
+
/** Pass a UnifiedEvent directly instead of eventId/slug/url. */
|
|
690
|
+
event?: UnifiedEvent;
|
|
691
|
+
/** Anchor the response to a specific event ID. */
|
|
692
|
+
eventId?: string;
|
|
693
|
+
/** Anchor the response to a specific event slug. */
|
|
694
|
+
slug?: string;
|
|
695
|
+
/** Anchor the response to a specific event URL. */
|
|
696
|
+
url?: string;
|
|
697
|
+
}
|
|
698
|
+
/** Pairwise edge used to build a matched market cluster. */
|
|
699
|
+
export interface MatchedMarketClusterEdge {
|
|
700
|
+
marketAId: string;
|
|
701
|
+
marketBId: string;
|
|
702
|
+
relation: MatchRelation;
|
|
703
|
+
confidence: number;
|
|
704
|
+
}
|
|
705
|
+
/** Pairwise edge used to build a matched event cluster. */
|
|
706
|
+
export interface MatchedEventClusterEdge extends MatchedMarketClusterEdge {
|
|
707
|
+
eventAId: string;
|
|
708
|
+
eventBId: string;
|
|
709
|
+
}
|
|
710
|
+
/** Connected cluster of semantically matched markets across venues. */
|
|
711
|
+
export interface MatchedMarketCluster {
|
|
712
|
+
clusterId: string;
|
|
713
|
+
canonicalTitle: string | null;
|
|
714
|
+
category?: string | null;
|
|
715
|
+
relations: MatchRelation[];
|
|
716
|
+
confidence: number;
|
|
717
|
+
volume24h: number;
|
|
718
|
+
markets: UnifiedMarket[];
|
|
719
|
+
rawMatches?: MatchedMarketClusterEdge[];
|
|
720
|
+
}
|
|
721
|
+
/** Connected cluster of semantically matched events across venues. */
|
|
722
|
+
export interface MatchedEventCluster {
|
|
723
|
+
clusterId: string;
|
|
724
|
+
canonicalTitle: string | null;
|
|
725
|
+
category?: string | null;
|
|
726
|
+
relations: MatchRelation[];
|
|
727
|
+
confidence: number;
|
|
728
|
+
volume24h: number;
|
|
729
|
+
events: UnifiedEvent[];
|
|
730
|
+
rawMatches?: MatchedEventClusterEdge[];
|
|
731
|
+
}
|
|
642
732
|
/** Side-by-side price comparison for a matched market. */
|
|
643
733
|
export interface PriceComparison {
|
|
644
734
|
/** The matched market. */
|
package/dist/pmxt/router.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* every venue PMXT supports. Only requires a PMXT API key.
|
|
6
6
|
*/
|
|
7
7
|
import { Exchange } from "./client.js";
|
|
8
|
-
import { MatchResult, MatchRelation, EventMatchResult, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
|
|
8
|
+
import { MatchResult, MatchRelation, EventMatchResult, FetchMatchedEventClustersParams, FetchMatchedMarketClustersParams, MatchedEventCluster, MatchedMarketCluster, PriceComparison, ArbitrageOpportunity, UnifiedMarket, UnifiedEvent } from "./models.js";
|
|
9
9
|
/** Options for creating a Router. */
|
|
10
10
|
export interface RouterOptions {
|
|
11
11
|
/** PMXT API key (required for hosted mode). */
|
|
@@ -78,6 +78,20 @@ export declare class Router extends Exchange {
|
|
|
78
78
|
limit?: number;
|
|
79
79
|
includePrices?: boolean;
|
|
80
80
|
}): Promise<EventMatchResult[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Fetch connected clusters of semantically matched markets across venues.
|
|
83
|
+
*
|
|
84
|
+
* @param marketOrParams - A UnifiedMarket, or an options object.
|
|
85
|
+
*/
|
|
86
|
+
fetchMatchedMarketClusters(market: UnifiedMarket): Promise<MatchedMarketCluster[]>;
|
|
87
|
+
fetchMatchedMarketClusters(params?: FetchMatchedMarketClustersParams): Promise<MatchedMarketCluster[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Fetch connected clusters of semantically matched events across venues.
|
|
90
|
+
*
|
|
91
|
+
* @param eventOrParams - A UnifiedEvent, or an options object.
|
|
92
|
+
*/
|
|
93
|
+
fetchMatchedEventClusters(event: UnifiedEvent): Promise<MatchedEventCluster[]>;
|
|
94
|
+
fetchMatchedEventClusters(params?: FetchMatchedEventClustersParams): Promise<MatchedEventCluster[]>;
|
|
81
95
|
/**
|
|
82
96
|
* Compare prices for the same market across venues.
|
|
83
97
|
*
|
package/dist/pmxt/router.js
CHANGED
|
@@ -49,6 +49,8 @@ function convertMarket(raw) {
|
|
|
49
49
|
label: o.label,
|
|
50
50
|
price: o.price,
|
|
51
51
|
priceChange24h: o.priceChange24h,
|
|
52
|
+
bestBid: o.bestBid,
|
|
53
|
+
bestAsk: o.bestAsk,
|
|
52
54
|
metadata: o.metadata,
|
|
53
55
|
}));
|
|
54
56
|
const convertOutcome = (o) => o ? ({
|
|
@@ -57,6 +59,8 @@ function convertMarket(raw) {
|
|
|
57
59
|
label: o.label,
|
|
58
60
|
price: o.price,
|
|
59
61
|
priceChange24h: o.priceChange24h,
|
|
62
|
+
bestBid: o.bestBid,
|
|
63
|
+
bestAsk: o.bestAsk,
|
|
60
64
|
metadata: o.metadata,
|
|
61
65
|
}) : undefined;
|
|
62
66
|
return {
|
|
@@ -115,6 +119,58 @@ function parseMatchResult(raw) {
|
|
|
115
119
|
sourceMarket: raw.sourceMarket ? convertMarket(raw.sourceMarket) : undefined,
|
|
116
120
|
};
|
|
117
121
|
}
|
|
122
|
+
function normalizeQueryValue(value) {
|
|
123
|
+
if (value instanceof Date)
|
|
124
|
+
return value.toISOString();
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function appendQuery(query, key, value) {
|
|
128
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
129
|
+
query[key] = normalizeQueryValue(value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function addClusterFilters(query, params) {
|
|
133
|
+
appendQuery(query, 'query', params.query);
|
|
134
|
+
appendQuery(query, 'category', params.category);
|
|
135
|
+
appendQuery(query, 'relations', params.relations);
|
|
136
|
+
appendQuery(query, 'relation', params.relation);
|
|
137
|
+
appendQuery(query, 'minConfidence', params.minConfidence);
|
|
138
|
+
appendQuery(query, 'venues', params.venues);
|
|
139
|
+
appendQuery(query, 'excludeVenues', params.excludeVenues);
|
|
140
|
+
appendQuery(query, 'minVenues', params.minVenues);
|
|
141
|
+
appendQuery(query, 'withOrderbook', params.withOrderbook);
|
|
142
|
+
appendQuery(query, 'updatedSince', params.updatedSince);
|
|
143
|
+
appendQuery(query, 'includeRawMatches', params.includeRawMatches);
|
|
144
|
+
appendQuery(query, 'sort', params.sort);
|
|
145
|
+
appendQuery(query, 'limit', params.limit);
|
|
146
|
+
appendQuery(query, 'offset', params.offset);
|
|
147
|
+
appendQuery(query, 'edgeLimit', params.edgeLimit);
|
|
148
|
+
}
|
|
149
|
+
function extractCatalogArray(raw) {
|
|
150
|
+
if (Array.isArray(raw))
|
|
151
|
+
return raw;
|
|
152
|
+
if (Array.isArray(raw?.data))
|
|
153
|
+
return raw.data;
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
function isUnifiedMarket(value) {
|
|
157
|
+
return Boolean(value && 'marketId' in value && 'title' in value);
|
|
158
|
+
}
|
|
159
|
+
function isUnifiedEvent(value) {
|
|
160
|
+
return Boolean(value && 'id' in value && 'title' in value && 'markets' in value);
|
|
161
|
+
}
|
|
162
|
+
function parseMatchedMarketCluster(raw) {
|
|
163
|
+
return {
|
|
164
|
+
...raw,
|
|
165
|
+
markets: (raw.markets || []).map(convertMarket),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function parseMatchedEventCluster(raw) {
|
|
169
|
+
return {
|
|
170
|
+
...raw,
|
|
171
|
+
events: (raw.events || []).map(convertEvent),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
118
174
|
/**
|
|
119
175
|
* Cross-venue intelligence layer.
|
|
120
176
|
*
|
|
@@ -208,6 +264,52 @@ class Router extends client_js_1.Exchange {
|
|
|
208
264
|
throw new Error(`Failed to fetchEventMatches: ${error}`);
|
|
209
265
|
}
|
|
210
266
|
}
|
|
267
|
+
async fetchMatchedMarketClusters(marketOrParams = {}) {
|
|
268
|
+
const params = isUnifiedMarket(marketOrParams)
|
|
269
|
+
? { market: marketOrParams }
|
|
270
|
+
: marketOrParams;
|
|
271
|
+
await this.initPromise;
|
|
272
|
+
const query = {};
|
|
273
|
+
const marketId = params.marketId ?? params.market?.marketId;
|
|
274
|
+
const slug = params.slug ?? (!marketId ? params.market?.slug : undefined);
|
|
275
|
+
const url = params.url ?? (!marketId && !slug ? params.market?.url : undefined);
|
|
276
|
+
appendQuery(query, 'marketId', marketId);
|
|
277
|
+
appendQuery(query, 'slug', slug);
|
|
278
|
+
appendQuery(query, 'url', url);
|
|
279
|
+
addClusterFilters(query, params);
|
|
280
|
+
try {
|
|
281
|
+
const raw = await this.catalogReadRequest('/v0/matched-market-clusters', query);
|
|
282
|
+
return extractCatalogArray(raw).map(parseMatchedMarketCluster);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (error instanceof Error)
|
|
286
|
+
throw error;
|
|
287
|
+
throw new Error(`Failed to fetchMatchedMarketClusters: ${error}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async fetchMatchedEventClusters(eventOrParams = {}) {
|
|
291
|
+
const params = isUnifiedEvent(eventOrParams)
|
|
292
|
+
? { event: eventOrParams }
|
|
293
|
+
: eventOrParams;
|
|
294
|
+
await this.initPromise;
|
|
295
|
+
const query = {};
|
|
296
|
+
const eventId = params.eventId ?? params.event?.id;
|
|
297
|
+
const slug = params.slug ?? (!eventId ? params.event?.slug : undefined);
|
|
298
|
+
const url = params.url ?? (!eventId && !slug ? params.event?.url : undefined);
|
|
299
|
+
appendQuery(query, 'eventId', eventId);
|
|
300
|
+
appendQuery(query, 'slug', slug);
|
|
301
|
+
appendQuery(query, 'url', url);
|
|
302
|
+
addClusterFilters(query, params);
|
|
303
|
+
try {
|
|
304
|
+
const raw = await this.catalogReadRequest('/v0/matched-event-clusters', query);
|
|
305
|
+
return extractCatalogArray(raw).map(parseMatchedEventCluster);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error instanceof Error)
|
|
309
|
+
throw error;
|
|
310
|
+
throw new Error(`Failed to fetchMatchedEventClusters: ${error}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
211
313
|
async compareMarketPrices(marketOrParams = {}) {
|
|
212
314
|
const params = 'title' in marketOrParams ? { market: marketOrParams } : marketOrParams;
|
|
213
315
|
await this.initPromise;
|
package/generated/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.44.1",
|
|
4
4
|
"description": "Unified prediction market data API - The ccxt for prediction markets",
|
|
5
5
|
"author": "PMXT Contributors",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"unified"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"pmxt-core": "2.
|
|
46
|
+
"pmxt-core": "2.44.1",
|
|
47
47
|
"ws": "^8.18.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
package/pmxt/client.ts
CHANGED
|
@@ -632,6 +632,30 @@ export abstract class Exchange {
|
|
|
632
632
|
return response.json();
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Read a hosted catalog endpoint directly.
|
|
637
|
+
*
|
|
638
|
+
* Hosted-only Router APIs such as matched clusters are not part of the
|
|
639
|
+
* core sidecar method namespace. They live under /v0 and return their own
|
|
640
|
+
* response envelopes, so callers intentionally receive the raw JSON body.
|
|
641
|
+
*/
|
|
642
|
+
protected async catalogReadRequest(path: string, query: Record<string, unknown> = {}): Promise<any> {
|
|
643
|
+
const qs = buildSidecarQueryString(query);
|
|
644
|
+
const url = `${this.resolveBaseUrl()}${path}${qs ? `?${qs}` : ''}`;
|
|
645
|
+
const response = await this.fetchWithRetry(url, {
|
|
646
|
+
method: 'GET',
|
|
647
|
+
headers: this.getAuthHeaders(),
|
|
648
|
+
});
|
|
649
|
+
if (!response.ok) {
|
|
650
|
+
const body = await response.json().catch(() => ({}));
|
|
651
|
+
if (body.error && typeof body.error === "object") {
|
|
652
|
+
throw fromServerError(body.error);
|
|
653
|
+
}
|
|
654
|
+
throw new PmxtError(body.error?.message || response.statusText);
|
|
655
|
+
}
|
|
656
|
+
return response.json();
|
|
657
|
+
}
|
|
658
|
+
|
|
635
659
|
// BEGIN GENERATED METHODS
|
|
636
660
|
|
|
637
661
|
async loadMarkets(reload: boolean = false): Promise<Record<string, UnifiedMarket>> {
|
package/pmxt/models.ts
CHANGED
|
@@ -861,6 +861,124 @@ export interface EventMatchResult extends Readonly<UnifiedEvent> {
|
|
|
861
861
|
marketMatches: MatchResult[];
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
+
export type MatchedClusterSort = 'volume' | 'confidence';
|
|
865
|
+
|
|
866
|
+
/** Shared filters for matched market/event cluster discovery. */
|
|
867
|
+
export interface MatchedClusterFilterParams {
|
|
868
|
+
/** Text search across cluster titles. */
|
|
869
|
+
query?: string;
|
|
870
|
+
|
|
871
|
+
/** Filter both sides of matched edges by category. */
|
|
872
|
+
category?: string;
|
|
873
|
+
|
|
874
|
+
/** Comma-separated relation filter. */
|
|
875
|
+
relations?: MatchRelation | MatchRelation[] | string;
|
|
876
|
+
|
|
877
|
+
/** Single relation filter. Alias for relations. */
|
|
878
|
+
relation?: MatchRelation;
|
|
879
|
+
|
|
880
|
+
/** Minimum match confidence score (0.0 to 1.0). */
|
|
881
|
+
minConfidence?: number;
|
|
882
|
+
|
|
883
|
+
/** Venue allow-list. */
|
|
884
|
+
venues?: string | string[];
|
|
885
|
+
|
|
886
|
+
/** Venue deny-list. */
|
|
887
|
+
excludeVenues?: string | string[];
|
|
888
|
+
|
|
889
|
+
/** Minimum number of venues required in a cluster. */
|
|
890
|
+
minVenues?: number;
|
|
891
|
+
|
|
892
|
+
/** Require live orderbook coverage on matched edges. */
|
|
893
|
+
withOrderbook?: boolean;
|
|
894
|
+
|
|
895
|
+
/** Only include matches updated after this timestamp. */
|
|
896
|
+
updatedSince?: string | Date;
|
|
897
|
+
|
|
898
|
+
/** Include the pairwise match edges used to build each cluster. */
|
|
899
|
+
includeRawMatches?: boolean;
|
|
900
|
+
|
|
901
|
+
/** Cluster sort order. */
|
|
902
|
+
sort?: MatchedClusterSort;
|
|
903
|
+
|
|
904
|
+
/** Maximum clusters to return. */
|
|
905
|
+
limit?: number;
|
|
906
|
+
|
|
907
|
+
/** Pagination offset. */
|
|
908
|
+
offset?: number;
|
|
909
|
+
|
|
910
|
+
/** Maximum pairwise edges to scan before clustering. */
|
|
911
|
+
edgeLimit?: number;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** Parameters for fetching matched market clusters. */
|
|
915
|
+
export interface FetchMatchedMarketClustersParams extends MatchedClusterFilterParams {
|
|
916
|
+
/** Pass a UnifiedMarket directly instead of marketId/slug/url. */
|
|
917
|
+
market?: UnifiedMarket;
|
|
918
|
+
|
|
919
|
+
/** Anchor the response to a specific market ID. */
|
|
920
|
+
marketId?: string;
|
|
921
|
+
|
|
922
|
+
/** Anchor the response to a specific market slug. */
|
|
923
|
+
slug?: string;
|
|
924
|
+
|
|
925
|
+
/** Anchor the response to a specific market URL. */
|
|
926
|
+
url?: string;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Parameters for fetching matched event clusters. */
|
|
930
|
+
export interface FetchMatchedEventClustersParams extends MatchedClusterFilterParams {
|
|
931
|
+
/** Pass a UnifiedEvent directly instead of eventId/slug/url. */
|
|
932
|
+
event?: UnifiedEvent;
|
|
933
|
+
|
|
934
|
+
/** Anchor the response to a specific event ID. */
|
|
935
|
+
eventId?: string;
|
|
936
|
+
|
|
937
|
+
/** Anchor the response to a specific event slug. */
|
|
938
|
+
slug?: string;
|
|
939
|
+
|
|
940
|
+
/** Anchor the response to a specific event URL. */
|
|
941
|
+
url?: string;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/** Pairwise edge used to build a matched market cluster. */
|
|
945
|
+
export interface MatchedMarketClusterEdge {
|
|
946
|
+
marketAId: string;
|
|
947
|
+
marketBId: string;
|
|
948
|
+
relation: MatchRelation;
|
|
949
|
+
confidence: number;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** Pairwise edge used to build a matched event cluster. */
|
|
953
|
+
export interface MatchedEventClusterEdge extends MatchedMarketClusterEdge {
|
|
954
|
+
eventAId: string;
|
|
955
|
+
eventBId: string;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/** Connected cluster of semantically matched markets across venues. */
|
|
959
|
+
export interface MatchedMarketCluster {
|
|
960
|
+
clusterId: string;
|
|
961
|
+
canonicalTitle: string | null;
|
|
962
|
+
category?: string | null;
|
|
963
|
+
relations: MatchRelation[];
|
|
964
|
+
confidence: number;
|
|
965
|
+
volume24h: number;
|
|
966
|
+
markets: UnifiedMarket[];
|
|
967
|
+
rawMatches?: MatchedMarketClusterEdge[];
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/** Connected cluster of semantically matched events across venues. */
|
|
971
|
+
export interface MatchedEventCluster {
|
|
972
|
+
clusterId: string;
|
|
973
|
+
canonicalTitle: string | null;
|
|
974
|
+
category?: string | null;
|
|
975
|
+
relations: MatchRelation[];
|
|
976
|
+
confidence: number;
|
|
977
|
+
volume24h: number;
|
|
978
|
+
events: UnifiedEvent[];
|
|
979
|
+
rawMatches?: MatchedEventClusterEdge[];
|
|
980
|
+
}
|
|
981
|
+
|
|
864
982
|
/** Side-by-side price comparison for a matched market. */
|
|
865
983
|
export interface PriceComparison {
|
|
866
984
|
/** The matched market. */
|
package/pmxt/router.ts
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
MatchResult,
|
|
12
12
|
MatchRelation,
|
|
13
13
|
EventMatchResult,
|
|
14
|
+
FetchMatchedEventClustersParams,
|
|
15
|
+
FetchMatchedMarketClustersParams,
|
|
16
|
+
MatchedEventCluster,
|
|
17
|
+
MatchedMarketCluster,
|
|
14
18
|
PriceComparison,
|
|
15
19
|
ArbitrageOpportunity,
|
|
16
20
|
UnifiedMarket,
|
|
@@ -24,6 +28,8 @@ function convertMarket(raw: any): UnifiedMarket {
|
|
|
24
28
|
label: o.label,
|
|
25
29
|
price: o.price,
|
|
26
30
|
priceChange24h: o.priceChange24h,
|
|
31
|
+
bestBid: o.bestBid,
|
|
32
|
+
bestAsk: o.bestAsk,
|
|
27
33
|
metadata: o.metadata,
|
|
28
34
|
}));
|
|
29
35
|
|
|
@@ -33,6 +39,8 @@ function convertMarket(raw: any): UnifiedMarket {
|
|
|
33
39
|
label: o.label,
|
|
34
40
|
price: o.price,
|
|
35
41
|
priceChange24h: o.priceChange24h,
|
|
42
|
+
bestBid: o.bestBid,
|
|
43
|
+
bestAsk: o.bestAsk,
|
|
36
44
|
metadata: o.metadata,
|
|
37
45
|
}) : undefined;
|
|
38
46
|
|
|
@@ -95,6 +103,79 @@ function parseMatchResult(raw: any): MatchResult {
|
|
|
95
103
|
};
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
function normalizeQueryValue(value: unknown): unknown {
|
|
107
|
+
if (value instanceof Date) return value.toISOString();
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function appendQuery(query: Record<string, unknown>, key: string, value: unknown): void {
|
|
112
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
113
|
+
query[key] = normalizeQueryValue(value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function addClusterFilters(query: Record<string, unknown>, params: {
|
|
118
|
+
query?: string;
|
|
119
|
+
category?: string;
|
|
120
|
+
relations?: MatchRelation | MatchRelation[] | string;
|
|
121
|
+
relation?: MatchRelation;
|
|
122
|
+
minConfidence?: number;
|
|
123
|
+
venues?: string | string[];
|
|
124
|
+
excludeVenues?: string | string[];
|
|
125
|
+
minVenues?: number;
|
|
126
|
+
withOrderbook?: boolean;
|
|
127
|
+
updatedSince?: string | Date;
|
|
128
|
+
includeRawMatches?: boolean;
|
|
129
|
+
sort?: string;
|
|
130
|
+
limit?: number;
|
|
131
|
+
offset?: number;
|
|
132
|
+
edgeLimit?: number;
|
|
133
|
+
}): void {
|
|
134
|
+
appendQuery(query, 'query', params.query);
|
|
135
|
+
appendQuery(query, 'category', params.category);
|
|
136
|
+
appendQuery(query, 'relations', params.relations);
|
|
137
|
+
appendQuery(query, 'relation', params.relation);
|
|
138
|
+
appendQuery(query, 'minConfidence', params.minConfidence);
|
|
139
|
+
appendQuery(query, 'venues', params.venues);
|
|
140
|
+
appendQuery(query, 'excludeVenues', params.excludeVenues);
|
|
141
|
+
appendQuery(query, 'minVenues', params.minVenues);
|
|
142
|
+
appendQuery(query, 'withOrderbook', params.withOrderbook);
|
|
143
|
+
appendQuery(query, 'updatedSince', params.updatedSince);
|
|
144
|
+
appendQuery(query, 'includeRawMatches', params.includeRawMatches);
|
|
145
|
+
appendQuery(query, 'sort', params.sort);
|
|
146
|
+
appendQuery(query, 'limit', params.limit);
|
|
147
|
+
appendQuery(query, 'offset', params.offset);
|
|
148
|
+
appendQuery(query, 'edgeLimit', params.edgeLimit);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractCatalogArray(raw: any): any[] {
|
|
152
|
+
if (Array.isArray(raw)) return raw;
|
|
153
|
+
if (Array.isArray(raw?.data)) return raw.data;
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isUnifiedMarket(value: UnifiedMarket | FetchMatchedMarketClustersParams): value is UnifiedMarket {
|
|
158
|
+
return Boolean(value && 'marketId' in value && 'title' in value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isUnifiedEvent(value: UnifiedEvent | FetchMatchedEventClustersParams): value is UnifiedEvent {
|
|
162
|
+
return Boolean(value && 'id' in value && 'title' in value && 'markets' in value);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseMatchedMarketCluster(raw: any): MatchedMarketCluster {
|
|
166
|
+
return {
|
|
167
|
+
...raw,
|
|
168
|
+
markets: (raw.markets || []).map(convertMarket),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseMatchedEventCluster(raw: any): MatchedEventCluster {
|
|
173
|
+
return {
|
|
174
|
+
...raw,
|
|
175
|
+
events: (raw.events || []).map(convertEvent),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
98
179
|
/** Options for creating a Router. */
|
|
99
180
|
export interface RouterOptions {
|
|
100
181
|
/** PMXT API key (required for hosted mode). */
|
|
@@ -262,6 +343,72 @@ export class Router extends Exchange {
|
|
|
262
343
|
}
|
|
263
344
|
}
|
|
264
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Fetch connected clusters of semantically matched markets across venues.
|
|
348
|
+
*
|
|
349
|
+
* @param marketOrParams - A UnifiedMarket, or an options object.
|
|
350
|
+
*/
|
|
351
|
+
async fetchMatchedMarketClusters(market: UnifiedMarket): Promise<MatchedMarketCluster[]>;
|
|
352
|
+
async fetchMatchedMarketClusters(params?: FetchMatchedMarketClustersParams): Promise<MatchedMarketCluster[]>;
|
|
353
|
+
async fetchMatchedMarketClusters(
|
|
354
|
+
marketOrParams: UnifiedMarket | FetchMatchedMarketClustersParams = {},
|
|
355
|
+
): Promise<MatchedMarketCluster[]> {
|
|
356
|
+
const params: FetchMatchedMarketClustersParams = isUnifiedMarket(marketOrParams)
|
|
357
|
+
? { market: marketOrParams }
|
|
358
|
+
: marketOrParams;
|
|
359
|
+
await this.initPromise;
|
|
360
|
+
|
|
361
|
+
const query: Record<string, unknown> = {};
|
|
362
|
+
const marketId = params.marketId ?? params.market?.marketId;
|
|
363
|
+
const slug = params.slug ?? (!marketId ? params.market?.slug : undefined);
|
|
364
|
+
const url = params.url ?? (!marketId && !slug ? params.market?.url : undefined);
|
|
365
|
+
appendQuery(query, 'marketId', marketId);
|
|
366
|
+
appendQuery(query, 'slug', slug);
|
|
367
|
+
appendQuery(query, 'url', url);
|
|
368
|
+
addClusterFilters(query, params);
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const raw = await this.catalogReadRequest('/v0/matched-market-clusters', query);
|
|
372
|
+
return extractCatalogArray(raw).map(parseMatchedMarketCluster);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error instanceof Error) throw error;
|
|
375
|
+
throw new Error(`Failed to fetchMatchedMarketClusters: ${error}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Fetch connected clusters of semantically matched events across venues.
|
|
381
|
+
*
|
|
382
|
+
* @param eventOrParams - A UnifiedEvent, or an options object.
|
|
383
|
+
*/
|
|
384
|
+
async fetchMatchedEventClusters(event: UnifiedEvent): Promise<MatchedEventCluster[]>;
|
|
385
|
+
async fetchMatchedEventClusters(params?: FetchMatchedEventClustersParams): Promise<MatchedEventCluster[]>;
|
|
386
|
+
async fetchMatchedEventClusters(
|
|
387
|
+
eventOrParams: UnifiedEvent | FetchMatchedEventClustersParams = {},
|
|
388
|
+
): Promise<MatchedEventCluster[]> {
|
|
389
|
+
const params: FetchMatchedEventClustersParams = isUnifiedEvent(eventOrParams)
|
|
390
|
+
? { event: eventOrParams }
|
|
391
|
+
: eventOrParams;
|
|
392
|
+
await this.initPromise;
|
|
393
|
+
|
|
394
|
+
const query: Record<string, unknown> = {};
|
|
395
|
+
const eventId = params.eventId ?? params.event?.id;
|
|
396
|
+
const slug = params.slug ?? (!eventId ? params.event?.slug : undefined);
|
|
397
|
+
const url = params.url ?? (!eventId && !slug ? params.event?.url : undefined);
|
|
398
|
+
appendQuery(query, 'eventId', eventId);
|
|
399
|
+
appendQuery(query, 'slug', slug);
|
|
400
|
+
appendQuery(query, 'url', url);
|
|
401
|
+
addClusterFilters(query, params);
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const raw = await this.catalogReadRequest('/v0/matched-event-clusters', query);
|
|
405
|
+
return extractCatalogArray(raw).map(parseMatchedEventCluster);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (error instanceof Error) throw error;
|
|
408
|
+
throw new Error(`Failed to fetchMatchedEventClusters: ${error}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
265
412
|
// ------------------------------------------------------------------
|
|
266
413
|
// Price comparison
|
|
267
414
|
// ------------------------------------------------------------------
|