@catalyst-team/poly-sdk 0.2.1 → 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.
Files changed (277) hide show
  1. package/README.md +665 -807
  2. package/README.zh-CN.md +645 -342
  3. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts +12 -0
  4. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts.map +1 -0
  5. package/dist/__tests__/integration/arbitrage-service.integration.test.js +267 -0
  6. package/dist/__tests__/integration/arbitrage-service.integration.test.js.map +1 -0
  7. package/dist/__tests__/integration/data-api.integration.test.js +6 -3
  8. package/dist/__tests__/integration/data-api.integration.test.js.map +1 -1
  9. package/dist/__tests__/integration/market-service.integration.test.d.ts +10 -0
  10. package/dist/__tests__/integration/market-service.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/integration/market-service.integration.test.js +173 -0
  12. package/dist/__tests__/integration/market-service.integration.test.js.map +1 -0
  13. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts +10 -0
  14. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +1 -0
  15. package/dist/__tests__/integration/realtime-service-v2.integration.test.js +307 -0
  16. package/dist/__tests__/integration/realtime-service-v2.integration.test.js.map +1 -0
  17. package/dist/__tests__/integration/trading-service.integration.test.d.ts +10 -0
  18. package/dist/__tests__/integration/trading-service.integration.test.d.ts.map +1 -0
  19. package/dist/__tests__/integration/trading-service.integration.test.js +58 -0
  20. package/dist/__tests__/integration/trading-service.integration.test.js.map +1 -0
  21. package/dist/clients/clob-api.d.ts +73 -0
  22. package/dist/clients/clob-api.d.ts.map +1 -1
  23. package/dist/clients/clob-api.js +60 -0
  24. package/dist/clients/clob-api.js.map +1 -1
  25. package/dist/clients/ctf-client.d.ts +6 -4
  26. package/dist/clients/ctf-client.d.ts.map +1 -1
  27. package/dist/clients/ctf-client.js.map +1 -1
  28. package/dist/clients/data-api.d.ts +333 -15
  29. package/dist/clients/data-api.d.ts.map +1 -1
  30. package/dist/clients/data-api.js +398 -26
  31. package/dist/clients/data-api.js.map +1 -1
  32. package/dist/clients/gamma-api.d.ts +5 -0
  33. package/dist/clients/gamma-api.d.ts.map +1 -1
  34. package/dist/clients/gamma-api.js +2 -0
  35. package/dist/clients/gamma-api.js.map +1 -1
  36. package/dist/clients/subgraph.d.ts +196 -0
  37. package/dist/clients/subgraph.d.ts.map +1 -0
  38. package/dist/clients/subgraph.js +332 -0
  39. package/dist/clients/subgraph.js.map +1 -0
  40. package/dist/clients/websocket-manager.d.ts +3 -0
  41. package/dist/clients/websocket-manager.d.ts.map +1 -1
  42. package/dist/clients/websocket-manager.js +10 -3
  43. package/dist/clients/websocket-manager.js.map +1 -1
  44. package/dist/core/cache.d.ts +3 -0
  45. package/dist/core/cache.d.ts.map +1 -1
  46. package/dist/core/cache.js +5 -0
  47. package/dist/core/cache.js.map +1 -1
  48. package/dist/core/errors.d.ts +2 -1
  49. package/dist/core/errors.d.ts.map +1 -1
  50. package/dist/core/errors.js +2 -0
  51. package/dist/core/errors.js.map +1 -1
  52. package/dist/core/rate-limiter.d.ts +3 -1
  53. package/dist/core/rate-limiter.d.ts.map +1 -1
  54. package/dist/core/rate-limiter.js +12 -0
  55. package/dist/core/rate-limiter.js.map +1 -1
  56. package/dist/core/types.d.ts +205 -13
  57. package/dist/core/types.d.ts.map +1 -1
  58. package/dist/core/types.js +30 -0
  59. package/dist/core/types.js.map +1 -1
  60. package/dist/core/types.test.d.ts +7 -0
  61. package/dist/core/types.test.d.ts.map +1 -0
  62. package/dist/core/types.test.js +122 -0
  63. package/dist/core/types.test.js.map +1 -0
  64. package/dist/index.d.ts +84 -18
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +139 -132
  67. package/dist/index.js.map +1 -1
  68. package/dist/scripts/dip-arb/auto-trade.d.ts +20 -0
  69. package/dist/scripts/dip-arb/auto-trade.d.ts.map +1 -0
  70. package/dist/scripts/dip-arb/auto-trade.js +373 -0
  71. package/dist/scripts/dip-arb/auto-trade.js.map +1 -0
  72. package/dist/scripts/dip-arb/example-basic.d.ts +30 -0
  73. package/dist/scripts/dip-arb/example-basic.d.ts.map +1 -0
  74. package/dist/scripts/dip-arb/example-basic.js +222 -0
  75. package/dist/scripts/dip-arb/example-basic.js.map +1 -0
  76. package/dist/scripts/dip-arb/redeem-positions.d.ts +11 -0
  77. package/dist/scripts/dip-arb/redeem-positions.d.ts.map +1 -0
  78. package/dist/scripts/dip-arb/redeem-positions.js +201 -0
  79. package/dist/scripts/dip-arb/redeem-positions.js.map +1 -0
  80. package/dist/scripts/dip-arb/scan-markets.d.ts +6 -0
  81. package/dist/scripts/dip-arb/scan-markets.d.ts.map +1 -0
  82. package/dist/scripts/dip-arb/scan-markets.js +73 -0
  83. package/dist/scripts/dip-arb/scan-markets.js.map +1 -0
  84. package/dist/services/arbitrage-service.d.ts +3 -2
  85. package/dist/services/arbitrage-service.d.ts.map +1 -1
  86. package/dist/services/arbitrage-service.js +71 -43
  87. package/dist/services/arbitrage-service.js.map +1 -1
  88. package/dist/services/binance-service.d.ts +154 -0
  89. package/dist/services/binance-service.d.ts.map +1 -0
  90. package/dist/services/binance-service.js +266 -0
  91. package/dist/services/binance-service.js.map +1 -0
  92. package/dist/services/dip-arb-service.d.ts +209 -0
  93. package/dist/services/dip-arb-service.d.ts.map +1 -0
  94. package/dist/services/dip-arb-service.js +1602 -0
  95. package/dist/services/dip-arb-service.js.map +1 -0
  96. package/dist/services/dip-arb-types.d.ts +553 -0
  97. package/dist/services/dip-arb-types.d.ts.map +1 -0
  98. package/dist/services/dip-arb-types.js +164 -0
  99. package/dist/services/dip-arb-types.js.map +1 -0
  100. package/dist/services/market-service.d.ts +267 -8
  101. package/dist/services/market-service.d.ts.map +1 -1
  102. package/dist/services/market-service.js +771 -42
  103. package/dist/services/market-service.js.map +1 -1
  104. package/dist/services/onchain-service.d.ts +309 -0
  105. package/dist/services/onchain-service.d.ts.map +1 -0
  106. package/dist/services/onchain-service.js +417 -0
  107. package/dist/services/onchain-service.js.map +1 -0
  108. package/dist/services/realtime-service-v2.d.ts +362 -0
  109. package/dist/services/realtime-service-v2.d.ts.map +1 -0
  110. package/dist/services/realtime-service-v2.js +858 -0
  111. package/dist/services/realtime-service-v2.js.map +1 -0
  112. package/dist/services/realtime-service.d.ts +17 -17
  113. package/dist/services/realtime-service.d.ts.map +1 -1
  114. package/dist/services/realtime-service.js +91 -59
  115. package/dist/services/realtime-service.js.map +1 -1
  116. package/dist/services/smart-money-service.d.ts +352 -0
  117. package/dist/services/smart-money-service.d.ts.map +1 -0
  118. package/dist/services/smart-money-service.js +582 -0
  119. package/dist/services/smart-money-service.js.map +1 -0
  120. package/dist/services/trading-service.d.ts +177 -0
  121. package/dist/services/trading-service.d.ts.map +1 -0
  122. package/dist/services/trading-service.js +422 -0
  123. package/dist/services/trading-service.js.map +1 -0
  124. package/dist/services/wallet-service.d.ts +225 -3
  125. package/dist/services/wallet-service.d.ts.map +1 -1
  126. package/dist/services/wallet-service.js +511 -3
  127. package/dist/services/wallet-service.js.map +1 -1
  128. package/dist/src/__tests__/integration/arbitrage-service.integration.test.d.ts +12 -0
  129. package/dist/src/__tests__/integration/arbitrage-service.integration.test.d.ts.map +1 -0
  130. package/dist/src/__tests__/integration/arbitrage-service.integration.test.js +267 -0
  131. package/dist/src/__tests__/integration/arbitrage-service.integration.test.js.map +1 -0
  132. package/dist/src/__tests__/integration/bridge-client.integration.test.d.ts +11 -0
  133. package/dist/src/__tests__/integration/bridge-client.integration.test.d.ts.map +1 -0
  134. package/dist/src/__tests__/integration/bridge-client.integration.test.js +260 -0
  135. package/dist/src/__tests__/integration/bridge-client.integration.test.js.map +1 -0
  136. package/dist/src/__tests__/integration/ctf-client.integration.test.d.ts +17 -0
  137. package/dist/src/__tests__/integration/ctf-client.integration.test.d.ts.map +1 -0
  138. package/dist/src/__tests__/integration/ctf-client.integration.test.js +234 -0
  139. package/dist/src/__tests__/integration/ctf-client.integration.test.js.map +1 -0
  140. package/dist/src/__tests__/integration/data-api.integration.test.d.ts +9 -0
  141. package/dist/src/__tests__/integration/data-api.integration.test.d.ts.map +1 -0
  142. package/dist/src/__tests__/integration/data-api.integration.test.js +164 -0
  143. package/dist/src/__tests__/integration/data-api.integration.test.js.map +1 -0
  144. package/dist/src/__tests__/integration/gamma-api.integration.test.d.ts +9 -0
  145. package/dist/src/__tests__/integration/gamma-api.integration.test.d.ts.map +1 -0
  146. package/dist/src/__tests__/integration/gamma-api.integration.test.js +170 -0
  147. package/dist/src/__tests__/integration/gamma-api.integration.test.js.map +1 -0
  148. package/dist/src/__tests__/integration/market-service.integration.test.d.ts +10 -0
  149. package/dist/src/__tests__/integration/market-service.integration.test.d.ts.map +1 -0
  150. package/dist/src/__tests__/integration/market-service.integration.test.js +180 -0
  151. package/dist/src/__tests__/integration/market-service.integration.test.js.map +1 -0
  152. package/dist/src/__tests__/integration/realtime-service-v2.integration.test.d.ts +10 -0
  153. package/dist/src/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +1 -0
  154. package/dist/src/__tests__/integration/realtime-service-v2.integration.test.js +307 -0
  155. package/dist/src/__tests__/integration/realtime-service-v2.integration.test.js.map +1 -0
  156. package/dist/src/__tests__/integration/trading-service.integration.test.d.ts +10 -0
  157. package/dist/src/__tests__/integration/trading-service.integration.test.d.ts.map +1 -0
  158. package/dist/src/__tests__/integration/trading-service.integration.test.js +58 -0
  159. package/dist/src/__tests__/integration/trading-service.integration.test.js.map +1 -0
  160. package/dist/src/__tests__/test-utils.d.ts +92 -0
  161. package/dist/src/__tests__/test-utils.d.ts.map +1 -0
  162. package/dist/src/__tests__/test-utils.js +143 -0
  163. package/dist/src/__tests__/test-utils.js.map +1 -0
  164. package/dist/src/clients/bridge-client.d.ts +388 -0
  165. package/dist/src/clients/bridge-client.d.ts.map +1 -0
  166. package/dist/src/clients/bridge-client.js +587 -0
  167. package/dist/src/clients/bridge-client.js.map +1 -0
  168. package/dist/src/clients/ctf-client.d.ts +475 -0
  169. package/dist/src/clients/ctf-client.d.ts.map +1 -0
  170. package/dist/src/clients/ctf-client.js +915 -0
  171. package/dist/src/clients/ctf-client.js.map +1 -0
  172. package/dist/src/clients/data-api.d.ts +452 -0
  173. package/dist/src/clients/data-api.d.ts.map +1 -0
  174. package/dist/src/clients/data-api.js +637 -0
  175. package/dist/src/clients/data-api.js.map +1 -0
  176. package/dist/src/clients/gamma-api.d.ts +421 -0
  177. package/dist/src/clients/gamma-api.d.ts.map +1 -0
  178. package/dist/src/clients/gamma-api.js +359 -0
  179. package/dist/src/clients/gamma-api.js.map +1 -0
  180. package/dist/src/clients/subgraph.d.ts +196 -0
  181. package/dist/src/clients/subgraph.d.ts.map +1 -0
  182. package/dist/src/clients/subgraph.js +332 -0
  183. package/dist/src/clients/subgraph.js.map +1 -0
  184. package/dist/src/core/cache-adapter-bridge.d.ts +36 -0
  185. package/dist/src/core/cache-adapter-bridge.d.ts.map +1 -0
  186. package/dist/src/core/cache-adapter-bridge.js +81 -0
  187. package/dist/src/core/cache-adapter-bridge.js.map +1 -0
  188. package/dist/src/core/cache.d.ts +43 -0
  189. package/dist/src/core/cache.d.ts.map +1 -0
  190. package/dist/src/core/cache.js +76 -0
  191. package/dist/src/core/cache.js.map +1 -0
  192. package/dist/src/core/errors.d.ts +39 -0
  193. package/dist/src/core/errors.d.ts.map +1 -0
  194. package/dist/src/core/errors.js +86 -0
  195. package/dist/src/core/errors.js.map +1 -0
  196. package/dist/src/core/rate-limiter.d.ts +33 -0
  197. package/dist/src/core/rate-limiter.d.ts.map +1 -0
  198. package/dist/src/core/rate-limiter.js +82 -0
  199. package/dist/src/core/rate-limiter.js.map +1 -0
  200. package/dist/src/core/types.d.ts +506 -0
  201. package/dist/src/core/types.d.ts.map +1 -0
  202. package/dist/src/core/types.js +49 -0
  203. package/dist/src/core/types.js.map +1 -0
  204. package/dist/src/core/types.test.d.ts +7 -0
  205. package/dist/src/core/types.test.d.ts.map +1 -0
  206. package/dist/src/core/types.test.js +122 -0
  207. package/dist/src/core/types.test.js.map +1 -0
  208. package/dist/src/core/unified-cache.d.ts +63 -0
  209. package/dist/src/core/unified-cache.d.ts.map +1 -0
  210. package/dist/src/core/unified-cache.js +114 -0
  211. package/dist/src/core/unified-cache.js.map +1 -0
  212. package/dist/src/index.d.ts +159 -0
  213. package/dist/src/index.d.ts.map +1 -0
  214. package/dist/src/index.js +262 -0
  215. package/dist/src/index.js.map +1 -0
  216. package/dist/src/services/arbitrage-service.d.ts +409 -0
  217. package/dist/src/services/arbitrage-service.d.ts.map +1 -0
  218. package/dist/src/services/arbitrage-service.js +1450 -0
  219. package/dist/src/services/arbitrage-service.js.map +1 -0
  220. package/dist/src/services/authorization-service.d.ts +97 -0
  221. package/dist/src/services/authorization-service.d.ts.map +1 -0
  222. package/dist/src/services/authorization-service.js +279 -0
  223. package/dist/src/services/authorization-service.js.map +1 -0
  224. package/dist/src/services/binance-service.d.ts +154 -0
  225. package/dist/src/services/binance-service.d.ts.map +1 -0
  226. package/dist/src/services/binance-service.js +266 -0
  227. package/dist/src/services/binance-service.js.map +1 -0
  228. package/dist/src/services/dip-arb-service.d.ts +245 -0
  229. package/dist/src/services/dip-arb-service.d.ts.map +1 -0
  230. package/dist/src/services/dip-arb-service.js +1865 -0
  231. package/dist/src/services/dip-arb-service.js.map +1 -0
  232. package/dist/src/services/dip-arb-types.d.ts +553 -0
  233. package/dist/src/services/dip-arb-types.d.ts.map +1 -0
  234. package/dist/src/services/dip-arb-types.js +164 -0
  235. package/dist/src/services/dip-arb-types.js.map +1 -0
  236. package/dist/src/services/market-service.d.ts +370 -0
  237. package/dist/src/services/market-service.d.ts.map +1 -0
  238. package/dist/src/services/market-service.js +1200 -0
  239. package/dist/src/services/market-service.js.map +1 -0
  240. package/dist/src/services/onchain-service.d.ts +309 -0
  241. package/dist/src/services/onchain-service.d.ts.map +1 -0
  242. package/dist/src/services/onchain-service.js +417 -0
  243. package/dist/src/services/onchain-service.js.map +1 -0
  244. package/dist/src/services/realtime-service-v2.d.ts +367 -0
  245. package/dist/src/services/realtime-service-v2.d.ts.map +1 -0
  246. package/dist/src/services/realtime-service-v2.js +876 -0
  247. package/dist/src/services/realtime-service-v2.js.map +1 -0
  248. package/dist/src/services/smart-money-service.d.ts +352 -0
  249. package/dist/src/services/smart-money-service.d.ts.map +1 -0
  250. package/dist/src/services/smart-money-service.js +582 -0
  251. package/dist/src/services/smart-money-service.js.map +1 -0
  252. package/dist/src/services/swap-service.d.ts +217 -0
  253. package/dist/src/services/swap-service.d.ts.map +1 -0
  254. package/dist/src/services/swap-service.js +695 -0
  255. package/dist/src/services/swap-service.js.map +1 -0
  256. package/dist/src/services/trading-service.d.ts +177 -0
  257. package/dist/src/services/trading-service.d.ts.map +1 -0
  258. package/dist/src/services/trading-service.js +422 -0
  259. package/dist/src/services/trading-service.js.map +1 -0
  260. package/dist/src/services/wallet-service.d.ts +316 -0
  261. package/dist/src/services/wallet-service.d.ts.map +1 -0
  262. package/dist/src/services/wallet-service.js +681 -0
  263. package/dist/src/services/wallet-service.js.map +1 -0
  264. package/dist/src/utils/price-utils.d.ts +153 -0
  265. package/dist/src/utils/price-utils.d.ts.map +1 -0
  266. package/dist/src/utils/price-utils.js +236 -0
  267. package/dist/src/utils/price-utils.js.map +1 -0
  268. package/dist/src/utils/price-utils.test.d.ts +5 -0
  269. package/dist/src/utils/price-utils.test.d.ts.map +1 -0
  270. package/dist/src/utils/price-utils.test.js +192 -0
  271. package/dist/src/utils/price-utils.test.js.map +1 -0
  272. package/dist/utils/price-utils.test.d.ts +5 -0
  273. package/dist/utils/price-utils.test.d.ts.map +1 -0
  274. package/dist/utils/price-utils.test.js +192 -0
  275. package/dist/utils/price-utils.test.js.map +1 -0
  276. package/package.json +6 -5
  277. package/README.en.md +0 -502
