parlayx 3.2.2__tar.gz → 3.2.3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: parlayx
3
- Version: 3.2.2
3
+ Version: 3.2.3
4
4
  Summary: Official Python client for the ParlayX public API.
5
5
  Project-URL: Homepage, https://docs.parlayx.com
6
6
  Project-URL: Documentation, https://docs.parlayx.com
@@ -222,6 +222,90 @@ Set `time_in_force={"type": "FOK"}` for the venue's fill-or-kill, where an order
222
222
 
223
223
  Discovery listings report `venue` in uppercase (`KALSHI`, `POLYMARKET`), while the order and streaming surfaces take it lowercase. The client passes the value through unchanged in both directions rather than quietly rewriting it.
224
224
 
225
+ ## Connections
226
+
227
+ From 3.2.3 the client keeps its connection to the API open between requests, so
228
+ an order sent after a few seconds of quiet does not pay for a new TLS handshake. Create
229
+ one client when your process starts and reuse it.
230
+
231
+ Close it when your work is done, or use it as a context manager:
232
+
233
+ ```python
234
+ with ParlayX() as client:
235
+ client.whoami()
236
+ ```
237
+
238
+ Supplying your own `transport` replaces the pool, and with it the setting that
239
+ holds the connection open.
240
+
241
+ ## Migrating to 3.2.3
242
+
243
+ Kalshi orders and positions are now described the way Kalshi describes them, and
244
+ every field below changed shape. Nothing else changed; the connection behaviour
245
+ above is separate and needs nothing from you.
246
+
247
+ A Kalshi order carries one `side`, either `"BID"` or `"ASK"`, in place of the old
248
+ `side` (`"YES"` / `"NO"`) and `action` (`"BUY"` / `"SELL"`) pair. A `BID` buys YES
249
+ contracts and profits if the market resolves YES; an `ASK` sells them and profits
250
+ if it resolves NO. Selling YES and buying NO are the same order on Kalshi's book,
251
+ so both are `"ASK"`.
252
+
253
+ ```python
254
+ # 3.2.2
255
+ order = KalshiOrderRequest(ticker=ticker, side="YES", action="BUY", count=10, price_cents=38)
256
+ # 3.2.3
257
+ order = KalshiOrderRequest(ticker=ticker, side="BID", count="10", price="0.38")
258
+ ```
259
+
260
+ The old pair maps across like this:
261
+
262
+ | 3.2.2 `side` + `action` | 3.2.3 `side` | 3.2.3 `price` |
263
+ | ----------------------- | ------------ | ----------------------------- |
264
+ | `YES` + `BUY` | `BID` | the YES price you sent |
265
+ | `YES` + `SELL` | `ASK` | the YES price you sent |
266
+ | `NO` + `BUY` | `ASK` | 1 minus the NO price you sent |
267
+ | `NO` + `SELL` | `BID` | 1 minus the NO price you sent |
268
+
269
+ `count` and `price` are decimal strings rather than numbers. `price` is dollars
270
+ per contract on the market's YES book, replacing `price_cents`; `"0.38"` is what
271
+ `price_cents=38` used to mean. Contracts trade in steps of 0.01 and prices go as
272
+ fine as $0.0001, so sizes and prices Kalshi accepts are no longer refused before
273
+ they reach it. Each market publishes its own grid of valid prices, and a price
274
+ off that grid is refused rather than moved to the nearest one.
275
+
276
+ Order and fill reads changed to match: `side` reads `"BID"` or `"ASK"`, `count`
277
+ and `price` are decimal strings on the YES book, and `average_price` replaces
278
+ `average_price_cents`. Orders placed before the upgrade read back in the new
279
+ vocabulary, including ones placed on the NO leg. Amend and decrease take the same
280
+ decimal strings, and their `previous` block reports `price` and `count` the same
281
+ way.
282
+
283
+ Positions changed with them. A Kalshi position is keyed by ticker alone, because
284
+ the venue nets a market into one holding, and `side` is gone from the row.
285
+ `contracts` is a decimal string on the market's YES side and its sign says which
286
+ way the holding leans: positive is long YES, and negative is long NO, which is
287
+ the same holding as short YES. It is a decimal because contracts trade in steps
288
+ of 0.01. A ticker you hold both ways is one row now rather
289
+ than two you have to net yourself.
290
+
291
+ ```python
292
+ from decimal import Decimal
293
+
294
+ # 3.2.2: two rows for one holding, netted by hand, as integers
295
+ long, short = (p for p in positions if p.ticker == ticker)
296
+ net = long.contracts - short.contracts
297
+ # 3.2.3: one row, and the venue's own number
298
+ (position,) = (p for p in positions if p.ticker == ticker)
299
+ net = Decimal(position.contracts)
300
+ ```
301
+
302
+ `average_price` and `mark` replace `average_price_cents` and `mark_cents`, as
303
+ dollar strings from 0 to 1 on that same YES side, which is the frame you submit a
304
+ price in. They are no longer rounded to a whole cent, so a market quoting in
305
+ tenths of a cent reports the price it actually trades at. `average_price` reads
306
+ `None` when the holding has no single entry price, and `value_usd` and
307
+ `unrealized_pnl_usd` compose from the two published figures.
308
+
225
309
  ## License
