pmxtjs 0.0.1 → 0.1.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 (58) hide show
  1. package/API_REFERENCE.md +88 -0
  2. package/coverage/clover.xml +334 -0
  3. package/coverage/coverage-final.json +4 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +131 -0
  8. package/coverage/lcov-report/pmxt/BaseExchange.ts.html +256 -0
  9. package/coverage/lcov-report/pmxt/exchanges/Kalshi.ts.html +1132 -0
  10. package/coverage/lcov-report/pmxt/exchanges/Polymarket.ts.html +1456 -0
  11. package/coverage/lcov-report/pmxt/exchanges/index.html +131 -0
  12. package/coverage/lcov-report/pmxt/index.html +116 -0
  13. package/coverage/lcov-report/prettify.css +1 -0
  14. package/coverage/lcov-report/prettify.js +2 -0
  15. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  16. package/coverage/lcov-report/sorter.js +210 -0
  17. package/coverage/lcov-report/src/BaseExchange.ts.html +256 -0
  18. package/coverage/lcov-report/src/exchanges/Kalshi.ts.html +1132 -0
  19. package/coverage/lcov-report/src/exchanges/Polymarket.ts.html +1456 -0
  20. package/coverage/lcov-report/src/exchanges/index.html +131 -0
  21. package/coverage/lcov-report/src/index.html +116 -0
  22. package/coverage/lcov.info +766 -0
  23. package/examples/get_event_prices.ts +37 -0
  24. package/examples/historical_prices.ts +117 -0
  25. package/examples/orderbook.ts +102 -0
  26. package/examples/recent_trades.ts +29 -0
  27. package/examples/search_events.ts +68 -0
  28. package/examples/search_market.ts +29 -0
  29. package/jest.config.js +11 -0
  30. package/package.json +21 -21
  31. package/pmxt-0.1.0.tgz +0 -0
  32. package/src/BaseExchange.ts +57 -0
  33. package/src/exchanges/Kalshi.ts +349 -0
  34. package/src/exchanges/Polymarket.ts +457 -0
  35. package/src/index.ts +5 -0
  36. package/src/types.ts +61 -0
  37. package/test/exchanges/kalshi/ApiErrors.test.ts +132 -0
  38. package/test/exchanges/kalshi/EmptyResponse.test.ts +44 -0
  39. package/test/exchanges/kalshi/FetchAndNormalizeMarkets.test.ts +56 -0
  40. package/test/exchanges/kalshi/LiveApi.integration.test.ts +40 -0
  41. package/test/exchanges/kalshi/MarketHistory.test.ts +185 -0
  42. package/test/exchanges/kalshi/OrderBook.test.ts +149 -0
  43. package/test/exchanges/kalshi/SearchMarkets.test.ts +174 -0
  44. package/test/exchanges/kalshi/VolumeFallback.test.ts +44 -0
  45. package/test/exchanges/polymarket/DataValidation.test.ts +271 -0
  46. package/test/exchanges/polymarket/ErrorHandling.test.ts +34 -0
  47. package/test/exchanges/polymarket/FetchAndNormalizeMarkets.test.ts +68 -0
  48. package/test/exchanges/polymarket/GetMarketsBySlug.test.ts +268 -0
  49. package/test/exchanges/polymarket/LiveApi.integration.test.ts +44 -0
  50. package/test/exchanges/polymarket/MarketHistory.test.ts +207 -0
  51. package/test/exchanges/polymarket/OrderBook.test.ts +167 -0
  52. package/test/exchanges/polymarket/RequestParameters.test.ts +39 -0
  53. package/test/exchanges/polymarket/SearchMarkets.test.ts +176 -0
  54. package/test/exchanges/polymarket/TradeHistory.test.ts +248 -0
  55. package/tsconfig.json +12 -0
  56. package/Cargo.toml +0 -15
  57. package/README.md +0 -47
  58. package/src/lib.rs +0 -6
