@catalyst-team/poly-sdk 0.3.0 → 0.4.0
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/README.md +125 -0
- package/dist/clients/ctf-client.d.ts +6 -4
- package/dist/clients/ctf-client.d.ts.map +1 -1
- package/dist/clients/ctf-client.js.map +1 -1
- package/dist/clients/data-api.d.ts +19 -6
- package/dist/clients/data-api.d.ts.map +1 -1
- package/dist/clients/data-api.js +79 -34
- package/dist/clients/data-api.js.map +1 -1
- package/dist/clients/gamma-api.d.ts +5 -0
- package/dist/clients/gamma-api.d.ts.map +1 -1
- package/dist/clients/gamma-api.js +2 -0
- package/dist/clients/gamma-api.js.map +1 -1
- package/dist/core/cache.d.ts +2 -0
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +4 -0
- package/dist/core/cache.js.map +1 -1
- package/dist/core/rate-limiter.d.ts +2 -1
- package/dist/core/rate-limiter.d.ts.map +1 -1
- package/dist/core/rate-limiter.js +7 -0
- package/dist/core/rate-limiter.js.map +1 -1
- package/dist/core/types.d.ts +105 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +30 -0
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/scripts/dip-arb/auto-trade.d.ts +20 -0
- package/dist/scripts/dip-arb/auto-trade.d.ts.map +1 -0
- package/dist/scripts/dip-arb/auto-trade.js +373 -0
- package/dist/scripts/dip-arb/auto-trade.js.map +1 -0
- package/dist/scripts/dip-arb/example-basic.d.ts +30 -0
- package/dist/scripts/dip-arb/example-basic.d.ts.map +1 -0
- package/dist/scripts/dip-arb/example-basic.js +222 -0
- package/dist/scripts/dip-arb/example-basic.js.map +1 -0
- package/dist/scripts/dip-arb/redeem-positions.d.ts +11 -0
- package/dist/scripts/dip-arb/redeem-positions.d.ts.map +1 -0
- package/dist/scripts/dip-arb/redeem-positions.js +201 -0
- package/dist/scripts/dip-arb/redeem-positions.js.map +1 -0
- package/dist/scripts/dip-arb/scan-markets.d.ts +6 -0
- package/dist/scripts/dip-arb/scan-markets.d.ts.map +1 -0
- package/dist/scripts/dip-arb/scan-markets.js +73 -0
- package/dist/scripts/dip-arb/scan-markets.js.map +1 -0
- package/dist/services/arbitrage-service.d.ts.map +1 -1
- package/dist/services/arbitrage-service.js +14 -4
- package/dist/services/arbitrage-service.js.map +1 -1
- package/dist/services/binance-service.d.ts +154 -0
- package/dist/services/binance-service.d.ts.map +1 -0
- package/dist/services/binance-service.js +266 -0
- package/dist/services/binance-service.js.map +1 -0
- package/dist/services/dip-arb-service.d.ts +209 -0
- package/dist/services/dip-arb-service.d.ts.map +1 -0
- package/dist/services/dip-arb-service.js +1602 -0
- package/dist/services/dip-arb-service.js.map +1 -0
- package/dist/services/dip-arb-types.d.ts +553 -0
- package/dist/services/dip-arb-types.d.ts.map +1 -0
- package/dist/services/dip-arb-types.js +164 -0
- package/dist/services/dip-arb-types.js.map +1 -0
- package/dist/services/market-service.d.ts +162 -3
- package/dist/services/market-service.d.ts.map +1 -1
- package/dist/services/market-service.js +427 -14
- package/dist/services/market-service.js.map +1 -1
- package/dist/services/realtime-service-v2.d.ts +1 -0
- package/dist/services/realtime-service-v2.d.ts.map +1 -1
- package/dist/services/realtime-service-v2.js +20 -2
- package/dist/services/realtime-service-v2.js.map +1 -1
- package/dist/services/smart-money-service.d.ts +157 -1
- package/dist/services/smart-money-service.d.ts.map +1 -1
- package/dist/services/smart-money-service.js +224 -0
- package/dist/services/smart-money-service.js.map +1 -1
- package/dist/services/trading-service.d.ts +21 -0
- package/dist/services/trading-service.d.ts.map +1 -1
- package/dist/services/trading-service.js +67 -1
- package/dist/services/trading-service.js.map +1 -1
- package/dist/services/wallet-service.d.ts +43 -2
- package/dist/services/wallet-service.d.ts.map +1 -1
- package/dist/services/wallet-service.js +54 -3
- package/dist/services/wallet-service.js.map +1 -1
- package/dist/src/__tests__/integration/arbitrage-service.integration.test.d.ts +12 -0
- package/dist/src/__tests__/integration/arbitrage-service.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/arbitrage-service.integration.test.js +267 -0
- package/dist/src/__tests__/integration/arbitrage-service.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/bridge-client.integration.test.d.ts +11 -0
- package/dist/src/__tests__/integration/bridge-client.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/bridge-client.integration.test.js +260 -0
- package/dist/src/__tests__/integration/bridge-client.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/ctf-client.integration.test.d.ts +17 -0
- package/dist/src/__tests__/integration/ctf-client.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/ctf-client.integration.test.js +234 -0
- package/dist/src/__tests__/integration/ctf-client.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/data-api.integration.test.d.ts +9 -0
- package/dist/src/__tests__/integration/data-api.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/data-api.integration.test.js +164 -0
- package/dist/src/__tests__/integration/data-api.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/gamma-api.integration.test.d.ts +9 -0
- package/dist/src/__tests__/integration/gamma-api.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/gamma-api.integration.test.js +170 -0
- package/dist/src/__tests__/integration/gamma-api.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/market-service.integration.test.d.ts +10 -0
- package/dist/src/__tests__/integration/market-service.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/market-service.integration.test.js +180 -0
- package/dist/src/__tests__/integration/market-service.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/realtime-service-v2.integration.test.d.ts +10 -0
- package/dist/src/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/realtime-service-v2.integration.test.js +307 -0
- package/dist/src/__tests__/integration/realtime-service-v2.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/trading-service.integration.test.d.ts +10 -0
- package/dist/src/__tests__/integration/trading-service.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/trading-service.integration.test.js +58 -0
- package/dist/src/__tests__/integration/trading-service.integration.test.js.map +1 -0
- package/dist/src/__tests__/test-utils.d.ts +92 -0
- package/dist/src/__tests__/test-utils.d.ts.map +1 -0
- package/dist/src/__tests__/test-utils.js +143 -0
- package/dist/src/__tests__/test-utils.js.map +1 -0
- package/dist/src/clients/bridge-client.d.ts +388 -0
- package/dist/src/clients/bridge-client.d.ts.map +1 -0
- package/dist/src/clients/bridge-client.js +587 -0
- package/dist/src/clients/bridge-client.js.map +1 -0
- package/dist/src/clients/ctf-client.d.ts +475 -0
- package/dist/src/clients/ctf-client.d.ts.map +1 -0
- package/dist/src/clients/ctf-client.js +915 -0
- package/dist/src/clients/ctf-client.js.map +1 -0
- package/dist/src/clients/data-api.d.ts +452 -0
- package/dist/src/clients/data-api.d.ts.map +1 -0
- package/dist/src/clients/data-api.js +637 -0
- package/dist/src/clients/data-api.js.map +1 -0
- package/dist/src/clients/gamma-api.d.ts +421 -0
- package/dist/src/clients/gamma-api.d.ts.map +1 -0
- package/dist/src/clients/gamma-api.js +359 -0
- package/dist/src/clients/gamma-api.js.map +1 -0
- package/dist/src/clients/subgraph.d.ts +196 -0
- package/dist/src/clients/subgraph.d.ts.map +1 -0
- package/dist/src/clients/subgraph.js +332 -0
- package/dist/src/clients/subgraph.js.map +1 -0
- package/dist/src/core/cache-adapter-bridge.d.ts +36 -0
- package/dist/src/core/cache-adapter-bridge.d.ts.map +1 -0
- package/dist/src/core/cache-adapter-bridge.js +81 -0
- package/dist/src/core/cache-adapter-bridge.js.map +1 -0
- package/dist/src/core/cache.d.ts +43 -0
- package/dist/src/core/cache.d.ts.map +1 -0
- package/dist/src/core/cache.js +76 -0
- package/dist/src/core/cache.js.map +1 -0
- package/dist/src/core/errors.d.ts +39 -0
- package/dist/src/core/errors.d.ts.map +1 -0
- package/dist/src/core/errors.js +86 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/rate-limiter.d.ts +33 -0
- package/dist/src/core/rate-limiter.d.ts.map +1 -0
- package/dist/src/core/rate-limiter.js +82 -0
- package/dist/src/core/rate-limiter.js.map +1 -0
- package/dist/src/core/types.d.ts +506 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +49 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/types.test.d.ts +7 -0
- package/dist/src/core/types.test.d.ts.map +1 -0
- package/dist/src/core/types.test.js +122 -0
- package/dist/src/core/types.test.js.map +1 -0
- package/dist/src/core/unified-cache.d.ts +63 -0
- package/dist/src/core/unified-cache.d.ts.map +1 -0
- package/dist/src/core/unified-cache.js +114 -0
- package/dist/src/core/unified-cache.js.map +1 -0
- package/dist/src/index.d.ts +159 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +262 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/services/arbitrage-service.d.ts +409 -0
- package/dist/src/services/arbitrage-service.d.ts.map +1 -0
- package/dist/src/services/arbitrage-service.js +1450 -0
- package/dist/src/services/arbitrage-service.js.map +1 -0
- package/dist/src/services/authorization-service.d.ts +97 -0
- package/dist/src/services/authorization-service.d.ts.map +1 -0
- package/dist/src/services/authorization-service.js +279 -0
- package/dist/src/services/authorization-service.js.map +1 -0
- package/dist/src/services/binance-service.d.ts +154 -0
- package/dist/src/services/binance-service.d.ts.map +1 -0
- package/dist/src/services/binance-service.js +266 -0
- package/dist/src/services/binance-service.js.map +1 -0
- package/dist/src/services/dip-arb-service.d.ts +245 -0
- package/dist/src/services/dip-arb-service.d.ts.map +1 -0
- package/dist/src/services/dip-arb-service.js +1865 -0
- package/dist/src/services/dip-arb-service.js.map +1 -0
- package/dist/src/services/dip-arb-types.d.ts +553 -0
- package/dist/src/services/dip-arb-types.d.ts.map +1 -0
- package/dist/src/services/dip-arb-types.js +164 -0
- package/dist/src/services/dip-arb-types.js.map +1 -0
- package/dist/src/services/market-service.d.ts +370 -0
- package/dist/src/services/market-service.d.ts.map +1 -0
- package/dist/src/services/market-service.js +1200 -0
- package/dist/src/services/market-service.js.map +1 -0
- package/dist/src/services/onchain-service.d.ts +309 -0
- package/dist/src/services/onchain-service.d.ts.map +1 -0
- package/dist/src/services/onchain-service.js +417 -0
- package/dist/src/services/onchain-service.js.map +1 -0
- package/dist/src/services/realtime-service-v2.d.ts +367 -0
- package/dist/src/services/realtime-service-v2.d.ts.map +1 -0
- package/dist/src/services/realtime-service-v2.js +876 -0
- package/dist/src/services/realtime-service-v2.js.map +1 -0
- package/dist/src/services/smart-money-service.d.ts +352 -0
- package/dist/src/services/smart-money-service.d.ts.map +1 -0
- package/dist/src/services/smart-money-service.js +582 -0
- package/dist/src/services/smart-money-service.js.map +1 -0
- package/dist/src/services/swap-service.d.ts +217 -0
- package/dist/src/services/swap-service.d.ts.map +1 -0
- package/dist/src/services/swap-service.js +695 -0
- package/dist/src/services/swap-service.js.map +1 -0
- package/dist/src/services/trading-service.d.ts +177 -0
- package/dist/src/services/trading-service.d.ts.map +1 -0
- package/dist/src/services/trading-service.js +422 -0
- package/dist/src/services/trading-service.js.map +1 -0
- package/dist/src/services/wallet-service.d.ts +316 -0
- package/dist/src/services/wallet-service.d.ts.map +1 -0
- package/dist/src/services/wallet-service.js +681 -0
- package/dist/src/services/wallet-service.js.map +1 -0
- package/dist/src/utils/price-utils.d.ts +153 -0
- package/dist/src/utils/price-utils.d.ts.map +1 -0
- package/dist/src/utils/price-utils.js +236 -0
- package/dist/src/utils/price-utils.js.map +1 -0
- package/dist/src/utils/price-utils.test.d.ts +5 -0
- package/dist/src/utils/price-utils.test.d.ts.map +1 -0
- package/dist/src/utils/price-utils.test.js +192 -0
- package/dist/src/utils/price-utils.test.js.map +1 -0
- package/package.json +3 -3
|
@@ -17,6 +17,22 @@ import { PolymarketError, ErrorCode } from '../core/errors.js';
|
|
|
17
17
|
const CLOB_HOST = 'https://clob.polymarket.com';
|
|
18
18
|
// Chain IDs
|
|
19
19
|
export const POLYGON_MAINNET = 137;
|
|
20
|
+
// Mapping from underlying asset to Binance symbol
|
|
21
|
+
const UNDERLYING_TO_SYMBOL = {
|
|
22
|
+
BTC: 'BTCUSDT',
|
|
23
|
+
ETH: 'ETHUSDT',
|
|
24
|
+
SOL: 'SOLUSDT',
|
|
25
|
+
};
|
|
26
|
+
// Map from KLineInterval to BinanceInterval (Binance doesn't support 30s or 12h)
|
|
27
|
+
const KLINE_TO_BINANCE_INTERVAL = {
|
|
28
|
+
'1m': '1m',
|
|
29
|
+
'5m': '5m',
|
|
30
|
+
'15m': '15m',
|
|
31
|
+
'30m': '30m',
|
|
32
|
+
'1h': '1h',
|
|
33
|
+
'4h': '4h',
|
|
34
|
+
'1d': '1d',
|
|
35
|
+
};
|
|
20
36
|
// ============================================================================
|
|
21
37
|
// MarketService Implementation
|
|
22
38
|
// ============================================================================
|
|
@@ -26,14 +42,16 @@ export class MarketService {
|
|
|
26
42
|
rateLimiter;
|
|
27
43
|
cache;
|
|
28
44
|
config;
|
|
45
|
+
binanceService;
|
|
29
46
|
clobClient = null;
|
|
30
47
|
initialized = false;
|
|
31
|
-
constructor(gammaApi, dataApi, rateLimiter, cache, config) {
|
|
48
|
+
constructor(gammaApi, dataApi, rateLimiter, cache, config, binanceService) {
|
|
32
49
|
this.gammaApi = gammaApi;
|
|
33
50
|
this.dataApi = dataApi;
|
|
34
51
|
this.rateLimiter = rateLimiter;
|
|
35
52
|
this.cache = cache;
|
|
36
53
|
this.config = config;
|
|
54
|
+
this.binanceService = binanceService;
|
|
37
55
|
}
|
|
38
56
|
// ============================================================================
|
|
39
57
|
// Initialization
|
|
@@ -65,8 +83,20 @@ export class MarketService {
|
|
|
65
83
|
return this.cache.getOrSet(cacheKey, CACHE_TTL.MARKET_INFO, async () => {
|
|
66
84
|
const client = await this.ensureInitialized();
|
|
67
85
|
return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
try {
|
|
87
|
+
const market = await client.getMarket(conditionId);
|
|
88
|
+
if (!market || !market.tokens) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return this.normalizeClobMarket(market);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Handle 404 "market not found" gracefully
|
|
95
|
+
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
70
100
|
});
|
|
71
101
|
});
|
|
72
102
|
}
|
|
@@ -156,8 +186,12 @@ export class MarketService {
|
|
|
156
186
|
*/
|
|
157
187
|
async getProcessedOrderbook(conditionId) {
|
|
158
188
|
const market = await this.getClobMarket(conditionId);
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
if (!market) {
|
|
190
|
+
throw new PolymarketError(ErrorCode.MARKET_NOT_FOUND, `Market not found: ${conditionId}`);
|
|
191
|
+
}
|
|
192
|
+
// Use index-based access instead of name-based (supports Yes/No, Up/Down, Team1/Team2, etc.)
|
|
193
|
+
const yesToken = market.tokens[0]; // primary outcome
|
|
194
|
+
const noToken = market.tokens[1]; // secondary outcome
|
|
161
195
|
if (!yesToken || !noToken) {
|
|
162
196
|
throw new PolymarketError(ErrorCode.INVALID_RESPONSE, 'Missing tokens in market');
|
|
163
197
|
}
|
|
@@ -251,7 +285,10 @@ export class MarketService {
|
|
|
251
285
|
}
|
|
252
286
|
try {
|
|
253
287
|
const clobMarket = await this.getClobMarket(gammaMarket.conditionId);
|
|
254
|
-
|
|
288
|
+
if (clobMarket) {
|
|
289
|
+
return this.mergeMarkets(gammaMarket, clobMarket);
|
|
290
|
+
}
|
|
291
|
+
return this.fromGammaMarket(gammaMarket);
|
|
255
292
|
}
|
|
256
293
|
catch {
|
|
257
294
|
return this.fromGammaMarket(gammaMarket);
|
|
@@ -303,12 +340,36 @@ export class MarketService {
|
|
|
303
340
|
// ===== K-Line Aggregation =====
|
|
304
341
|
/**
|
|
305
342
|
* Get K-Line candles for a market (single token)
|
|
343
|
+
*
|
|
344
|
+
* @param conditionId - Market condition ID
|
|
345
|
+
* @param interval - K-line interval (1s, 5s, 15s, 30s, 1m, 5m, 15m, 30m, 1h, 4h, 12h, 1d)
|
|
346
|
+
* @param options - Query options
|
|
347
|
+
* @param options.limit - Maximum number of trades to fetch for aggregation (default: 1000)
|
|
348
|
+
* @param options.tokenId - Filter by specific token ID
|
|
349
|
+
* @param options.outcomeIndex - Filter by outcome index (0 = primary, 1 = secondary)
|
|
350
|
+
* @param options.startTimestamp - Start timestamp (Unix ms) - filter trades after this time
|
|
351
|
+
* @param options.endTimestamp - End timestamp (Unix ms) - filter trades before this time
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```typescript
|
|
355
|
+
* // Get 5s candles for the last 15 minutes
|
|
356
|
+
* const now = Date.now();
|
|
357
|
+
* const candles = await sdk.markets.getKLines(conditionId, '5s', {
|
|
358
|
+
* startTimestamp: now - 15 * 60 * 1000,
|
|
359
|
+
* endTimestamp: now,
|
|
360
|
+
* });
|
|
361
|
+
* ```
|
|
306
362
|
*/
|
|
307
363
|
async getKLines(conditionId, interval, options) {
|
|
308
364
|
if (!this.dataApi) {
|
|
309
365
|
throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'DataApiClient is required for K-Line data');
|
|
310
366
|
}
|
|
311
|
-
const trades = await this.dataApi.
|
|
367
|
+
const trades = await this.dataApi.getTrades({
|
|
368
|
+
market: conditionId,
|
|
369
|
+
limit: options?.limit || 1000,
|
|
370
|
+
startTimestamp: options?.startTimestamp,
|
|
371
|
+
endTimestamp: options?.endTimestamp,
|
|
372
|
+
});
|
|
312
373
|
// Filter by token/outcome if specified
|
|
313
374
|
let filteredTrades = trades;
|
|
314
375
|
if (options?.tokenId) {
|
|
@@ -321,16 +382,40 @@ export class MarketService {
|
|
|
321
382
|
}
|
|
322
383
|
/**
|
|
323
384
|
* Get dual K-Lines (YES + NO tokens)
|
|
385
|
+
*
|
|
386
|
+
* @param conditionId - Market condition ID
|
|
387
|
+
* @param interval - K-line interval (1s, 5s, 15s, 30s, 1m, 5m, 15m, 30m, 1h, 4h, 12h, 1d)
|
|
388
|
+
* @param options - Query options
|
|
389
|
+
* @param options.limit - Maximum number of trades to fetch for aggregation (default: 1000)
|
|
390
|
+
* @param options.startTimestamp - Start timestamp (Unix ms) - filter trades after this time
|
|
391
|
+
* @param options.endTimestamp - End timestamp (Unix ms) - filter trades before this time
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* // Get 15s dual K-lines for a 15-minute market
|
|
396
|
+
* const now = Date.now();
|
|
397
|
+
* const data = await sdk.markets.getDualKLines(conditionId, '15s', {
|
|
398
|
+
* startTimestamp: now - 15 * 60 * 1000,
|
|
399
|
+
* endTimestamp: now,
|
|
400
|
+
* });
|
|
401
|
+
* console.log(`Up candles: ${data.yes.length}, Down candles: ${data.no.length}`);
|
|
402
|
+
* ```
|
|
324
403
|
*/
|
|
325
404
|
async getDualKLines(conditionId, interval, options) {
|
|
326
405
|
if (!this.dataApi) {
|
|
327
406
|
throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'DataApiClient is required for K-Line data');
|
|
328
407
|
}
|
|
329
408
|
const market = await this.getMarket(conditionId);
|
|
330
|
-
const trades = await this.dataApi.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
409
|
+
const trades = await this.dataApi.getTrades({
|
|
410
|
+
market: conditionId,
|
|
411
|
+
limit: options?.limit || 1000,
|
|
412
|
+
startTimestamp: options?.startTimestamp,
|
|
413
|
+
endTimestamp: options?.endTimestamp,
|
|
414
|
+
});
|
|
415
|
+
// Separate trades by outcome using index (more reliable than name matching)
|
|
416
|
+
// outcomeIndex 0 = primary (Yes/Up/Team1), outcomeIndex 1 = secondary (No/Down/Team2)
|
|
417
|
+
const yesTrades = trades.filter((t) => t.outcomeIndex === 0);
|
|
418
|
+
const noTrades = trades.filter((t) => t.outcomeIndex === 1);
|
|
334
419
|
const yesCandles = this.aggregateToKLines(yesTrades, interval);
|
|
335
420
|
const noCandles = this.aggregateToKLines(noTrades, interval);
|
|
336
421
|
// Get current orderbook for real-time spread analysis
|
|
@@ -356,6 +441,184 @@ export class MarketService {
|
|
|
356
441
|
currentOrderbook,
|
|
357
442
|
};
|
|
358
443
|
}
|
|
444
|
+
// ===== Token vs Underlying Correlation =====
|
|
445
|
+
/**
|
|
446
|
+
* Get aligned K-line data for token and underlying asset
|
|
447
|
+
*
|
|
448
|
+
* This method fetches K-line data from both Polymarket (token prices)
|
|
449
|
+
* and Binance (underlying asset prices), aligns them by timestamp,
|
|
450
|
+
* and optionally calculates Pearson correlation coefficients.
|
|
451
|
+
*
|
|
452
|
+
* @param conditionId - Market condition ID
|
|
453
|
+
* @param underlying - Underlying asset (BTC, ETH, SOL)
|
|
454
|
+
* @param interval - K-line interval (must be supported by both Poly and Binance)
|
|
455
|
+
* @param options - Optional parameters
|
|
456
|
+
* @returns Aligned data with optional correlation coefficients
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* const data = await marketService.getTokenUnderlyingData(
|
|
461
|
+
* '0x123...',
|
|
462
|
+
* 'BTC',
|
|
463
|
+
* '1h',
|
|
464
|
+
* { limit: 100, calculateCorrelation: true }
|
|
465
|
+
* );
|
|
466
|
+
*
|
|
467
|
+
* // Access aligned data
|
|
468
|
+
* for (const point of data.data) {
|
|
469
|
+
* console.log(`${point.timestamp}: Up=${point.upPrice}, BTC=${point.underlyingPrice}`);
|
|
470
|
+
* }
|
|
471
|
+
*
|
|
472
|
+
* // Check correlation
|
|
473
|
+
* if (data.correlation) {
|
|
474
|
+
* console.log(`Correlation: ${data.correlation.upVsUnderlying}`);
|
|
475
|
+
* }
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
async getTokenUnderlyingData(conditionId, underlying, interval, options) {
|
|
479
|
+
// Validate BinanceService is available
|
|
480
|
+
if (!this.binanceService) {
|
|
481
|
+
throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'BinanceService is required for token-underlying correlation analysis');
|
|
482
|
+
}
|
|
483
|
+
// Validate interval is supported by Binance
|
|
484
|
+
const binanceInterval = KLINE_TO_BINANCE_INTERVAL[interval];
|
|
485
|
+
if (!binanceInterval) {
|
|
486
|
+
throw new PolymarketError(ErrorCode.INVALID_CONFIG, `Interval ${interval} is not supported for correlation analysis. ` +
|
|
487
|
+
`Supported intervals: ${Object.keys(KLINE_TO_BINANCE_INTERVAL).join(', ')}`);
|
|
488
|
+
}
|
|
489
|
+
const limit = options?.limit || 500;
|
|
490
|
+
// Fetch data in parallel
|
|
491
|
+
const [dualKLines, binanceKLines] = await Promise.all([
|
|
492
|
+
this.getDualKLines(conditionId, interval, { limit }),
|
|
493
|
+
this.binanceService.getKLines(UNDERLYING_TO_SYMBOL[underlying], binanceInterval, { limit }),
|
|
494
|
+
]);
|
|
495
|
+
// Create maps for quick lookup
|
|
496
|
+
const upMap = new Map(dualKLines.yes.map(c => [c.timestamp, c.close]));
|
|
497
|
+
const downMap = new Map(dualKLines.no.map(c => [c.timestamp, c.close]));
|
|
498
|
+
const binanceMap = new Map(binanceKLines.map(c => [c.timestamp, c.close]));
|
|
499
|
+
// Get all unique timestamps and sort them
|
|
500
|
+
const allTimestamps = new Set([
|
|
501
|
+
...upMap.keys(),
|
|
502
|
+
...downMap.keys(),
|
|
503
|
+
...binanceMap.keys(),
|
|
504
|
+
]);
|
|
505
|
+
const sortedTimestamps = [...allTimestamps].sort((a, b) => a - b);
|
|
506
|
+
// Find the first Binance price for calculating percentage change
|
|
507
|
+
const firstBinancePrice = binanceKLines.length > 0 ? binanceKLines[0].close : 0;
|
|
508
|
+
// Align data points
|
|
509
|
+
const alignedData = [];
|
|
510
|
+
let lastUpPrice;
|
|
511
|
+
let lastDownPrice;
|
|
512
|
+
let lastBinancePrice;
|
|
513
|
+
for (const timestamp of sortedTimestamps) {
|
|
514
|
+
// Get prices, falling back to previous values if not available
|
|
515
|
+
const upPrice = upMap.get(timestamp) ?? this.findNearestPrice(timestamp, upMap, sortedTimestamps);
|
|
516
|
+
const downPrice = downMap.get(timestamp) ?? this.findNearestPrice(timestamp, downMap, sortedTimestamps);
|
|
517
|
+
const binancePrice = binanceMap.get(timestamp) ?? this.findNearestPrice(timestamp, binanceMap, sortedTimestamps);
|
|
518
|
+
// Update last known prices
|
|
519
|
+
if (upPrice !== undefined)
|
|
520
|
+
lastUpPrice = upPrice;
|
|
521
|
+
if (downPrice !== undefined)
|
|
522
|
+
lastDownPrice = downPrice;
|
|
523
|
+
if (binancePrice !== undefined)
|
|
524
|
+
lastBinancePrice = binancePrice;
|
|
525
|
+
// Skip if we don't have underlying price
|
|
526
|
+
if (lastBinancePrice === undefined)
|
|
527
|
+
continue;
|
|
528
|
+
const priceSum = (lastUpPrice !== undefined && lastDownPrice !== undefined)
|
|
529
|
+
? lastUpPrice + lastDownPrice
|
|
530
|
+
: undefined;
|
|
531
|
+
const underlyingChange = firstBinancePrice > 0
|
|
532
|
+
? ((lastBinancePrice - firstBinancePrice) / firstBinancePrice) * 100
|
|
533
|
+
: 0;
|
|
534
|
+
alignedData.push({
|
|
535
|
+
timestamp,
|
|
536
|
+
upPrice: lastUpPrice,
|
|
537
|
+
downPrice: lastDownPrice,
|
|
538
|
+
priceSum,
|
|
539
|
+
underlyingPrice: lastBinancePrice,
|
|
540
|
+
underlyingChange,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
// Calculate correlation if requested
|
|
544
|
+
let correlation;
|
|
545
|
+
if (options?.calculateCorrelation && alignedData.length >= 2) {
|
|
546
|
+
correlation = this.calculatePearsonCorrelation(alignedData);
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
conditionId,
|
|
550
|
+
underlying,
|
|
551
|
+
interval,
|
|
552
|
+
data: alignedData,
|
|
553
|
+
correlation,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Find the nearest available price for a timestamp
|
|
558
|
+
*/
|
|
559
|
+
findNearestPrice(targetTimestamp, priceMap, sortedTimestamps) {
|
|
560
|
+
if (priceMap.size === 0)
|
|
561
|
+
return undefined;
|
|
562
|
+
// Find the nearest timestamp that has a price
|
|
563
|
+
let nearestTimestamp;
|
|
564
|
+
let minDiff = Infinity;
|
|
565
|
+
for (const ts of sortedTimestamps) {
|
|
566
|
+
if (priceMap.has(ts)) {
|
|
567
|
+
const diff = Math.abs(ts - targetTimestamp);
|
|
568
|
+
if (diff < minDiff) {
|
|
569
|
+
minDiff = diff;
|
|
570
|
+
nearestTimestamp = ts;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return nearestTimestamp !== undefined ? priceMap.get(nearestTimestamp) : undefined;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Calculate Pearson correlation coefficients
|
|
578
|
+
*/
|
|
579
|
+
calculatePearsonCorrelation(data) {
|
|
580
|
+
// Filter data points that have all required prices
|
|
581
|
+
const upData = data.filter(d => d.upPrice !== undefined && d.underlyingPrice !== undefined);
|
|
582
|
+
const downData = data.filter(d => d.downPrice !== undefined && d.underlyingPrice !== undefined);
|
|
583
|
+
const upVsUnderlying = this.pearson(upData.map(d => d.upPrice), upData.map(d => d.underlyingPrice));
|
|
584
|
+
const downVsUnderlying = this.pearson(downData.map(d => d.downPrice), downData.map(d => d.underlyingPrice));
|
|
585
|
+
return {
|
|
586
|
+
upVsUnderlying,
|
|
587
|
+
downVsUnderlying,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Calculate Pearson correlation coefficient between two arrays
|
|
592
|
+
* Returns a value between -1 and 1
|
|
593
|
+
*/
|
|
594
|
+
pearson(x, y) {
|
|
595
|
+
const n = Math.min(x.length, y.length);
|
|
596
|
+
if (n < 2)
|
|
597
|
+
return 0;
|
|
598
|
+
// Calculate means
|
|
599
|
+
let sumX = 0, sumY = 0;
|
|
600
|
+
for (let i = 0; i < n; i++) {
|
|
601
|
+
sumX += x[i];
|
|
602
|
+
sumY += y[i];
|
|
603
|
+
}
|
|
604
|
+
const meanX = sumX / n;
|
|
605
|
+
const meanY = sumY / n;
|
|
606
|
+
// Calculate correlation
|
|
607
|
+
let numerator = 0;
|
|
608
|
+
let sumSqX = 0;
|
|
609
|
+
let sumSqY = 0;
|
|
610
|
+
for (let i = 0; i < n; i++) {
|
|
611
|
+
const dx = x[i] - meanX;
|
|
612
|
+
const dy = y[i] - meanY;
|
|
613
|
+
numerator += dx * dy;
|
|
614
|
+
sumSqX += dx * dx;
|
|
615
|
+
sumSqY += dy * dy;
|
|
616
|
+
}
|
|
617
|
+
const denominator = Math.sqrt(sumSqX * sumSqY);
|
|
618
|
+
if (denominator === 0)
|
|
619
|
+
return 0;
|
|
620
|
+
return numerator / denominator;
|
|
621
|
+
}
|
|
359
622
|
/**
|
|
360
623
|
* Aggregate trades into K-Line candles
|
|
361
624
|
*/
|
|
@@ -540,6 +803,148 @@ export class MarketService {
|
|
|
540
803
|
}
|
|
541
804
|
return this.gammaApi.getMarkets(params);
|
|
542
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* Scan for short-term crypto markets (Up/Down markets ending soon)
|
|
808
|
+
*
|
|
809
|
+
* ## Market Types
|
|
810
|
+
* Polymarket has short-term crypto markets in two durations:
|
|
811
|
+
* - **5-minute markets**: slug pattern `{coin}-updown-5m-{timestamp}`
|
|
812
|
+
* - **15-minute markets**: slug pattern `{coin}-updown-15m-{timestamp}`
|
|
813
|
+
*
|
|
814
|
+
* ## Slug Pattern
|
|
815
|
+
* The timestamp in the slug is the START time of the time window:
|
|
816
|
+
* - 15-minute markets: `{coin}-updown-15m-{Math.floor(startTime / 900) * 900}`
|
|
817
|
+
* - 5-minute markets: `{coin}-updown-5m-{Math.floor(startTime / 300) * 300}`
|
|
818
|
+
*
|
|
819
|
+
* Example: `btc-updown-15m-1767456000` starts at 1767456000 (16:00:00 UTC)
|
|
820
|
+
* and ends 15 minutes later at 1767456900 (16:15:00 UTC)
|
|
821
|
+
*
|
|
822
|
+
* ## Supported Coins
|
|
823
|
+
* - BTC (Bitcoin)
|
|
824
|
+
* - ETH (Ethereum)
|
|
825
|
+
* - SOL (Solana)
|
|
826
|
+
* - XRP (Ripple)
|
|
827
|
+
*
|
|
828
|
+
* ## Market Lifecycle Rules
|
|
829
|
+
* 1. Markets are created ahead of time (before they become tradeable)
|
|
830
|
+
* 2. New markets may not have prices yet (show 0.5/0.5)
|
|
831
|
+
* 3. When one market ends, the next one is already open for trading
|
|
832
|
+
* 4. A market ending doesn't mean no price - it means resolution is pending
|
|
833
|
+
*
|
|
834
|
+
* ## Outcomes
|
|
835
|
+
* All crypto short-term markets have:
|
|
836
|
+
* - outcomes: ["Up", "Down"]
|
|
837
|
+
* - Resolution based on price movement during the time window
|
|
838
|
+
*
|
|
839
|
+
* @param options - Scan options
|
|
840
|
+
* @param options.minMinutesUntilEnd - Minimum minutes until market ends (default: 5)
|
|
841
|
+
* @param options.maxMinutesUntilEnd - Maximum minutes until market ends (default: 60)
|
|
842
|
+
* @param options.limit - Maximum number of markets to return (default: 20)
|
|
843
|
+
* @param options.sortBy - Sort field: 'endDate' | 'volume' | 'liquidity' (default: 'endDate')
|
|
844
|
+
* @param options.duration - Filter by duration: '5m' | '15m' | 'all' (default: 'all')
|
|
845
|
+
* @param options.coin - Filter by coin: 'BTC' | 'ETH' | 'SOL' | 'XRP' | 'all' (default: 'all')
|
|
846
|
+
* @returns Array of crypto short-term markets
|
|
847
|
+
*
|
|
848
|
+
* @example
|
|
849
|
+
* ```typescript
|
|
850
|
+
* // Find all 15-minute markets ending in 5-30 minutes
|
|
851
|
+
* const markets = await sdk.markets.scanCryptoShortTermMarkets({
|
|
852
|
+
* minMinutesUntilEnd: 5,
|
|
853
|
+
* maxMinutesUntilEnd: 30,
|
|
854
|
+
* duration: '15m',
|
|
855
|
+
* });
|
|
856
|
+
*
|
|
857
|
+
* // Find BTC 5-minute markets only
|
|
858
|
+
* const btcMarkets = await sdk.markets.scanCryptoShortTermMarkets({
|
|
859
|
+
* coin: 'BTC',
|
|
860
|
+
* duration: '5m',
|
|
861
|
+
* });
|
|
862
|
+
* ```
|
|
863
|
+
*/
|
|
864
|
+
async scanCryptoShortTermMarkets(options) {
|
|
865
|
+
if (!this.gammaApi) {
|
|
866
|
+
throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'GammaApiClient is required for market scanning');
|
|
867
|
+
}
|
|
868
|
+
const { minMinutesUntilEnd = 5, maxMinutesUntilEnd = 60, limit = 20, sortBy = 'endDate', duration = 'all', coin = 'all', } = options ?? {};
|
|
869
|
+
// Duration to interval seconds mapping
|
|
870
|
+
const durationIntervals = {
|
|
871
|
+
'5m': 300, // 5 minutes in seconds
|
|
872
|
+
'15m': 900, // 15 minutes in seconds
|
|
873
|
+
};
|
|
874
|
+
// Supported coins
|
|
875
|
+
const allCoins = ['btc', 'eth', 'sol', 'xrp'];
|
|
876
|
+
const targetCoins = coin === 'all' ? allCoins : [coin.toLowerCase()];
|
|
877
|
+
// Target durations
|
|
878
|
+
const targetDurations = duration === 'all' ? ['5m', '15m'] : [duration];
|
|
879
|
+
// Calculate time slots to fetch
|
|
880
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
881
|
+
const minEndSeconds = nowSeconds + minMinutesUntilEnd * 60;
|
|
882
|
+
const maxEndSeconds = nowSeconds + maxMinutesUntilEnd * 60;
|
|
883
|
+
// Generate slugs for all combinations
|
|
884
|
+
const slugsToFetch = [];
|
|
885
|
+
for (const dur of targetDurations) {
|
|
886
|
+
const intervalSeconds = durationIntervals[dur];
|
|
887
|
+
const durationStr = dur.replace('m', 'm'); // 5m or 15m
|
|
888
|
+
// Calculate the current slot and extend to cover the time range
|
|
889
|
+
// The slug timestamp is the START time, endTime = startTime + interval
|
|
890
|
+
// So if we want markets ending after minEndSeconds:
|
|
891
|
+
// startTime + interval >= minEndSeconds => startTime >= minEndSeconds - interval
|
|
892
|
+
// And ending before maxEndSeconds:
|
|
893
|
+
// startTime + interval <= maxEndSeconds => startTime <= maxEndSeconds - interval
|
|
894
|
+
const minSlotStart = Math.floor((minEndSeconds - intervalSeconds) / intervalSeconds) * intervalSeconds;
|
|
895
|
+
const maxSlotStart = Math.ceil(maxEndSeconds / intervalSeconds) * intervalSeconds;
|
|
896
|
+
// Generate slots from minSlotStart to maxSlotStart
|
|
897
|
+
for (let slotStart = minSlotStart; slotStart <= maxSlotStart; slotStart += intervalSeconds) {
|
|
898
|
+
for (const coinName of targetCoins) {
|
|
899
|
+
slugsToFetch.push(`${coinName}-updown-${durationStr}-${slotStart}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
// Fetch markets in parallel batches
|
|
904
|
+
const BATCH_SIZE = 10;
|
|
905
|
+
const allMarkets = [];
|
|
906
|
+
for (let i = 0; i < slugsToFetch.length; i += BATCH_SIZE) {
|
|
907
|
+
const batch = slugsToFetch.slice(i, i + BATCH_SIZE);
|
|
908
|
+
const results = await Promise.all(batch.map(async (slug) => {
|
|
909
|
+
try {
|
|
910
|
+
const markets = await this.gammaApi.getMarkets({ slug, limit: 1 });
|
|
911
|
+
return markets.length > 0 ? markets[0] : null;
|
|
912
|
+
}
|
|
913
|
+
catch {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}));
|
|
917
|
+
for (const market of results) {
|
|
918
|
+
if (market && market.active && !market.closed) {
|
|
919
|
+
allMarkets.push(market);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
// Filter by end time range
|
|
924
|
+
const nowMs = Date.now();
|
|
925
|
+
const minEndTime = nowMs + minMinutesUntilEnd * 60 * 1000;
|
|
926
|
+
const maxEndTime = nowMs + maxMinutesUntilEnd * 60 * 1000;
|
|
927
|
+
const filteredMarkets = allMarkets.filter((market) => {
|
|
928
|
+
const endTime = market.endDate ? new Date(market.endDate).getTime() : 0;
|
|
929
|
+
return endTime >= minEndTime && endTime <= maxEndTime;
|
|
930
|
+
});
|
|
931
|
+
// Sort by preference
|
|
932
|
+
if (sortBy === 'volume') {
|
|
933
|
+
filteredMarkets.sort((a, b) => (b.volume24hr ?? 0) - (a.volume24hr ?? 0));
|
|
934
|
+
}
|
|
935
|
+
else if (sortBy === 'liquidity') {
|
|
936
|
+
filteredMarkets.sort((a, b) => (b.liquidity ?? 0) - (a.liquidity ?? 0));
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
// Sort by endDate (soonest first)
|
|
940
|
+
filteredMarkets.sort((a, b) => {
|
|
941
|
+
const aEnd = a.endDate ? new Date(a.endDate).getTime() : Infinity;
|
|
942
|
+
const bEnd = b.endDate ? new Date(b.endDate).getTime() : Infinity;
|
|
943
|
+
return aEnd - bEnd;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return filteredMarkets.slice(0, limit);
|
|
947
|
+
}
|
|
543
948
|
// ===== Market Signal Detection =====
|
|
544
949
|
/**
|
|
545
950
|
* Detect market signals (volume surge, depth imbalance, whale trades)
|
|
@@ -706,10 +1111,12 @@ export class MarketService {
|
|
|
706
1111
|
};
|
|
707
1112
|
}
|
|
708
1113
|
fromGammaMarket(gamma) {
|
|
709
|
-
// Create tokens from Gamma outcomes
|
|
1114
|
+
// Create tokens from Gamma outcomes - use actual outcome names from gamma data
|
|
1115
|
+
// This supports Yes/No, Up/Down, Team1/Team2, Heads/Tails, etc.
|
|
1116
|
+
const outcomes = gamma.outcomes || ['Yes', 'No'];
|
|
710
1117
|
const tokens = [
|
|
711
|
-
{ tokenId: '', outcome:
|
|
712
|
-
{ tokenId: '', outcome:
|
|
1118
|
+
{ tokenId: '', outcome: outcomes[0], price: gamma.outcomePrices[0] || 0.5 },
|
|
1119
|
+
{ tokenId: '', outcome: outcomes[1], price: gamma.outcomePrices[1] || 0.5 },
|
|
713
1120
|
];
|
|
714
1121
|
return {
|
|
715
1122
|
conditionId: gamma.conditionId,
|
|
@@ -759,11 +1166,17 @@ export class MarketService {
|
|
|
759
1166
|
// ===== Utility Functions =====
|
|
760
1167
|
export function getIntervalMs(interval) {
|
|
761
1168
|
const map = {
|
|
1169
|
+
// Second-level intervals (for 15-minute crypto markets)
|
|
1170
|
+
'1s': 1 * 1000,
|
|
1171
|
+
'5s': 5 * 1000,
|
|
1172
|
+
'15s': 15 * 1000,
|
|
762
1173
|
'30s': 30 * 1000,
|
|
1174
|
+
// Minute-level intervals
|
|
763
1175
|
'1m': 60 * 1000,
|
|
764
1176
|
'5m': 5 * 60 * 1000,
|
|
765
1177
|
'15m': 15 * 60 * 1000,
|
|
766
1178
|
'30m': 30 * 60 * 1000,
|
|
1179
|
+
// Hour-level intervals
|
|
767
1180
|
'1h': 60 * 60 * 1000,
|
|
768
1181
|
'4h': 4 * 60 * 60 * 1000,
|
|
769
1182
|
'12h': 12 * 60 * 60 * 1000,
|