226
310
 
227
311
  MIT. See [LICENSE](./LICENSE).
@@ -202,6 +202,90 @@ Set `time_in_force={"type": "FOK"}` for the venue's fill-or-kill, where an order
202
202
 
203
203
  Discovery listings report `venue` in uppercase (`KALSHI`, `POLYMARKET`), while the order and streaming surfaces take it lowercase. The client passes the value through unchanged in both directions rather than quietly rewriting it.
204
204
 
205
+ ## Connections
206
+
207
+ From 3.2.3 the client keeps its connection to the API open between requests, so
208
+ an order sent after a few seconds of quiet does not pay for a new TLS handshake. Create
209
+ one client when your process starts and reuse it.
210
+
211
+ Close it when your work is done, or use it as a context manager:
212
+
213
+ ```python
214
+ with ParlayX() as client:
215
+ client.whoami()
216
+ ```
217
+
218
+ Supplying your own `transport` replaces the pool, and with it the setting that
219
+ holds the connection open.
220
+
221
+ ## Migrating to 3.2.3
222
+
223
+ Kalshi orders and positions are now described the way Kalshi describes them, and
224
+ every field below changed shape. Nothing else changed; the connection behaviour
225
+ above is separate and needs nothing from you.
226
+
227
+ A Kalshi order carries one `side`, either `"BID"` or `"ASK"`, in place of the old
228
+ `side` (`"YES"` / `"NO"`) and `action` (`"BUY"` / `"SELL"`) pair. A `BID` buys YES
229
+ contracts and profits if the market resolves YES; an `ASK` sells them and profits
230
+ if it resolves NO. Selling YES and buying NO are the same order on Kalshi's book,
231
+ so both are `"ASK"`.
232
+
233
+ ```python
234
+ # 3.2.2
235
+ order = KalshiOrderRequest(ticker=ticker, side="YES", action="BUY", count=10, price_cents=38)
236
+ # 3.2.3
237
+ order = KalshiOrderRequest(ticker=ticker, side="BID", count="10", price="0.38")
238
+ ```
239
+
240
+ The old pair maps across like this:
241
+
242
+ | 3.2.2 `side` + `action` | 3.2.3 `side` | 3.2.3 `price` |
243
+ | ----------------------- | ------------ | ----------------------------- |
244
+ | `YES` + `BUY` | `BID` | the YES price you sent |
245
+ | `YES` + `SELL` | `ASK` | the YES price you sent |
246
+ | `NO` + `BUY` | `ASK` | 1 minus the NO price you sent |
247
+ | `NO` + `SELL` | `BID` | 1 minus the NO price you sent |
248
+
249
+ `count` and `price` are decimal strings rather than numbers. `price` is dollars
250
+ per contract on the market's YES book, replacing `price_cents`; `"0.38"` is what
251
+ `price_cents=38` used to mean. Contracts trade in steps of 0.01 and prices go as
252
+ fine as $0.0001, so sizes and prices Kalshi accepts are no longer refused before
253
+ they reach it. Each market publishes its own grid of valid prices, and a price
254
+ off that grid is refused rather than moved to the nearest one.
255
+
256
+ Order and fill reads changed to match: `side` reads `"BID"` or `"ASK"`, `count`
257
+ and `price` are decimal strings on the YES book, and `average_price` replaces
258
+ `average_price_cents`. Orders placed before the upgrade read back in the new
259
+ vocabulary, including ones placed on the NO leg. Amend and decrease take the same
260
+ decimal strings, and their `previous` block reports `price` and `count` the same
261
+ way.
262
+
263
+ Positions changed with them. A Kalshi position is keyed by ticker alone, because
264
+ the venue nets a market into one holding, and `side` is gone from the row.
265
+ `contracts` is a decimal string on the market's YES side and its sign says which
266
+ way the holding leans: positive is long YES, and negative is long NO, which is
267
+ the same holding as short YES. It is a decimal because contracts trade in steps
268
+ of 0.01. A ticker you hold both ways is one row now rather
269
+ than two you have to net yourself.
270
+
271
+ ```python
272
+ from decimal import Decimal
273
+
274
+ # 3.2.2: two rows for one holding, netted by hand, as integers
275
+ long, short = (p for p in positions if p.ticker == ticker)
276
+ net = long.contracts - short.contracts
277
+ # 3.2.3: one row, and the venue's own number
278
+ (position,) = (p for p in positions if p.ticker == ticker)
279
+ net = Decimal(position.contracts)
280
+ ```
281
+
282
+ `average_price` and `mark` replace `average_price_cents` and `mark_cents`, as
283
+ dollar strings from 0 to 1 on that same YES side, which is the frame you submit a
284
+ price in. They are no longer rounded to a whole cent, so a market quoting in
285
+ tenths of a cent reports the price it actually trades at. `average_price` reads
286
+ `None` when the holding has no single entry price, and `value_usd` and
287
+ `unrealized_pnl_usd` compose from the two published figures.
288
+
205
289
  ## License
