pmxt-core 2.24.0 → 2.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/utils.js +17 -1
- package/dist/exchanges/polymarket_us/config.d.ts +18 -0
- package/dist/exchanges/polymarket_us/config.js +22 -0
- package/dist/exchanges/polymarket_us/errors.d.ts +19 -0
- package/dist/exchanges/polymarket_us/errors.js +123 -0
- package/dist/exchanges/polymarket_us/errors.test.d.ts +1 -0
- package/dist/exchanges/polymarket_us/errors.test.js +54 -0
- package/dist/exchanges/polymarket_us/index.d.ts +90 -0
- package/dist/exchanges/polymarket_us/index.js +366 -0
- package/dist/exchanges/polymarket_us/index.test.d.ts +8 -0
- package/dist/exchanges/polymarket_us/index.test.js +237 -0
- package/dist/exchanges/polymarket_us/normalizer.d.ts +55 -0
- package/dist/exchanges/polymarket_us/normalizer.js +385 -0
- package/dist/exchanges/polymarket_us/normalizer.test.d.ts +1 -0
- package/dist/exchanges/polymarket_us/normalizer.test.js +224 -0
- package/dist/exchanges/polymarket_us/price.d.ts +94 -0
- package/dist/exchanges/polymarket_us/price.js +149 -0
- package/dist/exchanges/polymarket_us/price.test.d.ts +1 -0
- package/dist/exchanges/polymarket_us/price.test.js +131 -0
- package/dist/exchanges/polymarket_us/websocket.d.ts +39 -0
- package/dist/exchanges/polymarket_us/websocket.js +181 -0
- package/dist/exchanges/polymarket_us/websocket.test.d.ts +8 -0
- package/dist/exchanges/polymarket_us/websocket.test.js +162 -0
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -1
- package/dist/server/app.js +6 -0
- package/dist/types.d.ts +4 -0
- package/package.json +4 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { OrderIntent, Amount } from 'polymarket-us';
|
|
2
|
+
/**
|
|
3
|
+
* Polymarket US price/quantity conversion utilities.
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL CONVENTION: Polymarket US uses a LONG-SIDE PRICE convention.
|
|
6
|
+
* The `price.value` field on every order is ALWAYS the YES-side (long)
|
|
7
|
+
* price between $0.01 and $0.99 - even for orders on the NO side.
|
|
8
|
+
*
|
|
9
|
+
* To express "I want NO at $0.40", you submit a long-side price of
|
|
10
|
+
* `1.00 - 0.40 = $0.60`.
|
|
11
|
+
*
|
|
12
|
+
* This module exposes pure helpers to convert between user-facing
|
|
13
|
+
* side prices and the long-side prices the API expects.
|
|
14
|
+
*/
|
|
15
|
+
export declare const POLYMARKET_US_MIN_PRICE = 0.01;
|
|
16
|
+
export declare const POLYMARKET_US_MAX_PRICE = 0.99;
|
|
17
|
+
/**
|
|
18
|
+
* Default minimum price increment for Polymarket US markets. Observed live
|
|
19
|
+
* markets report `orderPriceMinTickSize: 0.001`, so we default to that.
|
|
20
|
+
* Per-market overrides (surfaced on `UnifiedMarket.tickSize`) should be
|
|
21
|
+
* preferred when available.
|
|
22
|
+
*/
|
|
23
|
+
export declare const POLYMARKET_US_TICK_SIZE = 0.001;
|
|
24
|
+
/**
|
|
25
|
+
* Number of decimal places used when serializing `Amount.value` strings.
|
|
26
|
+
* Matches the 0.001 tick-size precision observed on the live gateway
|
|
27
|
+
* (e.g. `"0.864"`, `"0.999"`).
|
|
28
|
+
*/
|
|
29
|
+
export declare const POLYMARKET_US_PRICE_DECIMALS = 3;
|
|
30
|
+
export declare const POLYMARKET_US_CURRENCY: 'USD';
|
|
31
|
+
/**
|
|
32
|
+
* Round a price to a tick size using Math.round. Defaults to the
|
|
33
|
+
* Polymarket US tick size (0.001) but accepts a per-market override.
|
|
34
|
+
* Re-rounds to `POLYMARKET_US_PRICE_DECIMALS` afterwards to avoid
|
|
35
|
+
* floating-point drift.
|
|
36
|
+
*
|
|
37
|
+
* Note: this is a pure rounding helper - it does NOT validate bounds.
|
|
38
|
+
* Out-of-range inputs (e.g. 0.9999 -> 1.000) will pass through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export declare function roundToTickSize(price: number, tickSize?: number): number;
|
|
41
|
+
/**
|
|
42
|
+
* Validate a price is finite and within [POLYMARKET_US_MIN_PRICE, POLYMARKET_US_MAX_PRICE].
|
|
43
|
+
*
|
|
44
|
+
* @throws RangeError if price is not finite or out of bounds.
|
|
45
|
+
*/
|
|
46
|
+
export declare function validatePriceBounds(price: number): void;
|
|
47
|
+
/**
|
|
48
|
+
* Convert a user-supplied price (in their natural side, 0.01-0.99) to the
|
|
49
|
+
* long-side price required by the Polymarket US API.
|
|
50
|
+
*
|
|
51
|
+
* For LONG intents (BUY_LONG, SELL_LONG) the user price is already the
|
|
52
|
+
* long-side price and is returned unchanged.
|
|
53
|
+
*
|
|
54
|
+
* For SHORT intents (BUY_SHORT, SELL_SHORT) the long-side price is
|
|
55
|
+
* `1 - userPrice`.
|
|
56
|
+
*
|
|
57
|
+
* Both the input and the resulting long-side output are validated against
|
|
58
|
+
* the Polymarket US price bounds.
|
|
59
|
+
*
|
|
60
|
+
* @throws RangeError if userPrice (or the derived long-side price) is not
|
|
61
|
+
* finite or outside [0.01, 0.99].
|
|
62
|
+
*/
|
|
63
|
+
export declare function toLongSidePrice(intent: OrderIntent, userPrice: number): number;
|
|
64
|
+
/**
|
|
65
|
+
* Inverse of `toLongSidePrice`. Given a long-side price returned by the API
|
|
66
|
+
* and the order's intent, recover the user-facing side price.
|
|
67
|
+
*
|
|
68
|
+
* For LONG intents the long-side price IS the user price.
|
|
69
|
+
* For SHORT intents the user price is `1 - longPrice`.
|
|
70
|
+
*
|
|
71
|
+
* @throws RangeError if longPrice (or the derived user price) is not finite
|
|
72
|
+
* or outside [0.01, 0.99].
|
|
73
|
+
*/
|
|
74
|
+
export declare function fromLongSidePrice(intent: OrderIntent, longPrice: number): number;
|
|
75
|
+
/**
|
|
76
|
+
* Build an SDK `Amount` object from a numeric USD price.
|
|
77
|
+
*
|
|
78
|
+
* The price is first rounded to tick size and then validated against the
|
|
79
|
+
* Polymarket US bounds. The resulting value is formatted as a fixed
|
|
80
|
+
* `POLYMARKET_US_PRICE_DECIMALS`-decimal string (e.g. "0.550"), matching
|
|
81
|
+
* the precision the live gateway reports.
|
|
82
|
+
*
|
|
83
|
+
* An optional `tickSize` override allows per-market tick sizes (as
|
|
84
|
+
* surfaced on `UnifiedMarket.tickSize`) to be honoured instead of the
|
|
85
|
+
* default `POLYMARKET_US_TICK_SIZE`.
|
|
86
|
+
*
|
|
87
|
+
* @throws RangeError if the rounded price is out of bounds.
|
|
88
|
+
*/
|
|
89
|
+
export declare function toAmount(price: number, tickSize?: number): Amount;
|
|
90
|
+
/**
|
|
91
|
+
* Parse an SDK `Amount` back to a number. Returns 0 for undefined or empty
|
|
92
|
+
* values; otherwise delegates to `parseFloat`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function fromAmount(amount: Amount | undefined): number;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POLYMARKET_US_CURRENCY = exports.POLYMARKET_US_PRICE_DECIMALS = exports.POLYMARKET_US_TICK_SIZE = exports.POLYMARKET_US_MAX_PRICE = exports.POLYMARKET_US_MIN_PRICE = void 0;
|
|
4
|
+
exports.roundToTickSize = roundToTickSize;
|
|
5
|
+
exports.validatePriceBounds = validatePriceBounds;
|
|
6
|
+
exports.toLongSidePrice = toLongSidePrice;
|
|
7
|
+
exports.fromLongSidePrice = fromLongSidePrice;
|
|
8
|
+
exports.toAmount = toAmount;
|
|
9
|
+
exports.fromAmount = fromAmount;
|
|
10
|
+
/**
|
|
11
|
+
* Polymarket US price/quantity conversion utilities.
|
|
12
|
+
*
|
|
13
|
+
* CRITICAL CONVENTION: Polymarket US uses a LONG-SIDE PRICE convention.
|
|
14
|
+
* The `price.value` field on every order is ALWAYS the YES-side (long)
|
|
15
|
+
* price between $0.01 and $0.99 - even for orders on the NO side.
|
|
16
|
+
*
|
|
17
|
+
* To express "I want NO at $0.40", you submit a long-side price of
|
|
18
|
+
* `1.00 - 0.40 = $0.60`.
|
|
19
|
+
*
|
|
20
|
+
* This module exposes pure helpers to convert between user-facing
|
|
21
|
+
* side prices and the long-side prices the API expects.
|
|
22
|
+
*/
|
|
23
|
+
exports.POLYMARKET_US_MIN_PRICE = 0.01;
|
|
24
|
+
exports.POLYMARKET_US_MAX_PRICE = 0.99;
|
|
25
|
+
/**
|
|
26
|
+
* Default minimum price increment for Polymarket US markets. Observed live
|
|
27
|
+
* markets report `orderPriceMinTickSize: 0.001`, so we default to that.
|
|
28
|
+
* Per-market overrides (surfaced on `UnifiedMarket.tickSize`) should be
|
|
29
|
+
* preferred when available.
|
|
30
|
+
*/
|
|
31
|
+
exports.POLYMARKET_US_TICK_SIZE = 0.001;
|
|
32
|
+
/**
|
|
33
|
+
* Number of decimal places used when serializing `Amount.value` strings.
|
|
34
|
+
* Matches the 0.001 tick-size precision observed on the live gateway
|
|
35
|
+
* (e.g. `"0.864"`, `"0.999"`).
|
|
36
|
+
*/
|
|
37
|
+
exports.POLYMARKET_US_PRICE_DECIMALS = 3;
|
|
38
|
+
exports.POLYMARKET_US_CURRENCY = 'USD';
|
|
39
|
+
const LONG_INTENTS = new Set([
|
|
40
|
+
'ORDER_INTENT_BUY_LONG',
|
|
41
|
+
'ORDER_INTENT_SELL_LONG',
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Round a price to a tick size using Math.round. Defaults to the
|
|
45
|
+
* Polymarket US tick size (0.001) but accepts a per-market override.
|
|
46
|
+
* Re-rounds to `POLYMARKET_US_PRICE_DECIMALS` afterwards to avoid
|
|
47
|
+
* floating-point drift.
|
|
48
|
+
*
|
|
49
|
+
* Note: this is a pure rounding helper - it does NOT validate bounds.
|
|
50
|
+
* Out-of-range inputs (e.g. 0.9999 -> 1.000) will pass through unchanged.
|
|
51
|
+
*/
|
|
52
|
+
function roundToTickSize(price, tickSize = exports.POLYMARKET_US_TICK_SIZE) {
|
|
53
|
+
const ticks = Math.round(price / tickSize);
|
|
54
|
+
const rounded = ticks * tickSize;
|
|
55
|
+
const scale = Math.pow(10, exports.POLYMARKET_US_PRICE_DECIMALS);
|
|
56
|
+
return Math.round(rounded * scale) / scale;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validate a price is finite and within [POLYMARKET_US_MIN_PRICE, POLYMARKET_US_MAX_PRICE].
|
|
60
|
+
*
|
|
61
|
+
* @throws RangeError if price is not finite or out of bounds.
|
|
62
|
+
*/
|
|
63
|
+
function validatePriceBounds(price) {
|
|
64
|
+
if (!Number.isFinite(price)) {
|
|
65
|
+
throw new RangeError(`Polymarket US price must be a finite number, got: ${price}`);
|
|
66
|
+
}
|
|
67
|
+
if (price < exports.POLYMARKET_US_MIN_PRICE || price > exports.POLYMARKET_US_MAX_PRICE) {
|
|
68
|
+
throw new RangeError(`Polymarket US price ${price} is out of bounds [${exports.POLYMARKET_US_MIN_PRICE}, ${exports.POLYMARKET_US_MAX_PRICE}]`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert a user-supplied price (in their natural side, 0.01-0.99) to the
|
|
73
|
+
* long-side price required by the Polymarket US API.
|
|
74
|
+
*
|
|
75
|
+
* For LONG intents (BUY_LONG, SELL_LONG) the user price is already the
|
|
76
|
+
* long-side price and is returned unchanged.
|
|
77
|
+
*
|
|
78
|
+
* For SHORT intents (BUY_SHORT, SELL_SHORT) the long-side price is
|
|
79
|
+
* `1 - userPrice`.
|
|
80
|
+
*
|
|
81
|
+
* Both the input and the resulting long-side output are validated against
|
|
82
|
+
* the Polymarket US price bounds.
|
|
83
|
+
*
|
|
84
|
+
* @throws RangeError if userPrice (or the derived long-side price) is not
|
|
85
|
+
* finite or outside [0.01, 0.99].
|
|
86
|
+
*/
|
|
87
|
+
function toLongSidePrice(intent, userPrice) {
|
|
88
|
+
validatePriceBounds(userPrice);
|
|
89
|
+
if (LONG_INTENTS.has(intent)) {
|
|
90
|
+
return userPrice;
|
|
91
|
+
}
|
|
92
|
+
const longPrice = 1 - userPrice;
|
|
93
|
+
validatePriceBounds(longPrice);
|
|
94
|
+
return longPrice;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Inverse of `toLongSidePrice`. Given a long-side price returned by the API
|
|
98
|
+
* and the order's intent, recover the user-facing side price.
|
|
99
|
+
*
|
|
100
|
+
* For LONG intents the long-side price IS the user price.
|
|
101
|
+
* For SHORT intents the user price is `1 - longPrice`.
|
|
102
|
+
*
|
|
103
|
+
* @throws RangeError if longPrice (or the derived user price) is not finite
|
|
104
|
+
* or outside [0.01, 0.99].
|
|
105
|
+
*/
|
|
106
|
+
function fromLongSidePrice(intent, longPrice) {
|
|
107
|
+
validatePriceBounds(longPrice);
|
|
108
|
+
if (LONG_INTENTS.has(intent)) {
|
|
109
|
+
return longPrice;
|
|
110
|
+
}
|
|
111
|
+
const userPrice = 1 - longPrice;
|
|
112
|
+
validatePriceBounds(userPrice);
|
|
113
|
+
return userPrice;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build an SDK `Amount` object from a numeric USD price.
|
|
117
|
+
*
|
|
118
|
+
* The price is first rounded to tick size and then validated against the
|
|
119
|
+
* Polymarket US bounds. The resulting value is formatted as a fixed
|
|
120
|
+
* `POLYMARKET_US_PRICE_DECIMALS`-decimal string (e.g. "0.550"), matching
|
|
121
|
+
* the precision the live gateway reports.
|
|
122
|
+
*
|
|
123
|
+
* An optional `tickSize` override allows per-market tick sizes (as
|
|
124
|
+
* surfaced on `UnifiedMarket.tickSize`) to be honoured instead of the
|
|
125
|
+
* default `POLYMARKET_US_TICK_SIZE`.
|
|
126
|
+
*
|
|
127
|
+
* @throws RangeError if the rounded price is out of bounds.
|
|
128
|
+
*/
|
|
129
|
+
function toAmount(price, tickSize = exports.POLYMARKET_US_TICK_SIZE) {
|
|
130
|
+
if (!Number.isFinite(price)) {
|
|
131
|
+
throw new RangeError(`Polymarket US price must be a finite number, got: ${price}`);
|
|
132
|
+
}
|
|
133
|
+
const rounded = roundToTickSize(price, tickSize);
|
|
134
|
+
validatePriceBounds(rounded);
|
|
135
|
+
return {
|
|
136
|
+
value: rounded.toFixed(exports.POLYMARKET_US_PRICE_DECIMALS),
|
|
137
|
+
currency: exports.POLYMARKET_US_CURRENCY,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse an SDK `Amount` back to a number. Returns 0 for undefined or empty
|
|
142
|
+
* values; otherwise delegates to `parseFloat`.
|
|
143
|
+
*/
|
|
144
|
+
function fromAmount(amount) {
|
|
145
|
+
if (!amount || !amount.value) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
return parseFloat(amount.value);
|
|
149
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const price_1 = require("./price");
|
|
4
|
+
describe('roundToTickSize', () => {
|
|
5
|
+
it('is idempotent for 0.123 at the default 0.001 tick', () => {
|
|
6
|
+
expect((0, price_1.roundToTickSize)(0.123)).toBe(0.123);
|
|
7
|
+
});
|
|
8
|
+
it('rounds 0.1234 down to 0.123 at default tick', () => {
|
|
9
|
+
expect((0, price_1.roundToTickSize)(0.1234)).toBe(0.123);
|
|
10
|
+
});
|
|
11
|
+
it('rounds 0.1235 up to 0.124 (Math.round half-away-from-zero)', () => {
|
|
12
|
+
expect((0, price_1.roundToTickSize)(0.1235)).toBe(0.124);
|
|
13
|
+
});
|
|
14
|
+
it('rounds 0.9999 up to 1.000 (out of bounds, validation is separate)', () => {
|
|
15
|
+
expect((0, price_1.roundToTickSize)(0.9999)).toBe(1.0);
|
|
16
|
+
});
|
|
17
|
+
it('is idempotent for 0.55', () => {
|
|
18
|
+
expect((0, price_1.roundToTickSize)(0.55)).toBe(0.55);
|
|
19
|
+
});
|
|
20
|
+
it('handles floating-point drift cleanly', () => {
|
|
21
|
+
// 0.1 + 0.2 = 0.30000000000000004
|
|
22
|
+
expect((0, price_1.roundToTickSize)(0.1 + 0.2)).toBe(0.3);
|
|
23
|
+
});
|
|
24
|
+
it('honours an explicit tick-size override (0.01)', () => {
|
|
25
|
+
expect((0, price_1.roundToTickSize)(0.123, 0.01)).toBe(0.12);
|
|
26
|
+
expect((0, price_1.roundToTickSize)(0.125, 0.01)).toBe(0.13);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('validatePriceBounds', () => {
|
|
30
|
+
it.each([
|
|
31
|
+
price_1.POLYMARKET_US_MIN_PRICE,
|
|
32
|
+
0.5,
|
|
33
|
+
price_1.POLYMARKET_US_MAX_PRICE,
|
|
34
|
+
])('accepts %s', (price) => {
|
|
35
|
+
expect(() => (0, price_1.validatePriceBounds)(price)).not.toThrow();
|
|
36
|
+
});
|
|
37
|
+
it.each([
|
|
38
|
+
0.0,
|
|
39
|
+
0.001,
|
|
40
|
+
1.0,
|
|
41
|
+
1.5,
|
|
42
|
+
Number.NaN,
|
|
43
|
+
Number.POSITIVE_INFINITY,
|
|
44
|
+
Number.NEGATIVE_INFINITY,
|
|
45
|
+
])('throws RangeError for %s', (price) => {
|
|
46
|
+
expect(() => (0, price_1.validatePriceBounds)(price)).toThrow(RangeError);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('toLongSidePrice', () => {
|
|
50
|
+
it('returns user price unchanged for BUY_LONG', () => {
|
|
51
|
+
expect((0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', 0.55)).toBe(0.55);
|
|
52
|
+
});
|
|
53
|
+
it('returns user price unchanged for SELL_LONG', () => {
|
|
54
|
+
expect((0, price_1.toLongSidePrice)('ORDER_INTENT_SELL_LONG', 0.55)).toBe(0.55);
|
|
55
|
+
});
|
|
56
|
+
it('inverts user price for BUY_SHORT', () => {
|
|
57
|
+
expect((0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_SHORT', 0.4)).toBeCloseTo(0.6, 10);
|
|
58
|
+
});
|
|
59
|
+
it('inverts user price for SELL_SHORT', () => {
|
|
60
|
+
expect((0, price_1.toLongSidePrice)('ORDER_INTENT_SELL_SHORT', 0.4)).toBeCloseTo(0.6, 10);
|
|
61
|
+
});
|
|
62
|
+
it('throws on out-of-bounds input (LONG)', () => {
|
|
63
|
+
expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', 1.5)).toThrow(RangeError);
|
|
64
|
+
expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', 0)).toThrow(RangeError);
|
|
65
|
+
});
|
|
66
|
+
it('throws on out-of-bounds input (SHORT)', () => {
|
|
67
|
+
expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_SHORT', 1.5)).toThrow(RangeError);
|
|
68
|
+
expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_SELL_SHORT', 0)).toThrow(RangeError);
|
|
69
|
+
});
|
|
70
|
+
it('throws on NaN', () => {
|
|
71
|
+
expect(() => (0, price_1.toLongSidePrice)('ORDER_INTENT_BUY_LONG', Number.NaN)).toThrow(RangeError);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('fromLongSidePrice (round trip)', () => {
|
|
75
|
+
const intents = [
|
|
76
|
+
'ORDER_INTENT_BUY_LONG',
|
|
77
|
+
'ORDER_INTENT_SELL_LONG',
|
|
78
|
+
'ORDER_INTENT_BUY_SHORT',
|
|
79
|
+
'ORDER_INTENT_SELL_SHORT',
|
|
80
|
+
];
|
|
81
|
+
const prices = [0.01, 0.25, 0.5, 0.75, 0.99];
|
|
82
|
+
for (const intent of intents) {
|
|
83
|
+
for (const price of prices) {
|
|
84
|
+
it(`round-trips ${price} for ${intent}`, () => {
|
|
85
|
+
const longPrice = (0, price_1.toLongSidePrice)(intent, price);
|
|
86
|
+
const userPrice = (0, price_1.fromLongSidePrice)(intent, longPrice);
|
|
87
|
+
expect(userPrice).toBeCloseTo(price, 10);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
it('throws on out-of-bounds input', () => {
|
|
92
|
+
expect(() => (0, price_1.fromLongSidePrice)('ORDER_INTENT_BUY_LONG', 1.5)).toThrow(RangeError);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('toAmount', () => {
|
|
96
|
+
it('formats 0.55 to { value: "0.550", currency: "USD" } at 3-decimal precision', () => {
|
|
97
|
+
expect((0, price_1.toAmount)(0.55)).toEqual({ value: '0.550', currency: 'USD' });
|
|
98
|
+
});
|
|
99
|
+
it('keeps 0.123 as "0.123" at the default 0.001 tick', () => {
|
|
100
|
+
expect((0, price_1.toAmount)(0.123)).toEqual({ value: '0.123', currency: 'USD' });
|
|
101
|
+
});
|
|
102
|
+
it('rounds 0.1234 down to "0.123"', () => {
|
|
103
|
+
expect((0, price_1.toAmount)(0.1234)).toEqual({ value: '0.123', currency: 'USD' });
|
|
104
|
+
});
|
|
105
|
+
it('honours an explicit 0.01 tick override', () => {
|
|
106
|
+
expect((0, price_1.toAmount)(0.123, 0.01)).toEqual({ value: '0.120', currency: 'USD' });
|
|
107
|
+
});
|
|
108
|
+
it('throws for 0.0 (below min)', () => {
|
|
109
|
+
expect(() => (0, price_1.toAmount)(0.0)).toThrow(RangeError);
|
|
110
|
+
});
|
|
111
|
+
it('throws for 1.0 (above max)', () => {
|
|
112
|
+
expect(() => (0, price_1.toAmount)(1.0)).toThrow(RangeError);
|
|
113
|
+
});
|
|
114
|
+
it('throws for NaN', () => {
|
|
115
|
+
expect(() => (0, price_1.toAmount)(Number.NaN)).toThrow(RangeError);
|
|
116
|
+
});
|
|
117
|
+
it('throws for Infinity', () => {
|
|
118
|
+
expect(() => (0, price_1.toAmount)(Number.POSITIVE_INFINITY)).toThrow(RangeError);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('fromAmount', () => {
|
|
122
|
+
it('parses { value: "0.550" } to 0.55', () => {
|
|
123
|
+
expect((0, price_1.fromAmount)({ value: '0.550', currency: 'USD' })).toBe(0.55);
|
|
124
|
+
});
|
|
125
|
+
it('returns 0 for undefined', () => {
|
|
126
|
+
expect((0, price_1.fromAmount)(undefined)).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
it('returns 0 for empty string value', () => {
|
|
129
|
+
expect((0, price_1.fromAmount)({ value: '', currency: 'USD' })).toBe(0);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polymarket US WebSocket wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Provides CCXT Pro-style `watchOrderBook()` and `watchTrades()` on top of
|
|
5
|
+
* the `polymarket-us` SDK's `MarketsWebSocket` class. Each call awaits the
|
|
6
|
+
* next matching event for the requested market slug (the last segment of a
|
|
7
|
+
* PMXT outcomeId is stripped before subscribing).
|
|
8
|
+
*
|
|
9
|
+
* Notes:
|
|
10
|
+
* - The SDK's WebSocket factory requires `keyId` + `secretKey` even for
|
|
11
|
+
* the public `markets()` socket, so callers must instantiate the exchange
|
|
12
|
+
* with credentials before calling any watch method.
|
|
13
|
+
* - Order book and trade prices are reported as LONG-SIDE prices, matching
|
|
14
|
+
* the convention used by `normalizeOrderBook` and `normalizeOrder`.
|
|
15
|
+
*/
|
|
16
|
+
import type { PolymarketUS as PolymarketUSClient } from 'polymarket-us';
|
|
17
|
+
import { OrderBook, Trade } from '../../types';
|
|
18
|
+
import { PolymarketUSNormalizer } from './normalizer';
|
|
19
|
+
export declare class PolymarketUSWebSocket {
|
|
20
|
+
private readonly client;
|
|
21
|
+
private readonly normalizer;
|
|
22
|
+
private socket;
|
|
23
|
+
private initializationPromise?;
|
|
24
|
+
private readonly bookSubscriptions;
|
|
25
|
+
private readonly tradeSubscriptions;
|
|
26
|
+
private readonly orderBookResolvers;
|
|
27
|
+
private readonly tradeResolvers;
|
|
28
|
+
constructor(client: PolymarketUSClient, normalizer: PolymarketUSNormalizer);
|
|
29
|
+
watchOrderBook(id: string): Promise<OrderBook>;
|
|
30
|
+
watchTrades(id: string): Promise<Trade[]>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
private ensureInitialized;
|
|
33
|
+
private handleMarketData;
|
|
34
|
+
private handleTrade;
|
|
35
|
+
private handleError;
|
|
36
|
+
private handleClose;
|
|
37
|
+
private resolveOrderBook;
|
|
38
|
+
private rejectAllPending;
|
|
39
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Polymarket US WebSocket wrapper.
|
|
4
|
+
*
|
|
5
|
+
* Provides CCXT Pro-style `watchOrderBook()` and `watchTrades()` on top of
|
|
6
|
+
* the `polymarket-us` SDK's `MarketsWebSocket` class. Each call awaits the
|
|
7
|
+
* next matching event for the requested market slug (the last segment of a
|
|
8
|
+
* PMXT outcomeId is stripped before subscribing).
|
|
9
|
+
*
|
|
10
|
+
* Notes:
|
|
11
|
+
* - The SDK's WebSocket factory requires `keyId` + `secretKey` even for
|
|
12
|
+
* the public `markets()` socket, so callers must instantiate the exchange
|
|
13
|
+
* with credentials before calling any watch method.
|
|
14
|
+
* - Order book and trade prices are reported as LONG-SIDE prices, matching
|
|
15
|
+
* the convention used by `normalizeOrderBook` and `normalizeOrder`.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.PolymarketUSWebSocket = void 0;
|
|
19
|
+
const price_1 = require("./price");
|
|
20
|
+
function sideFromOrderSide(raw) {
|
|
21
|
+
if (raw === 'ORDER_SIDE_BUY')
|
|
22
|
+
return 'buy';
|
|
23
|
+
if (raw === 'ORDER_SIDE_SELL')
|
|
24
|
+
return 'sell';
|
|
25
|
+
return 'unknown';
|
|
26
|
+
}
|
|
27
|
+
function parseTradeTimeMs(value) {
|
|
28
|
+
if (!value)
|
|
29
|
+
return Date.now();
|
|
30
|
+
const ms = new Date(value).getTime();
|
|
31
|
+
return Number.isFinite(ms) ? ms : Date.now();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Strip a trailing `:long` or `:short` from a PMXT identifier to recover
|
|
35
|
+
* the bare Polymarket US market slug.
|
|
36
|
+
*/
|
|
37
|
+
function slugFromId(id) {
|
|
38
|
+
if (id.endsWith(':long'))
|
|
39
|
+
return id.slice(0, -':long'.length);
|
|
40
|
+
if (id.endsWith(':short'))
|
|
41
|
+
return id.slice(0, -':short'.length);
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
44
|
+
class PolymarketUSWebSocket {
|
|
45
|
+
client;
|
|
46
|
+
normalizer;
|
|
47
|
+
socket = null;
|
|
48
|
+
initializationPromise;
|
|
49
|
+
bookSubscriptions = new Set();
|
|
50
|
+
tradeSubscriptions = new Set();
|
|
51
|
+
orderBookResolvers = new Map();
|
|
52
|
+
tradeResolvers = new Map();
|
|
53
|
+
constructor(client, normalizer) {
|
|
54
|
+
this.client = client;
|
|
55
|
+
this.normalizer = normalizer;
|
|
56
|
+
}
|
|
57
|
+
async watchOrderBook(id) {
|
|
58
|
+
const slug = slugFromId(id);
|
|
59
|
+
await this.ensureInitialized();
|
|
60
|
+
if (!this.bookSubscriptions.has(slug)) {
|
|
61
|
+
this.bookSubscriptions.add(slug);
|
|
62
|
+
this.socket.subscribeMarketData(`book:${slug}`, [slug]);
|
|
63
|
+
}
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const queue = this.orderBookResolvers.get(slug) ?? [];
|
|
66
|
+
queue.push({ resolve, reject });
|
|
67
|
+
this.orderBookResolvers.set(slug, queue);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async watchTrades(id) {
|
|
71
|
+
const slug = slugFromId(id);
|
|
72
|
+
await this.ensureInitialized();
|
|
73
|
+
if (!this.tradeSubscriptions.has(slug)) {
|
|
74
|
+
this.tradeSubscriptions.add(slug);
|
|
75
|
+
this.socket.subscribeTrades(`trade:${slug}`, [slug]);
|
|
76
|
+
}
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const queue = this.tradeResolvers.get(slug) ?? [];
|
|
79
|
+
queue.push({ resolve, reject });
|
|
80
|
+
this.tradeResolvers.set(slug, queue);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async close() {
|
|
84
|
+
if (this.socket) {
|
|
85
|
+
try {
|
|
86
|
+
this.socket.close();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Ignore close errors.
|
|
90
|
+
}
|
|
91
|
+
this.socket = null;
|
|
92
|
+
}
|
|
93
|
+
this.bookSubscriptions.clear();
|
|
94
|
+
this.tradeSubscriptions.clear();
|
|
95
|
+
this.rejectAllPending(new Error('PolymarketUS WebSocket closed'));
|
|
96
|
+
this.initializationPromise = undefined;
|
|
97
|
+
}
|
|
98
|
+
async ensureInitialized() {
|
|
99
|
+
if (this.initializationPromise)
|
|
100
|
+
return this.initializationPromise;
|
|
101
|
+
this.initializationPromise = (async () => {
|
|
102
|
+
const socket = this.client.ws.markets();
|
|
103
|
+
socket.on('marketData', (msg) => this.handleMarketData(msg));
|
|
104
|
+
socket.on('trade', (msg) => this.handleTrade(msg));
|
|
105
|
+
socket.on('error', (err) => this.handleError(err));
|
|
106
|
+
socket.on('close', () => this.handleClose());
|
|
107
|
+
await socket.connect();
|
|
108
|
+
this.socket = socket;
|
|
109
|
+
})();
|
|
110
|
+
try {
|
|
111
|
+
await this.initializationPromise;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
this.initializationPromise = undefined;
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
handleMarketData(msg) {
|
|
119
|
+
const payload = msg.marketData;
|
|
120
|
+
if (!payload)
|
|
121
|
+
return;
|
|
122
|
+
const slug = payload.marketSlug;
|
|
123
|
+
// The WS MarketData payload shape is structurally compatible with
|
|
124
|
+
// the REST MarketBook shape used by normalizeOrderBook.
|
|
125
|
+
const book = this.normalizer.normalizeOrderBook(payload, slug);
|
|
126
|
+
this.resolveOrderBook(slug, book);
|
|
127
|
+
}
|
|
128
|
+
handleTrade(msg) {
|
|
129
|
+
const payload = msg.trade;
|
|
130
|
+
if (!payload)
|
|
131
|
+
return;
|
|
132
|
+
const slug = payload.marketSlug;
|
|
133
|
+
const timestamp = parseTradeTimeMs(payload.tradeTime);
|
|
134
|
+
const priceValue = payload.price?.value ?? '0';
|
|
135
|
+
const trade = {
|
|
136
|
+
id: `${slug}-${timestamp}-${priceValue}`,
|
|
137
|
+
timestamp,
|
|
138
|
+
price: (0, price_1.fromAmount)(payload.price),
|
|
139
|
+
amount: (0, price_1.fromAmount)(payload.quantity),
|
|
140
|
+
side: sideFromOrderSide(payload.taker?.side),
|
|
141
|
+
};
|
|
142
|
+
const queue = this.tradeResolvers.get(slug);
|
|
143
|
+
if (queue && queue.length > 0) {
|
|
144
|
+
for (const { resolve } of queue)
|
|
145
|
+
resolve([trade]);
|
|
146
|
+
this.tradeResolvers.set(slug, []);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
handleError(err) {
|
|
150
|
+
// Surface the error to any waiting callers rather than losing it.
|
|
151
|
+
this.rejectAllPending(err);
|
|
152
|
+
}
|
|
153
|
+
handleClose() {
|
|
154
|
+
this.rejectAllPending(new Error('PolymarketUS WebSocket closed'));
|
|
155
|
+
this.socket = null;
|
|
156
|
+
this.initializationPromise = undefined;
|
|
157
|
+
this.bookSubscriptions.clear();
|
|
158
|
+
this.tradeSubscriptions.clear();
|
|
159
|
+
}
|
|
160
|
+
resolveOrderBook(slug, book) {
|
|
161
|
+
const queue = this.orderBookResolvers.get(slug);
|
|
162
|
+
if (queue && queue.length > 0) {
|
|
163
|
+
for (const { resolve } of queue)
|
|
164
|
+
resolve(book);
|
|
165
|
+
this.orderBookResolvers.set(slug, []);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
rejectAllPending(err) {
|
|
169
|
+
for (const queue of this.orderBookResolvers.values()) {
|
|
170
|
+
for (const { reject } of queue)
|
|
171
|
+
reject(err);
|
|
172
|
+
}
|
|
173
|
+
this.orderBookResolvers.clear();
|
|
174
|
+
for (const queue of this.tradeResolvers.values()) {
|
|
175
|
+
for (const { reject } of queue)
|
|
176
|
+
reject(err);
|
|
177
|
+
}
|
|
178
|
+
this.tradeResolvers.clear();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.PolymarketUSWebSocket = PolymarketUSWebSocket;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for PolymarketUSWebSocket.
|
|
3
|
+
*
|
|
4
|
+
* The polymarket-us SDK is fully stubbed here so the tests run without
|
|
5
|
+
* touching the network. A fake MarketsWebSocket captures subscriptions
|
|
6
|
+
* and lets each test fire synthetic messages through a listener map.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|