@@ -1,25 +1,268 @@
1
1
  /**
2
2
  * Market Service
3
3
  *
4
- * Provides enhanced market analysis features:
4
+ * Provides market data and analysis:
5
+ * - Market info and discovery
6
+ * - Orderbook data and analysis
5
7
  * - K-Line aggregation from trade data
6
- * - Dual token K-Lines (YES + NO)
7
8
  * - Spread analysis
8
- * - Market signal detection
9
+ * - Arbitrage detection
9
10
  */
11
+ import { ClobClient, Side as ClobSide, PriceHistoryInterval, } from '@polymarket/clob-client';
12
+ import { Wallet } from 'ethers';
13
+ import { CACHE_TTL } from '../core/unified-cache.js';
14
+ import { ApiType } from '../core/rate-limiter.js';
10
15
  import { PolymarketError, ErrorCode } from '../core/errors.js';
16
+ // CLOB Host
17
+ const CLOB_HOST = 'https://clob.polymarket.com';
18
+ // Chain IDs
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
+ };
36
+ // ============================================================================
37
+ // MarketService Implementation
38
+ // ============================================================================
11
39
  export class MarketService {
12
40
  gammaApi;
13
- clobApi;
14
41
  dataApi;
42
+ rateLimiter;
15
43
  cache;
16
- constructor(gammaApi, clobApi, dataApi, cache) {
44
+ config;
45
+ binanceService;
46
+ clobClient = null;
47
+ initialized = false;
48
+ constructor(gammaApi, dataApi, rateLimiter, cache, config, binanceService) {
17
49
  this.gammaApi = gammaApi;
18
- this.clobApi = clobApi;
19
50
  this.dataApi = dataApi;
51
+ this.rateLimiter = rateLimiter;
20
52
  this.cache = cache;
53
+ this.config = config;
54
+ this.binanceService = binanceService;
55
+ }
56
+ // ============================================================================
57
+ // Initialization
58
+ // ============================================================================
59
+ async ensureInitialized() {
60
+ if (!this.initialized || !this.clobClient) {
61
+ const chainId = (this.config?.chainId || POLYGON_MAINNET);
62
+ if (this.config?.privateKey) {
63
+ // Authenticated client
64
+ const wallet = new Wallet(this.config.privateKey);
65
+ this.clobClient = new ClobClient(CLOB_HOST, chainId, wallet);
66
+ }
67
+ else {
68
+ // Read-only client (no auth needed for market data)
69
+ this.clobClient = new ClobClient(CLOB_HOST, chainId);
70
+ }
71
+ this.initialized = true;
72
+ }
73
+ return this.clobClient;
74
+ }
75
+ // ============================================================================
76
+ // CLOB Market Data Methods
77
+ // ============================================================================
78
+ /**
79
+ * Get market from CLOB by condition ID
80
+ */
81
+ async getClobMarket(conditionId) {
82
+ const cacheKey = `clob:market:${conditionId}`;
83
+ return this.cache.getOrSet(cacheKey, CACHE_TTL.MARKET_INFO, async () => {
84
+ const client = await this.ensureInitialized();
85
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
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
+ }
100
+ });
101
+ });
102
+ }
103
+ /**
104
+ * Get multiple markets from CLOB
105
+ */
106
+ async getClobMarkets(nextCursor) {
107
+ const client = await this.ensureInitialized();
108
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
109
+ const result = await client.getMarkets(nextCursor);
110
+ return {
111
+ markets: result.data.map(m => this.normalizeClobMarket(m)),
112
+ nextCursor: result.next_cursor,
113
+ };
114
+ });
115
+ }
116
+ /**
117
+ * Get orderbook for a single token
118
+ */
119
+ async getTokenOrderbook(tokenId) {
120
+ const client = await this.ensureInitialized();
121
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
122
+ const book = await client.getOrderBook(tokenId);
123
+ const bids = (book.bids || [])
124
+ .map((l) => ({
125
+ price: parseFloat(l.price),
126
+ size: parseFloat(l.size),
127
+ }))
128
+ .sort((a, b) => b.price - a.price);
129
+ const asks = (book.asks || [])
130
+ .map((l) => ({
131
+ price: parseFloat(l.price),
132
+ size: parseFloat(l.size),
133
+ }))
134
+ .sort((a, b) => a.price - b.price);
135
+ return {
136
+ tokenId: book.asset_id,
137
+ assetId: book.asset_id, // Backward compatibility
138
+ bids,
139
+ asks,
140
+ timestamp: parseInt(book.timestamp || '0', 10) || Date.now(),
141
+ market: book.market,
142
+ hash: book.hash,
143
+ };
144
+ });
145
+ }
146
+ /**
147
+ * Get orderbooks for multiple tokens
148
+ */
149
+ async getTokenOrderbooks(params) {
150
+ const client = await this.ensureInitialized();
151
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
152
+ const bookParams = params.map(p => ({
153
+ token_id: p.tokenId,
154
+ side: p.side === 'BUY' ? ClobSide.BUY : ClobSide.SELL,
155
+ }));
156
+ const books = await client.getOrderBooks(bookParams);
157
+ const result = new Map();
158
+ for (const book of books) {
159
+ const bids = (book.bids || [])
160
+ .map((l) => ({
161
+ price: parseFloat(l.price),
162
+ size: parseFloat(l.size),
163
+ }))
164
+ .sort((a, b) => b.price - a.price);
165
+ const asks = (book.asks || [])
166
+ .map((l) => ({
167
+ price: parseFloat(l.price),
168
+ size: parseFloat(l.size),
169
+ }))
170
+ .sort((a, b) => a.price - b.price);
171
+ result.set(book.asset_id, {
172
+ tokenId: book.asset_id,
173
+ assetId: book.asset_id, // Backward compatibility
174
+ bids,
175
+ asks,
176
+ timestamp: parseInt(book.timestamp || '0', 10) || Date.now(),
177
+ market: book.market,
178
+ hash: book.hash,
179
+ });
180
+ }
181
+ return result;
182
+ });
183
+ }
184
+ /**
185
+ * Get processed orderbook with arbitrage analysis for a market
186
+ */
187
+ async getProcessedOrderbook(conditionId) {
188
+ const market = await this.getClobMarket(conditionId);
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
195
+ if (!yesToken || !noToken) {
196
+ throw new PolymarketError(ErrorCode.INVALID_RESPONSE, 'Missing tokens in market');
197
+ }
198
+ const [yesBook, noBook] = await Promise.all([
199
+ this.getTokenOrderbook(yesToken.tokenId),
200
+ this.getTokenOrderbook(noToken.tokenId),
201
+ ]);
202
+ return this.processOrderbooks(yesBook, noBook, yesToken.tokenId, noToken.tokenId);
203
+ }
204
+ /**
205
+ * Get price history for a token
206
+ */
207
+ async getPricesHistory(params) {
208
+ const client = await this.ensureInitialized();
209
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
210
+ const intervalMap = {
211
+ '1h': PriceHistoryInterval.ONE_HOUR,
212
+ '6h': PriceHistoryInterval.SIX_HOURS,
213
+ '1d': PriceHistoryInterval.ONE_DAY,
214
+ '1w': PriceHistoryInterval.ONE_WEEK,
215
+ 'max': PriceHistoryInterval.MAX,
216
+ };
217
+ const history = await client.getPricesHistory({
218
+ market: params.tokenId,
219
+ interval: params.interval ? intervalMap[params.interval] : undefined,
220
+ startTs: params.startTs,
221
+ endTs: params.endTs,
222
+ fidelity: params.fidelity,
223
+ });
224
+ const historyArray = Array.isArray(history)
225
+ ? history
226
+ : history?.history || [];
227
+ return historyArray.map((pt) => ({
228
+ timestamp: pt.t,
229
+ price: pt.p,
230
+ }));
231
+ });
21
232
  }