206
290
 
207
291
  MIT. See [LICENSE](./LICENSE).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "parlayx"
7
- version = "3.2.2"
7
+ version = "3.2.3"
8
8
  description = "Official Python client for the ParlayX public API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -39,21 +39,23 @@ from ._generated.models import (
39
39
  GetLimitlessOrderFillsResponse,
40
40
  GetPolymarketOrderFillsResponse,
41
41
  GetProphetXOrderFillsResponse,
42
- KalshiAction,
43
42
  KalshiBalance,
43
+ KalshiBatchOrder,
44
44
  KalshiFillView,
45
45
  KalshiListing,
46
+ KalshiOrderQueuePosition,
46
47
  KalshiOrderRequest,
48
+ KalshiOrderSide,
47
49
  KalshiOrderType,
48
50
  KalshiOrderView,
49
51
  KalshiPosition,
50
- KalshiSide,
51
52
  LimitlessBalance,
52
53
  LimitlessOrderRequest,
53
54
  LimitlessOrderView,
54
55
  ListCompetitionsResponse,
55
56
  ListEventMarketsResponse,
56
57
  ListEventsResponse,
58
+ ListKalshiOrderQueuePositionsResponse,
57
59
  ListKalshiOrdersResponse,
58
60
  ListKalshiPositionsResponse,
59
61
  ListLimitlessMarketsResponse,