@@ -0,0 +1,248 @@
1
+ import axios from 'axios';
2
+ import { PolymarketExchange } from '../../../src/exchanges/Polymarket';
3
+
4
+ /**
5
+ * Polymarket getTradeHistory() Test
6
+ *
7
+ * What: Tests fetching raw trade history data from CLOB API.
8
+ * Why: Trade history is CRITICAL for market analysis and price discovery.
9
+ * How: Mocks CLOB trades API responses and verifies data transformation.
10
+ */
11
+
12
+ jest.mock('axios');
13
+ const mockedAxios = axios as jest.Mocked<typeof axios>;
14
+
15
+ describe('PolymarketExchange - getTradeHistory', () => {
16
+ let exchange: PolymarketExchange;
17
+
18
+ beforeEach(() => {
19
+ exchange = new PolymarketExchange();
20
+ jest.clearAllMocks();
21
+ });
22
+
23
+ it('should fetch and parse trade history', async () => {
24
+ mockedAxios.get.mockResolvedValue({
25
+ data: [
26
+ {
27
+ id: 'trade-1',
28
+ timestamp: 1704067200,
29
+ price: '0.52',
30
+ size: '100',
31
+ side: 'BUY'
32
+ },
33
+ {
34
+ id: 'trade-2',
35
+ timestamp: 1704067260,
36
+ price: '0.53',
37
+ size: '250',
38
+ side: 'SELL'
39
+ }
40
+ ]
41
+ });
42
+
43
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
44
+
45
+ expect(trades.length).toBe(2);
46
+ expect(trades[0].id).toBe('trade-1');
47
+ expect(trades[0].price).toBe(0.52);
48
+ expect(trades[0].amount).toBe(100);
49
+ expect(trades[0].side).toBe('buy');
50
+ });
51
+
52
+ it('should convert timestamps to milliseconds', async () => {
53
+ mockedAxios.get.mockResolvedValue({
54
+ data: [{
55
+ id: 'trade-1',
56
+ timestamp: 1704067200,
57
+ price: '0.50',
58
+ size: '100',
59
+ side: 'BUY'
60
+ }]
61
+ });
62
+
63
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
64
+
65
+ expect(trades[0].timestamp).toBe(1704067200 * 1000);
66
+ });
67
+
68
+ it('should handle BUY and SELL sides correctly', async () => {
69
+ mockedAxios.get.mockResolvedValue({
70
+ data: [
71
+ { id: 'trade-1', timestamp: 1704067200, price: '0.50', size: '100', side: 'BUY' },
72
+ { id: 'trade-2', timestamp: 1704067260, price: '0.51', size: '150', side: 'SELL' }
73
+ ]
74
+ });
75
+
76
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
77
+
78
+ expect(trades[0].side).toBe('buy');
79
+ expect(trades[1].side).toBe('sell');
80
+ });
81
+
82
+ it('should handle unknown side values', async () => {
83
+ mockedAxios.get.mockResolvedValue({
84
+ data: [{
85
+ id: 'trade-1',
86
+ timestamp: 1704067200,
87
+ price: '0.50',
88
+ size: '100',
89
+ side: 'UNKNOWN'
90
+ }]
91
+ });
92
+
93
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
94
+
95
+ expect(trades[0].side).toBe('unknown');
96
+ });
97
+
98
+ it('should handle missing trade ID with fallback', async () => {
99
+ mockedAxios.get.mockResolvedValue({
100
+ data: [{
101
+ timestamp: 1704067200,
102
+ price: '0.50',
103
+ size: '100',
104
+ side: 'BUY'
105
+ // Missing id
106
+ }]
107
+ });
108
+
109
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
110
+
111
+ expect(trades[0].id).toBe('1704067200-0.50');
112
+ });
113
+
114
+ it('should handle alternative amount field names', async () => {
115
+ mockedAxios.get.mockResolvedValue({
116
+ data: [{
117
+ id: 'trade-1',
118
+ timestamp: 1704067200,
119
+ price: '0.50',
120
+ amount: '200', // Alternative field
121
+ side: 'BUY'
122
+ }]
123
+ });
124
+
125
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
126
+
127
+ expect(trades[0].amount).toBe(200);
128
+ });
129
+
130
+ it('should respect limit parameter', async () => {
131
+ const mockTrades = Array.from({ length: 100 }, (_, i) => ({
132
+ id: `trade-${i}`,
133
+ timestamp: 1704067200 + i,
134
+ price: '0.50',
135
+ size: '100',
136
+ side: 'BUY'
137
+ }));
138
+
139
+ mockedAxios.get.mockResolvedValue({ data: mockTrades });
140
+
141
+ const trades = await exchange.getTradeHistory('token123456789', {
142
+ resolution: '1h',
143
+ limit: 20
144
+ });
145
+
146
+ expect(trades.length).toBe(20);
147
+ });
148
+
149
+ it('should include start timestamp in query params', async () => {
150
+ mockedAxios.get.mockResolvedValue({ data: [] });
151
+
152
+ const start = new Date('2025-01-01T00:00:00Z');
153
+ await exchange.getTradeHistory('token123456789', {
154
+ resolution: '1h',
155
+ start
156
+ });
157
+
158
+ expect(mockedAxios.get).toHaveBeenCalledWith(
159
+ expect.any(String),
160
+ expect.objectContaining({
161
+ params: expect.objectContaining({
162
+ after: Math.floor(start.getTime() / 1000)
163
+ })
164
+ })
165
+ );
166
+ });
167
+
168
+ it('should include end timestamp in query params', async () => {
169
+ mockedAxios.get.mockResolvedValue({ data: [] });
170
+
171
+ const end = new Date('2025-01-31T00:00:00Z');
172
+ await exchange.getTradeHistory('token123456789', {
173
+ resolution: '1h',
174
+ end
175
+ });
176
+
177
+ expect(mockedAxios.get).toHaveBeenCalledWith(
178
+ expect.any(String),
179
+ expect.objectContaining({
180
+ params: expect.objectContaining({
181
+ before: Math.floor(end.getTime() / 1000)
182
+ })
183
+ })
184
+ );
185
+ });
186
+
187
+ it('should handle empty trades array', async () => {
188
+ mockedAxios.get.mockResolvedValue({ data: [] });
189
+
190
+ const trades = await exchange.getTradeHistory('token123456789', { resolution: '1h' });
191
+
192
+ expect(trades).toEqual([]);
193
+ });
194
+
195
+ it('should throw error for invalid token ID format', async () => {
196
+ await expect(exchange.getTradeHistory('123', { resolution: '1h' }))
197
+ .rejects
198
+ .toThrow(/Invalid ID/i);
199
+ });
200
+
201
+ it('should handle API errors with detailed messages', async () => {
202
+ const error = {
203
+ response: {
204
+ status: 401,
205
+ data: { error: 'Authentication required' }
206
+ },
207
+ isAxiosError: true
208
+ };
209
+ mockedAxios.get.mockRejectedValue(error);
210
+ // @ts-expect-error - Mock type mismatch is expected in tests
211
+ mockedAxios.isAxiosError = jest.fn().mockReturnValue(true);
212
+
213
+ await expect(exchange.getTradeHistory('token123456789', { resolution: '1h' }))
214
+ .rejects
215
+ .toThrow(/Trades API Error/i);
216
+ });
217
+
218
+ it('should handle unexpected errors', async () => {
219
+ mockedAxios.get.mockRejectedValue(new Error('Network failure'));
220
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
221
+
222
+ await expect(exchange.getTradeHistory('token123456789', { resolution: '1h' }))
223
+ .rejects
224
+ .toThrow('Network failure');
225
+
226
+ expect(consoleSpy).toHaveBeenCalled();
227
+ consoleSpy.mockRestore();
228
+ });
229
+
230
+ it('should return most recent trades when limit is applied', async () => {
231
+ const mockTrades = [
232
+ { id: 'trade-1', timestamp: 1704067200, price: '0.50', size: '100', side: 'BUY' },
233
+ { id: 'trade-2', timestamp: 1704067260, price: '0.51', size: '100', side: 'BUY' },
234
+ { id: 'trade-3', timestamp: 1704067320, price: '0.52', size: '100', side: 'BUY' }
235
+ ];
236
+
237
+ mockedAxios.get.mockResolvedValue({ data: mockTrades });
238
+
239
+ const trades = await exchange.getTradeHistory('token123456789', {
240
+ resolution: '1h',
241
+ limit: 2
242
+ });
243
+
244
+ expect(trades.length).toBe(2);
245
+ expect(trades[0].id).toBe('trade-2'); // Most recent 2
246
+ expect(trades[1].id).toBe('trade-3');
247
+ });
248
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "strict": true,
6
+ "esModuleInterop": true,
7
+ "skipLibCheck": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "outDir": "./dist"
10
+ },
11
+ "include": ["src/**/*"]
12
+ }
package/Cargo.toml DELETED
@@ -1,15 +0,0 @@
1
- [package]
2
- name = "pmxt-node"
3
- version = "0.0.1"
4
- edition = "2021"
5
-
6
- [lib]
7
- crate-type = ["cdylib"]
8
-
9
- [dependencies]
10
- napi = { version = "2.12", features = ["async"] }
11
- napi-derive = "2.12"
12
- pmxt-core = { path = "../../crates/core" }
13
-
14
- [build-dependencies]
15
- napi-build = "2.0"
package/README.md DELETED
@@ -1,47 +0,0 @@
1
- # PMXT: The ccxt for Prediction Markets
2
-
3
- PMXT is a unified endpoint for trading on prediction markets (Polymarket, Kalshi, etc.), written in Rust for performance with native bindings for Python and Node.js.
4
-
5
- ## Architecture
6
-
7
- This project is organized as a Cargo Workspace:
8
-
9
- - **`crates/core`**: Defines the shared `Exchange` trait and data models (`Market`, `Order`, `Ticker`).
10
- - **`crates/polymarket`**: The implementation for Polymarket.
11
- - **`bindings/python`**: The native Python extension (using PyO3).
12
-
13
- ## Development
14
-
15
- ### Prerequisites
16
-
17
- - Rust (cargo)
18
- - Python 3.9+
19
- - `maturin` (for building Python bindings) -> `pip install maturin`
20
-
21
- ### Building Python Bindings
22
-
23
- To build and install the python library into your current environment:
24
-
25
- ```bash
26
- cd bindings/python
27
- maturin develop
28
- ```
29
-
30
- ### Usage (Python)
31
-
32
- ```python
33
- import pmxt
34
- import asyncio
35
-
36
- async def main():
37
- exchange = pmxt.Polymarket(api_key="your_key")
38
- markets = await exchange.load_markets()
39
- print(markets)
40
-
41
- asyncio.run(main())
42
- ```
43
-
44
- ## Contributing
45
-
46
- 1. Implement `Exchange` trait for a new market in `crates/new_market`.
47
- 2. Register it in `bindings/python/src/lib.rs`.
package/src/lib.rs DELETED
@@ -1,6 +0,0 @@
1
- use napi_derive::napi;
2
-
3
- #[napi]
4
- pub fn info() -> String {
5
- "PMXT Node.js Bindings".to_string()
6
- }