22
- // ===== Unified Market Access =====
233
+ /**
234
+ * Get midpoint price for a token
235
+ */
236
+ async getMidpoint(tokenId) {
237
+ const client = await this.ensureInitialized();
238
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
239
+ const midpoint = await client.getMidpoint(tokenId);
240
+ return Number(midpoint);
241
+ });
242
+ }
243
+ /**
244
+ * Get spread for a token
245
+ */
246
+ async getSpread(tokenId) {
247
+ const client = await this.ensureInitialized();
248
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
249
+ const spread = await client.getSpread(tokenId);
250
+ return Number(spread);
251
+ });
252
+ }
253
+ /**
254
+ * Get last trade price for a token
255
+ */
256
+ async getLastTradePrice(tokenId) {
257
+ const client = await this.ensureInitialized();
258
+ return this.rateLimiter.execute(ApiType.CLOB_API, async () => {
259
+ const price = await client.getLastTradePrice(tokenId);
260
+ return Number(price);
261
+ });
262
+ }
263
+ // ============================================================================
264
+ // Unified Market Access
265
+ // ============================================================================
23
266
  /**
24
267
  * Get market by slug or condition ID
25
268
  */
@@ -33,13 +276,19 @@ export class MarketService {
33
276
  }
34
277
  }
35
278
  async getMarketBySlug(slug) {
279
+ if (!this.gammaApi) {
280
+ throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'GammaApiClient is required for slug-based lookups');
281
+ }
36
282
  const gammaMarket = await this.gammaApi.getMarketBySlug(slug);