@@ -67,6 +69,7 @@ from ._generated.models import (
67
69
  LookupMarketResponse,
68
70
  Outcome,
69
71
  PolymarketBalance,
72
+ PolymarketBatchOrderResult,
70
73
  PolymarketCancelFailureCode,
71
74
  PolymarketCancellationReason,
72
75
  PolymarketFillView,
@@ -78,13 +81,17 @@ from ._generated.models import (
78
81
  PolymarketSide,
79
82
  PolymarketUncancelledOrder,
80
83
  ProphetXBalance,
84
+ ProphetXBatchOrder,
85
+ ProphetXFailedOrder,
81
86
  ProphetXFillView,
82
87
  ProphetXFokTimeInForce,
88
+ ProphetXGtdTimeInForce,
83
89
  ProphetXLadder,
84
90
  ProphetXListing,
85
91
  ProphetXOrderRequest,
86
92
  ProphetXOrderView,
87
93
  ProphetXPosition,
94
+ ProphetXSucceededOrder,
88
95
  ReplaceLimitlessOrderRequest,
89
96
  ReplaceLimitlessOrderResponse,
90
97
  ReplaceLimitlessOrdersRequest,
@@ -92,9 +99,15 @@ from ._generated.models import (
92
99
  ServerTimeResponse,
93
100
  Sport,
94
101
  SubmitKalshiOrderResponse,
102
+ SubmitKalshiOrdersRequest,
103
+ SubmitKalshiOrdersResponse,
95
104
  SubmitLimitlessOrderResponse,
96
105
  SubmitPolymarketOrderResponse,
106
+ SubmitPolymarketOrdersRequest,
107
+ SubmitPolymarketOrdersResponse,
97
108
  SubmitProphetXOrderResponse,
109
+ SubmitProphetXOrdersRequest,
110
+ SubmitProphetXOrdersResponse,
98
111
  TradingOrderStatus,
99
112
  TradingOrderType,
100
113
  WhoamiResponse,
@@ -138,16 +151,17 @@ __all__ = [
138
151
  "GetLimitlessOrderFillsResponse",
139
152
  "GetPolymarketOrderFillsResponse",
140
153
  "GetProphetXOrderFillsResponse",
141
- "KalshiAction",
142
154
  "KalshiBalance",
155
+ "KalshiBatchOrder",
143
156
  "KalshiFillView",
144
157
  "KalshiListing",
158
+ "KalshiOrderQueuePosition",
145
159
  "KalshiOrderRequest",
160
+ "KalshiOrderSide",
146
161
  "KalshiOrderType",
147
162
  "KalshiOrderView",
148
163
  "KalshiPosition",
149
164
  "KalshiResource",
150
- "KalshiSide",
151
165
  "LimitlessBalance",
152
166
  "LimitlessOrderRequest",
153
167
  "LimitlessOrderView",
@@ -155,6 +169,7 @@ __all__ = [
155
169
  "ListCompetitionsResponse",
156
170
  "ListEventMarketsResponse",
157
171
  "ListEventsResponse",
172
+ "ListKalshiOrderQueuePositionsResponse",
158
173
  "ListKalshiOrdersResponse",
159
174
  "ListKalshiPositionsResponse",
160
175
  "ListLimitlessMarketsResponse",
@@ -169,6 +184,7 @@ __all__ = [
169
184
  "Outcome",
170
185
  "ParlayX",
171
186
  "PolymarketBalance",
187
+ "PolymarketBatchOrderResult",
172
188
  "PolymarketCancelFailureCode",
173
189
  "PolymarketCancellationReason",
174
190
  "PolymarketFillView",
@@ -182,14 +198,18 @@ __all__ = [
182
198
  "PolymarketSide",
183
199
  "PolymarketUncancelledOrder",
184
200
  "ProphetXBalance",
201
+ "ProphetXBatchOrder",
202
+ "ProphetXFailedOrder",
185
203
  "ProphetXFillView",
186
204
  "ProphetXFokTimeInForce",
205
+ "ProphetXGtdTimeInForce",
187
206
  "ProphetXLadder",
188
207
  "ProphetXListing",
189
208
  "ProphetXOrderRequest",
190
209
  "ProphetXOrderView",
191
210
  "ProphetXPosition",
192
211
  "ProphetXResource",
212
+ "ProphetXSucceededOrder",
193
213
  "ReplaceLimitlessOrderRequest",
194
214
  "ReplaceLimitlessOrderResponse",
195
215
  "ReplaceLimitlessOrdersRequest",
@@ -198,9 +218,15 @@ __all__ = [
198
218
  "ServerTimeResponse",
199
219
  "Sport",
200
220
  "SubmitKalshiOrderResponse",
221
+ "SubmitKalshiOrdersRequest",
222
+ "SubmitKalshiOrdersResponse",
201
223
  "SubmitLimitlessOrderResponse",
202
224
  "SubmitPolymarketOrderResponse",
225
+ "SubmitPolymarketOrdersRequest",
226
+ "SubmitPolymarketOrdersResponse",
203
227
  "SubmitProphetXOrderResponse",
228
+ "SubmitProphetXOrdersRequest",
229
+ "SubmitProphetXOrdersResponse",
204
230
  "TradingOrderStatus",
205
231
  "TradingOrderType",
206
232
  "WhoamiResponse",
@@ -34,6 +34,7 @@ from ._generated.models import (
34
34
  ListCompetitionsResponse,
35
35
  ListEventMarketsResponse,
36
36
  ListEventsResponse,
37
+ ListKalshiOrderQueuePositionsResponse,
37
38
  ListKalshiOrdersResponse,
38
39
  ListKalshiPositionsResponse,
39
40
  ListLimitlessMarketsResponse,
@@ -57,13 +58,20 @@ from ._generated.models import (
57
58
  ReplaceLimitlessOrdersResponse,
58
59
  ServerTimeResponse,
59
60
  SubmitKalshiOrderResponse,
61
+ SubmitKalshiOrdersRequest,
62
+ SubmitKalshiOrdersResponse,
60
63
  SubmitLimitlessOrderResponse,
61
64
  SubmitPolymarketOrderResponse,
65
+ SubmitPolymarketOrdersRequest,
66
+ SubmitPolymarketOrdersResponse,
62
67
  SubmitProphetXOrderResponse,
68
+ SubmitProphetXOrdersRequest,
69
+ SubmitProphetXOrdersResponse,
63
70
  WhoamiResponse,
64
71
  )
65
72
  from ._transport import (
66
73
  DEFAULT_BASE_URL,
74
+ KEEPALIVE_LIMITS,
67
75
  ModelT,
68
76
  build_signed_request,
69
77
  new_idempotency_key,
@@ -97,6 +105,28 @@ class AsyncPolymarketResource:
97
105
  idempotency_key=new_idempotency_key(idempotency_key),
98
106
  )
99
107
 
108
+ async def submit_orders(
109
+ self, request: SubmitPolymarketOrdersRequest, *, idempotency_key: str | None = None
110
+ ) -> SubmitPolymarketOrdersResponse:
111
+ """Submit up to 15 orders in one request under one idempotency key.
112
+
113
+ The key defaults to a fresh value per call, so an unkeyed retry submits the
114
+ batch AGAIN. Pass your own and re-send the identical batch to retry safely.
115
+ Each order gets its own outcome, so a refused entry does not discard its
116
+ siblings. ``orders[i]`` answers request entry ``i``: ``success`` is true when
117
+ it was accepted, which is not confirmation at the venue and not a fill,
118
+ otherwise ``error`` says why. A ``success: false`` entry with an
119
+ ``ORDER_RECONCILIATION_REQUIRED`` error is not proof nothing was placed: its
120
+ outcome is unknown, so read it back by the entry's ``order_id``.
121
+ """
122
+ return await self._client._request(
123
+ "POST",
124
+ "/v1/polymarket/orders/batch",
125
+ SubmitPolymarketOrdersResponse,
126
+ body=request,
127
+ idempotency_key=new_idempotency_key(idempotency_key),
128
+ )
129
+
100
130
  async def list_orders(
101
131
  self,
102
132
  *,
@@ -293,6 +323,45 @@ class AsyncKalshiResource:
293
323
  body=request,
294
324
  )
295
325
 
326
+ async def submit_orders(
327
+ self, request: SubmitKalshiOrdersRequest, *, idempotency_key: str | None = None
328
+ ) -> SubmitKalshiOrdersResponse:
329
+ """Submit up to 20 orders in one request under one idempotency key.
330
+
331
+ The key covers the whole batch, is attached for you and defaults to a fresh value
332
+ per call, so an unkeyed retry submits the batch a SECOND time. Pass your own and
333
+ resend the identical batch under it to make a retry safe. Each order gets its own
334
+ outcome, so one refused order does not fail the others: ``orders[i]`` answers
335
+ order ``i`` as one flat record, and ``error`` None means it was accepted, not
336
+ confirmed at the venue, and is not a fill. An ``error`` coded
337
+ ``ORDER_RECONCILIATION_REQUIRED`` means the outcome is unknown and the order may
338
+ be live, so read back the order at the entry's ``order_id``. ``INTERNAL_ERROR``
339
+ means the outcome is unknown and no order id is known, so resend the identical
340
+ batch under the same key. Never resend either under a new key.
341
+ """
342
+ return await self._client._request(
343
+ "POST",
344
+ "/v1/kalshi/orders/batch",
345
+ SubmitKalshiOrdersResponse,
346
+ body=request,
347
+ idempotency_key=new_idempotency_key(idempotency_key),
348
+ )
349
+
350
+ async def list_order_queue_positions(
351
+ self, *, page_size: int | None = None, page_token: str | None = None
352
+ ) -> ListKalshiOrderQueuePositionsResponse:
353
+ """Queue positions for a page of your resting orders.
354
+
355
+ ``queue_position`` is the contracts ahead of the order in price-time priority, or
356
+ None when unavailable. A snapshot, not an execution guarantee.
357
+ """
358
+ return await self._client._request(
359
+ "GET",
360
+ "/v1/kalshi/orders/queue-positions",
361
+ ListKalshiOrderQueuePositionsResponse,
362
+ params={"pageSize": page_size, "pageToken": page_token},
363
+ )
364
+
296
365
  async def get_order_fills(self, order_id: str) -> GetKalshiOrderFillsResponse:
297
366
  """List the fills recorded against an order."""
298
367
  return await self._client._request(
@@ -344,6 +413,33 @@ class AsyncProphetXResource:
344
413
  idempotency_key=new_idempotency_key(idempotency_key),
345
414
  )
346
415
 
416
+ async def submit_orders(
417
+ self, request: SubmitProphetXOrdersRequest, *, idempotency_key: str | None = None
418
+ ) -> SubmitProphetXOrdersResponse:
419
+ """Submit up to 20 orders in one request.
420
+
421
+ The whole batch carries one idempotency key. Resending the same orders
422
+ under the same key returns each order's recorded outcome and places nothing
423
+ twice; the same key with different orders is refused with a 422. The key
424
+ defaults to a fresh value per call, so pass your own to make a retry safe.
425
+
426
+ Once a request is accepted, each order gets its own outcome, so an order the
427
+ venue refuses does not discard its siblings; a request that fails validation
428
+ is refused whole, with a 400 and no order placed. Accepted orders come back in
429
+ ``succeeded_orders``, accepted but not confirmed at the venue; refused or
430
+ unconfirmed ones come back in ``failed_orders``. A failed entry with code
431
+ ORDER_RECONCILIATION_REQUIRED may still have been placed, so read its
432
+ ``order_id`` before re-sending. Every order must be GTC: a ``timeInForce`` of
433
+ FOK or GTD is supported on ``submit_order`` only.
434
+ """
435
+ return await self._client._request(
436
+ "POST",
437
+ "/v1/prophetx/orders/batch",
438
+ SubmitProphetXOrdersResponse,
439
+ body=request,
440
+ idempotency_key=new_idempotency_key(idempotency_key),
441
+ )
442
+
347
443
  async def list_orders(
348
444
  self,
349
445
  *,
@@ -607,7 +703,12 @@ class AsyncParlayX:
607
703
  PARLAYX_KEY_ID and PARLAYX_PRIVATE_KEY_HEX when you do not.
608
704
  """
609
705
  self._credentials = resolve_credentials(key_id, private_key_hex)
610
- self._http = httpx.AsyncClient(base_url=base_url, timeout=timeout, transport=transport)
706
+ self._http = httpx.AsyncClient(
707
+ base_url=base_url,
708
+ timeout=timeout,
709
+ transport=transport,
710
+ limits=KEEPALIVE_LIMITS,
711
+ )
611
712
  self.polymarket = AsyncPolymarketResource(self)
612
713
  self.limitless = AsyncLimitlessResource(self)
613
714
  self.kalshi = AsyncKalshiResource(self)