37
283
  if (!gammaMarket) {
38
284
  throw new PolymarketError(ErrorCode.MARKET_NOT_FOUND, `Market not found: ${slug}`);
39
285
  }
40
286
  try {
41
- const clobMarket = await this.clobApi.getMarket(gammaMarket.conditionId);
42
- return this.mergeMarkets(gammaMarket, clobMarket);
287
+ const clobMarket = await this.getClobMarket(gammaMarket.conditionId);
288
+ if (clobMarket) {
289
+ return this.mergeMarkets(gammaMarket, clobMarket);
290
+ }
291
+ return this.fromGammaMarket(gammaMarket);
43
292
  }
44
293
  catch {
45
294
  return this.fromGammaMarket(gammaMarket);
@@ -51,17 +300,19 @@ export class MarketService {
51
300
  let gammaMarket = null;
52
301
  // Try CLOB first (authoritative for trading data)
53
302
  try {
54
- clobMarket = await this.clobApi.getMarket(conditionId);
303
+ clobMarket = await this.getClobMarket(conditionId);
55
304
  }
56
305
  catch {
57
306
  // CLOB failed, continue to try Gamma
58
307
  }
59
- // Always try Gamma for accurate slug and metadata
60
- try {
61
- gammaMarket = await this.gammaApi.getMarketByConditionId(conditionId);
62
- }
63
- catch {
64
- // Gamma failed
308
+ // Always try Gamma for accurate slug and metadata (if available)
309
+ if (this.gammaApi) {
310
+ try {
311
+ gammaMarket = await this.gammaApi.getMarketByConditionId(conditionId);
312
+ }
313
+ catch {
314
+ // Gamma failed
315
+ }
65
316
  }
66
317
  // Merge if both available (preferred)
67
318
  if (gammaMarket && clobMarket) {
@@ -74,7 +325,7 @@ export class MarketService {
74
325
  // CLOB only - slug might be stale, add warning
75
326
  if (clobMarket) {
76
327
  const market = this.fromClobMarket(clobMarket);
77
- // Check if CLOB slug looks stale (doesn't match question keywords)
328
+ // Check if slug looks stale (doesn't match question keywords)
78
329
  const questionWords = clobMarket.question.toLowerCase().split(/\s+/).slice(0, 3);
79
330
  const slugWords = clobMarket.marketSlug.toLowerCase().split('-');
80
331
  const hasMatchingWord = questionWords.some(qw => slugWords.some(sw => sw.includes(qw) || qw.includes(sw)));
@@ -89,9 +340,36 @@ export class MarketService {
89
340
  // ===== K-Line Aggregation =====
90
341
  /**
91
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
+ * ```
92
362
  */
93
363
  async getKLines(conditionId, interval, options) {
94
- const trades = await this.dataApi.getTradesByMarket(conditionId, options?.limit || 1000);
364
+ if (!this.dataApi) {
365
+ throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'DataApiClient is required for K-Line data');
366
+ }
367
+ const trades = await this.dataApi.getTrades({
368
+ market: conditionId,
369
+ limit: options?.limit || 1000,
370
+ startTimestamp: options?.startTimestamp,
371
+ endTimestamp: options?.endTimestamp,
372
+ });
95
373
  // Filter by token/outcome if specified
96
374
  let filteredTrades = trades;
97
375
  if (options?.tokenId) {
@@ -104,20 +382,47 @@ export class MarketService {
104
382
  }
105
383
  /**
106
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
+ * ```
107
403
  */
108
404
  async getDualKLines(conditionId, interval, options) {
405
+ if (!this.dataApi) {
406
+ throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'DataApiClient is required for K-Line data');
407
+ }
109
408
  const market = await this.getMarket(conditionId);
110
- const trades = await this.dataApi.getTradesByMarket(conditionId, options?.limit || 1000);
111
- // Separate trades by outcome
112
- const yesTrades = trades.filter((t) => t.outcomeIndex === 0 || t.outcome === 'Yes');
113
- const noTrades = trades.filter((t) => t.outcomeIndex === 1 || t.outcome === 'No');
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);
114
419
  const yesCandles = this.aggregateToKLines(yesTrades, interval);
115
420
  const noCandles = this.aggregateToKLines(noTrades, interval);
116
421
  // Get current orderbook for real-time spread analysis
117
422
  let currentOrderbook;
118
423
  let realtimeSpread;
119
424
  try {
120
- currentOrderbook = await this.clobApi.getProcessedOrderbook(conditionId);
425
+ currentOrderbook = await this.getProcessedOrderbook(conditionId);
121
426
  realtimeSpread = this.calculateRealtimeSpread(currentOrderbook);
122
427
  }
123
428
  catch {
@@ -136,6 +441,184 @@ export class MarketService {
136
441
  currentOrderbook,
137
442
  };
138
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
+ }
139
622
  /**
140
623
  * Aggregate trades into K-Line candles
141
624
  */
@@ -262,15 +745,15 @@ export class MarketService {
262
745
  * Use this for quick arbitrage checks
263
746
  */
264
747
  async getRealtimeSpread(conditionId) {
265
- const orderbook = await this.clobApi.getProcessedOrderbook(conditionId);
748
+ const orderbook = await this.getProcessedOrderbook(conditionId);
266
749
  return this.calculateRealtimeSpread(orderbook);
267
750
  }
268
751
  // ===== Orderbook Analysis =====
269
752
  /**
270
- * Get processed orderbook with analytics
753
+ * Get processed orderbook with analytics (alias for getProcessedOrderbook)
271
754
  */
272
755
  async getOrderbook(conditionId) {
273
- return this.clobApi.getProcessedOrderbook(conditionId);
756
+ return this.getProcessedOrderbook(conditionId);
274
757
  }
275
758
  /**
276
759
  * Detect arbitrage opportunity
@@ -306,20 +789,171 @@ export class MarketService {
306
789
  * Get trending markets
307
790
  */
308
791
  async getTrendingMarkets(limit = 20) {
792
+ if (!this.gammaApi) {
793
+ throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'GammaApiClient is required for trending markets');
794
+ }
309
795
  return this.gammaApi.getTrendingMarkets(limit);
310
796
  }
311
797
  /**
312
798
  * Search markets
313
799
  */
314
800
  async searchMarkets(params) {
801
+ if (!this.gammaApi) {
802
+ throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'GammaApiClient is required for market search');
803
+ }
315
804
  return this.gammaApi.getMarkets(params);
316
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
+ }
317
948
  // ===== Market Signal Detection =====
318
949
  /**
319
950
  * Detect market signals (volume surge, depth imbalance, whale trades)
320
951
  */
321
952
  async detectMarketSignals(conditionId) {
322
953
  const signals = [];
954
+ if (!this.dataApi) {
955
+ throw new PolymarketError(ErrorCode.INVALID_CONFIG, 'DataApiClient is required for signal detection');
956
+ }
323
957
  const market = await this.getMarket(conditionId);
324
958
  const orderbook = await this.getOrderbook(conditionId);
325
959
  const trades = await this.dataApi.getTradesByMarket(conditionId, 100);
@@ -368,18 +1002,101 @@ export class MarketService {
368
1002
  return signals;
369
1003
  }
370
1004
  // ===== Helper Methods =====
1005
+ normalizeClobMarket(m) {
1006
+ return {
1007
+ conditionId: m.condition_id,
1008
+ questionId: m.question_id,
1009
+ marketSlug: m.market_slug,
1010
+ question: m.question,
1011
+ description: m.description,
1012
+ tokens: m.tokens.map(t => ({
1013
+ tokenId: t.token_id,
1014
+ outcome: t.outcome,
1015
+ price: t.price,
1016
+ winner: t.winner,
1017
+ })),
1018
+ active: m.active,
1019
+ closed: m.closed,
1020
+ acceptingOrders: m.accepting_orders,
1021
+ endDateIso: m.end_date_iso,
1022
+ negRisk: m.neg_risk,
1023
+ minimumOrderSize: m.minimum_order_size,
1024
+ minimumTickSize: m.minimum_tick_size,
1025
+ };
1026
+ }
1027
+ processOrderbooks(yesBook, noBook, yesTokenId, noTokenId) {
1028
+ const yesBestBid = yesBook.bids[0]?.price || 0;
1029
+ const yesBestAsk = yesBook.asks[0]?.price || 1;
1030
+ const noBestBid = noBook.bids[0]?.price || 0;
1031
+ const noBestAsk = noBook.asks[0]?.price || 1;
1032
+ const yesBidDepth = yesBook.bids.reduce((sum, l) => sum + l.price * l.size, 0);
1033
+ const yesAskDepth = yesBook.asks.reduce((sum, l) => sum + l.price * l.size, 0);
1034
+ const noBidDepth = noBook.bids.reduce((sum, l) => sum + l.price * l.size, 0);
1035
+ const noAskDepth = noBook.asks.reduce((sum, l) => sum + l.price * l.size, 0);
1036
+ const askSum = yesBestAsk + noBestAsk;
1037
+ const bidSum = yesBestBid + noBestBid;
1038
+ // Effective prices (accounting for mirroring)
1039
+ const effectivePrices = {
1040
+ effectiveBuyYes: Math.min(yesBestAsk, 1 - noBestBid),
1041
+ effectiveBuyNo: Math.min(noBestAsk, 1 - yesBestBid),
1042
+ effectiveSellYes: Math.max(yesBestBid, 1 - noBestAsk),
1043
+ effectiveSellNo: Math.max(noBestBid, 1 - yesBestAsk),
1044
+ };
1045
+ const effectiveLongCost = effectivePrices.effectiveBuyYes + effectivePrices.effectiveBuyNo;
1046
+ const effectiveShortRevenue = effectivePrices.effectiveSellYes + effectivePrices.effectiveSellNo;
1047
+ const longArbProfit = 1 - effectiveLongCost;
1048
+ const shortArbProfit = effectiveShortRevenue - 1;
1049
+ const yesSpread = yesBestAsk - yesBestBid;
1050
+ return {
1051
+ yes: {
1052
+ bid: yesBestBid,
1053
+ ask: yesBestAsk,
1054
+ bidSize: yesBook.bids[0]?.size || 0,
1055
+ askSize: yesBook.asks[0]?.size || 0,
1056
+ bidDepth: yesBidDepth,
1057
+ askDepth: yesAskDepth,
1058
+ spread: yesSpread,
1059
+ tokenId: yesTokenId,
1060
+ },
1061
+ no: {
1062
+ bid: noBestBid,
1063
+ ask: noBestAsk,
1064
+ bidSize: noBook.bids[0]?.size || 0,
1065
+ askSize: noBook.asks[0]?.size || 0,
1066
+ bidDepth: noBidDepth,
1067
+ askDepth: noAskDepth,
1068
+ spread: noBestAsk - noBestBid,
1069
+ tokenId: noTokenId,
1070
+ },
1071
+ summary: {
1072
+ askSum,
1073
+ bidSum,
1074
+ effectivePrices,
1075
+ effectiveLongCost,
1076
+ effectiveShortRevenue,
1077
+ longArbProfit,
1078
+ shortArbProfit,
1079
+ totalBidDepth: yesBidDepth + noBidDepth,
1080
+ totalAskDepth: yesAskDepth + noAskDepth,
1081
+ imbalanceRatio: (yesBidDepth + noBidDepth) / (yesAskDepth + noAskDepth + 0.001),
1082
+ yesSpread,
1083
+ },
1084
+ };
1085
+ }
371
1086
  mergeMarkets(gamma, clob) {
372
- const yesToken = clob.tokens.find((t) => t.outcome === 'Yes');
373
- const noToken = clob.tokens.find((t) => t.outcome === 'No');
1087
+ // Build tokens array from CLOB data, falling back to Gamma prices
1088
+ const tokens = clob.tokens.map((t, index) => ({
1089
+ tokenId: t.tokenId,
1090
+ outcome: t.outcome,
1091
+ price: t.price || gamma.outcomePrices[index] || 0.5,
1092
+ winner: t.winner,
1093
+ }));
374
1094
  return {
375
1095
  conditionId: clob.conditionId,
376
1096
  slug: gamma.slug,
377
1097
  question: clob.question,
378
1098
  description: clob.description || gamma.description,
379
- tokens: {
380
- yes: { tokenId: yesToken?.tokenId || '', price: yesToken?.price || gamma.outcomePrices[0] || 0.5 },
381
- no: { tokenId: noToken?.tokenId || '', price: noToken?.price || gamma.outcomePrices[1] || 0.5 },
382
- },
1099
+ tokens,
383
1100
  volume: gamma.volume,
384
1101
  volume24hr: gamma.volume24hr,
385
1102
  liquidity: gamma.liquidity,
@@ -394,15 +1111,19 @@ export class MarketService {
394
1111
  };
395
1112
  }
396
1113
  fromGammaMarket(gamma) {
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'];
1117
+ const tokens = [
1118
+ { tokenId: '', outcome: outcomes[0], price: gamma.outcomePrices[0] || 0.5 },
1119
+ { tokenId: '', outcome: outcomes[1], price: gamma.outcomePrices[1] || 0.5 },
1120
+ ];
397
1121
  return {
398
1122
  conditionId: gamma.conditionId,
399
1123
  slug: gamma.slug,
400
1124
  question: gamma.question,
401
1125
  description: gamma.description,
402
- tokens: {
403
- yes: { tokenId: '', price: gamma.outcomePrices[0] || 0.5 },
404
- no: { tokenId: '', price: gamma.outcomePrices[1] || 0.5 },
405
- },
1126
+ tokens,
406
1127
  volume: gamma.volume,
407
1128
  volume24hr: gamma.volume24hr,
408
1129
  liquidity: gamma.liquidity,
@@ -417,17 +1138,19 @@ export class MarketService {
417
1138
  };
418
1139
  }
419
1140
  fromClobMarket(clob) {
420
- const yesToken = clob.tokens.find((t) => t.outcome === 'Yes');
421
- const noToken = clob.tokens.find((t) => t.outcome === 'No');
1141
+ // Convert CLOB tokens to UnifiedMarketToken format
1142
+ const tokens = clob.tokens.map(t => ({
1143
+ tokenId: t.tokenId,
1144
+ outcome: t.outcome,
1145
+ price: t.price,
1146
+ winner: t.winner,
1147
+ }));
422
1148
  return {
423
1149
  conditionId: clob.conditionId,
424
1150
  slug: clob.marketSlug,
425
1151
  question: clob.question,
426
1152
  description: clob.description,
427
- tokens: {
428
- yes: { tokenId: yesToken?.tokenId || '', price: yesToken?.price || 0.5 },
429
- no: { tokenId: noToken?.tokenId || '', price: noToken?.price || 0.5 },
430
- },
1153
+ tokens,
431
1154
  volume: 0,
432
1155
  volume24hr: undefined,
433
1156
  liquidity: 0,
@@ -443,11 +1166,17 @@ export class MarketService {
443
1166
  // ===== Utility Functions =====
444
1167
  export function getIntervalMs(interval) {
445
1168
  const map = {
1169
+ // Second-level intervals (for 15-minute crypto markets)
1170
+ '1s': 1 * 1000,
1171
+ '5s': 5 * 1000,
1172
+ '15s': 15 * 1000,
446
1173
  '30s': 30 * 1000,
1174
+ // Minute-level intervals
447
1175
  '1m': 60 * 1000,
448
1176
  '5m': 5 * 60 * 1000,
449
1177
  '15m': 15 * 60 * 1000,
450
1178
  '30m': 30 * 60 * 1000,
1179
+ // Hour-level intervals
451
1180
  '1h': 60 * 60 * 1000,
452
1181
  '4h': 4 * 60 * 60 * 1000,
453
1182
  '12h': 12 * 60 * 60 * 1000,