parlayx 0.0.0__py3-none-any.whl
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.
- parlayx/__init__.py +122 -0
- parlayx/_async_client.py +323 -0
- parlayx/_generated/__init__.py +0 -0
- parlayx/_generated/models.py +1304 -0
- parlayx/_generated/stream.py +703 -0
- parlayx/_signing.py +188 -0
- parlayx/_transport.py +113 -0
- parlayx/client.py +348 -0
- parlayx/errors.py +58 -0
- parlayx/pagination.py +92 -0
- parlayx/py.typed +0 -0
- parlayx/stream/__init__.py +67 -0
- parlayx/stream/_client.py +500 -0
- parlayx-0.0.0.dist-info/METADATA +156 -0
- parlayx-0.0.0.dist-info/RECORD +17 -0
- parlayx-0.0.0.dist-info/WHEEL +4 -0
- parlayx-0.0.0.dist-info/licenses/LICENSE +21 -0
parlayx/__init__.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Official Python client for the ParlayX public API.
|
|
2
|
+
|
|
3
|
+
from parlayx import ParlayX
|
|
4
|
+
|
|
5
|
+
with ParlayX.from_env() as client:
|
|
6
|
+
who = client.whoami()
|
|
7
|
+
balance = client.kalshi.get_balance()
|
|
8
|
+
|
|
9
|
+
`AsyncParlayX` is the same surface with `await`. Every method returns its typed payload
|
|
10
|
+
and raises `RequestError` on any non-2xx response.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from ._async_client import AsyncKalshiResource, AsyncParlayX, AsyncPolymarketResource
|
|
14
|
+
from ._generated import models
|
|
15
|
+
from ._generated.models import (
|
|
16
|
+
ApiErrorCode,
|
|
17
|
+
Competition,
|
|
18
|
+
Event,
|
|
19
|
+
EventMarket,
|
|
20
|
+
GetKalshiOrderFillsResponse,
|
|
21
|
+
GetPolymarketOrderFillsResponse,
|
|
22
|
+
KalshiAction,
|
|
23
|
+
KalshiBalance,
|
|
24
|
+
KalshiFillView,
|
|
25
|
+
KalshiListing,
|
|
26
|
+
KalshiOrderRequest,
|
|
27
|
+
KalshiOrderType,
|
|
28
|
+
KalshiOrderView,
|
|
29
|
+
KalshiPosition,
|
|
30
|
+
KalshiSide,
|
|
31
|
+
ListCompetitionsResponse,
|
|
32
|
+
ListEventMarketsResponse,
|
|
33
|
+
ListEventsResponse,
|
|
34
|
+
ListKalshiOrdersResponse,
|
|
35
|
+
ListKalshiPositionsResponse,
|
|
36
|
+
ListPolymarketOrdersResponse,
|
|
37
|
+
ListPolymarketPositionsResponse,
|
|
38
|
+
ListSportsResponse,
|
|
39
|
+
LookupMarketResponse,
|
|
40
|
+
Outcome,
|
|
41
|
+
PolymarketBalance,
|
|
42
|
+
PolymarketFillView,
|
|
43
|
+
PolymarketLimitOrder,
|
|
44
|
+
PolymarketListing,
|
|
45
|
+
PolymarketMarketOrder,
|
|
46
|
+
PolymarketOrderView,
|
|
47
|
+
PolymarketPosition,
|
|
48
|
+
PolymarketSide,
|
|
49
|
+
ProphetXListing,
|
|
50
|
+
ServerTimeResponse,
|
|
51
|
+
Sport,
|
|
52
|
+
SubmitKalshiOrderResponse,
|
|
53
|
+
SubmitPolymarketOrderResponse,
|
|
54
|
+
TradingOrderStatus,
|
|
55
|
+
TradingOrderType,
|
|
56
|
+
WhoamiResponse,
|
|
57
|
+
)
|
|
58
|
+
from .client import KalshiResource, ParlayX, PolymarketOrder, PolymarketResource
|
|
59
|
+
from .errors import RequestError
|
|
60
|
+
from .pagination import (
|
|
61
|
+
apaginate_kalshi_orders,
|
|
62
|
+
apaginate_polymarket_orders,
|
|
63
|
+
paginate_kalshi_orders,
|
|
64
|
+
paginate_polymarket_orders,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"ApiErrorCode",
|
|
69
|
+
"AsyncKalshiResource",
|
|
70
|
+
"AsyncParlayX",
|
|
71
|
+
"AsyncPolymarketResource",
|
|
72
|
+
"Competition",
|
|
73
|
+
"Event",
|
|
74
|
+
"EventMarket",
|
|
75
|
+
"GetKalshiOrderFillsResponse",
|
|
76
|
+
"GetPolymarketOrderFillsResponse",
|
|
77
|
+
"KalshiAction",
|
|
78
|
+
"KalshiBalance",
|
|
79
|
+
"KalshiFillView",
|
|
80
|
+
"KalshiListing",
|
|
81
|
+
"KalshiOrderRequest",
|
|
82
|
+
"KalshiOrderType",
|
|
83
|
+
"KalshiOrderView",
|
|
84
|
+
"KalshiPosition",
|
|
85
|
+
"KalshiResource",
|
|
86
|
+
"KalshiSide",
|
|
87
|
+
"ListCompetitionsResponse",
|
|
88
|
+
"ListEventMarketsResponse",
|
|
89
|
+
"ListEventsResponse",
|
|
90
|
+
"ListKalshiOrdersResponse",
|
|
91
|
+
"ListKalshiPositionsResponse",
|
|
92
|
+
"ListPolymarketOrdersResponse",
|
|
93
|
+
"ListPolymarketPositionsResponse",
|
|
94
|
+
"ListSportsResponse",
|
|
95
|
+
"LookupMarketResponse",
|
|
96
|
+
"Outcome",
|
|
97
|
+
"ParlayX",
|
|
98
|
+
"PolymarketBalance",
|
|
99
|
+
"PolymarketFillView",
|
|
100
|
+
"PolymarketLimitOrder",
|
|
101
|
+
"PolymarketListing",
|
|
102
|
+
"PolymarketMarketOrder",
|
|
103
|
+
"PolymarketOrder",
|
|
104
|
+
"PolymarketOrderView",
|
|
105
|
+
"PolymarketPosition",
|
|
106
|
+
"PolymarketResource",
|
|
107
|
+
"PolymarketSide",
|
|
108
|
+
"ProphetXListing",
|
|
109
|
+
"RequestError",
|
|
110
|
+
"ServerTimeResponse",
|
|
111
|
+
"Sport",
|
|
112
|
+
"SubmitKalshiOrderResponse",
|
|
113
|
+
"SubmitPolymarketOrderResponse",
|
|
114
|
+
"TradingOrderStatus",
|
|
115
|
+
"TradingOrderType",
|
|
116
|
+
"WhoamiResponse",
|
|
117
|
+
"apaginate_kalshi_orders",
|
|
118
|
+
"apaginate_polymarket_orders",
|
|
119
|
+
"models",
|
|
120
|
+
"paginate_kalshi_orders",
|
|
121
|
+
"paginate_polymarket_orders",
|
|
122
|
+
]
|
parlayx/_async_client.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Asynchronous ParlayX API client.
|
|
2
|
+
|
|
3
|
+
Mirrors `parlayx.client` method for method; the two differ by `await` and nothing else.
|
|
4
|
+
Both sign through the same module and share one set of models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from types import TracebackType
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from ._generated.models import (
|
|
16
|
+
GetKalshiOrderFillsResponse,
|
|
17
|
+
GetPolymarketOrderFillsResponse,
|
|
18
|
+
KalshiBalance,
|
|
19
|
+
KalshiOrderRequest,
|
|
20
|
+
KalshiOrderView,
|
|
21
|
+
ListCompetitionsResponse,
|
|
22
|
+
ListEventMarketsResponse,
|
|
23
|
+
ListEventsResponse,
|
|
24
|
+
ListKalshiOrdersResponse,
|
|
25
|
+
ListKalshiPositionsResponse,
|
|
26
|
+
ListPolymarketOrdersResponse,
|
|
27
|
+
ListPolymarketPositionsResponse,
|
|
28
|
+
ListSportsResponse,
|
|
29
|
+
LookupMarketResponse,
|
|
30
|
+
PolymarketBalance,
|
|
31
|
+
PolymarketOrderView,
|
|
32
|
+
ServerTimeResponse,
|
|
33
|
+
SubmitKalshiOrderResponse,
|
|
34
|
+
SubmitPolymarketOrderResponse,
|
|
35
|
+
WhoamiResponse,
|
|
36
|
+
)
|
|
37
|
+
from ._transport import (
|
|
38
|
+
DEFAULT_BASE_URL,
|
|
39
|
+
Credentials,
|
|
40
|
+
ModelT,
|
|
41
|
+
build_signed_request,
|
|
42
|
+
new_idempotency_key,
|
|
43
|
+
parse,
|
|
44
|
+
path_segment,
|
|
45
|
+
)
|
|
46
|
+
from .client import PolymarketOrder, _credentials_from_env
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AsyncPolymarketResource:
|
|
50
|
+
"""Polymarket orders, positions and balance."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, client: AsyncParlayX) -> None:
|
|
53
|
+
self._client = client
|
|
54
|
+
|
|
55
|
+
async def submit_order(
|
|
56
|
+
self, order: PolymarketOrder, *, idempotency_key: str | None = None
|
|
57
|
+
) -> SubmitPolymarketOrderResponse:
|
|
58
|
+
"""Submit an order.
|
|
59
|
+
|
|
60
|
+
An idempotency key is attached for you and defaults to a fresh value per call,
|
|
61
|
+
so an unkeyed retry submits a SECOND order. Pass your own to make a specific
|
|
62
|
+
retry safe.
|
|
63
|
+
"""
|
|
64
|
+
return await self._client._request(
|
|
65
|
+
"POST",
|
|
66
|
+
"/v1/polymarket/orders",
|
|
67
|
+
SubmitPolymarketOrderResponse,
|
|
68
|
+
body=order,
|
|
69
|
+
idempotency_key=new_idempotency_key(idempotency_key),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def list_orders(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
token_id: str | None = None,
|
|
76
|
+
status: str | None = None,
|
|
77
|
+
page_size: int | None = None,
|
|
78
|
+
page_token: str | None = None,
|
|
79
|
+
) -> ListPolymarketOrdersResponse:
|
|
80
|
+
"""List orders, most recent first."""
|
|
81
|
+
return await self._client._request(
|
|
82
|
+
"GET",
|
|
83
|
+
"/v1/polymarket/orders",
|
|
84
|
+
ListPolymarketOrdersResponse,
|
|
85
|
+
params={
|
|
86
|
+
"tokenId": token_id,
|
|
87
|
+
"status": status,
|
|
88
|
+
"pageSize": page_size,
|
|
89
|
+
"pageToken": page_token,
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async def get_order(self, order_id: str) -> PolymarketOrderView:
|
|
94
|
+
"""Fetch a single order by id."""
|
|
95
|
+
return await self._client._request(
|
|
96
|
+
"GET", f"/v1/polymarket/orders/{path_segment(order_id)}", PolymarketOrderView
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def cancel_order(self, order_id: str) -> PolymarketOrderView:
|
|
100
|
+
"""Cancel a single open order by id."""
|
|
101
|
+
return await self._client._request(
|
|
102
|
+
"DELETE", f"/v1/polymarket/orders/{path_segment(order_id)}", PolymarketOrderView
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
async def get_order_fills(self, order_id: str) -> GetPolymarketOrderFillsResponse:
|
|
106
|
+
"""List the fills recorded against an order."""
|
|
107
|
+
return await self._client._request(
|
|
108
|
+
"GET",
|
|
109
|
+
f"/v1/polymarket/orders/{path_segment(order_id)}/fills",
|
|
110
|
+
GetPolymarketOrderFillsResponse,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
async def list_positions(
|
|
114
|
+
self, *, token_id: str | None = None
|
|
115
|
+
) -> ListPolymarketPositionsResponse:
|
|
116
|
+
"""List open positions."""
|
|
117
|
+
return await self._client._request(
|
|
118
|
+
"GET",
|
|
119
|
+
"/v1/polymarket/positions",
|
|
120
|
+
ListPolymarketPositionsResponse,
|
|
121
|
+
params={"tokenId": token_id},
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
async def get_balance(self) -> PolymarketBalance:
|
|
125
|
+
"""Read the venue balance."""
|
|
126
|
+
return await self._client._request("GET", "/v1/polymarket/balance", PolymarketBalance)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AsyncKalshiResource:
|
|
130
|
+
"""Kalshi orders, positions and balance."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, client: AsyncParlayX) -> None:
|
|
133
|
+
self._client = client
|
|
134
|
+
|
|
135
|
+
async def submit_order(
|
|
136
|
+
self, order: KalshiOrderRequest, *, idempotency_key: str | None = None
|
|
137
|
+
) -> SubmitKalshiOrderResponse:
|
|
138
|
+
"""Submit an order.
|
|
139
|
+
|
|
140
|
+
An idempotency key is attached for you and defaults to a fresh value per call,
|
|
141
|
+
so an unkeyed retry submits a SECOND order. Pass your own to make a specific
|
|
142
|
+
retry safe.
|
|
143
|
+
"""
|
|
144
|
+
return await self._client._request(
|
|
145
|
+
"POST",
|
|
146
|
+
"/v1/kalshi/orders",
|
|
147
|
+
SubmitKalshiOrderResponse,
|
|
148
|
+
body=order,
|
|
149
|
+
idempotency_key=new_idempotency_key(idempotency_key),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
async def list_orders(
|
|
153
|
+
self,
|
|
154
|
+
*,
|
|
155
|
+
ticker: str | None = None,
|
|
156
|
+
status: str | None = None,
|
|
157
|
+
page_size: int | None = None,
|
|
158
|
+
page_token: str | None = None,
|
|
159
|
+
) -> ListKalshiOrdersResponse:
|
|
160
|
+
"""List orders, most recent first."""
|
|
161
|
+
return await self._client._request(
|
|
162
|
+
"GET",
|
|
163
|
+
"/v1/kalshi/orders",
|
|
164
|
+
ListKalshiOrdersResponse,
|
|
165
|
+
params={
|
|
166
|
+
"ticker": ticker,
|
|
167
|
+
"status": status,
|
|
168
|
+
"pageSize": page_size,
|
|
169
|
+
"pageToken": page_token,
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
async def get_order(self, order_id: str) -> KalshiOrderView:
|
|
174
|
+
"""Fetch a single order by id."""
|
|
175
|
+
return await self._client._request(
|
|
176
|
+
"GET", f"/v1/kalshi/orders/{path_segment(order_id)}", KalshiOrderView
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def cancel_order(self, order_id: str) -> KalshiOrderView:
|
|
180
|
+
"""Cancel a single open order by id."""
|
|
181
|
+
return await self._client._request(
|
|
182
|
+
"DELETE", f"/v1/kalshi/orders/{path_segment(order_id)}", KalshiOrderView
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
async def get_order_fills(self, order_id: str) -> GetKalshiOrderFillsResponse:
|
|
186
|
+
"""List the fills recorded against an order."""
|
|
187
|
+
return await self._client._request(
|
|
188
|
+
"GET", f"/v1/kalshi/orders/{path_segment(order_id)}/fills", GetKalshiOrderFillsResponse
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
async def list_positions(self, *, ticker: str | None = None) -> ListKalshiPositionsResponse:
|
|
192
|
+
"""List open positions."""
|
|
193
|
+
return await self._client._request(
|
|
194
|
+
"GET",
|
|
195
|
+
"/v1/kalshi/positions",
|
|
196
|
+
ListKalshiPositionsResponse,
|
|
197
|
+
params={"ticker": ticker},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def get_balance(self) -> KalshiBalance:
|
|
201
|
+
"""Read the venue balance."""
|
|
202
|
+
return await self._client._request("GET", "/v1/kalshi/balance", KalshiBalance)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class AsyncParlayX:
|
|
206
|
+
"""Asynchronous ParlayX API client.
|
|
207
|
+
|
|
208
|
+
Every method returns its typed payload and raises `RequestError` on any non-2xx
|
|
209
|
+
response. Create one and reuse it; each instance holds a connection pool.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
def __init__(
|
|
213
|
+
self,
|
|
214
|
+
*,
|
|
215
|
+
key_id: str,
|
|
216
|
+
private_key_hex: str,
|
|
217
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
218
|
+
timeout: float = 30.0,
|
|
219
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
220
|
+
) -> None:
|
|
221
|
+
self._credentials = Credentials(key_id=key_id, private_key_hex=private_key_hex)
|
|
222
|
+
self._http = httpx.AsyncClient(base_url=base_url, timeout=timeout, transport=transport)
|
|
223
|
+
self.polymarket = AsyncPolymarketResource(self)
|
|
224
|
+
self.kalshi = AsyncKalshiResource(self)
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def from_env(cls, **kwargs: Any) -> AsyncParlayX:
|
|
228
|
+
"""Build a client from PARLAYX_KEY_ID and PARLAYX_PRIVATE_KEY_HEX."""
|
|
229
|
+
credentials = _credentials_from_env()
|
|
230
|
+
return cls(key_id=credentials.key_id, private_key_hex=credentials.private_key_hex, **kwargs)
|
|
231
|
+
|
|
232
|
+
async def _request(
|
|
233
|
+
self,
|
|
234
|
+
method: str,
|
|
235
|
+
path: str,
|
|
236
|
+
model: type[ModelT],
|
|
237
|
+
*,
|
|
238
|
+
params: dict[str, Any] | None = None,
|
|
239
|
+
body: BaseModel | None = None,
|
|
240
|
+
idempotency_key: str | None = None,
|
|
241
|
+
) -> ModelT:
|
|
242
|
+
request = build_signed_request(
|
|
243
|
+
self._http,
|
|
244
|
+
self._credentials,
|
|
245
|
+
method,
|
|
246
|
+
path,
|
|
247
|
+
params=params,
|
|
248
|
+
body=body,
|
|
249
|
+
idempotency_key=idempotency_key,
|
|
250
|
+
)
|
|
251
|
+
return parse(await self._http.send(request), model)
|
|
252
|
+
|
|
253
|
+
async def whoami(self) -> WhoamiResponse:
|
|
254
|
+
"""Identify the calling key: its actor, pod, scope and capabilities."""
|
|
255
|
+
return await self._request("GET", "/v1/whoami", WhoamiResponse)
|
|
256
|
+
|
|
257
|
+
async def get_time(self) -> ServerTimeResponse:
|
|
258
|
+
"""Read the server clock.
|
|
259
|
+
|
|
260
|
+
Verified without a timestamp, so a host whose clock has drifted past the
|
|
261
|
+
signature window can still call it and measure the offset.
|
|
262
|
+
"""
|
|
263
|
+
return await self._request("GET", "/v1/time", ServerTimeResponse)
|
|
264
|
+
|
|
265
|
+
async def list_sports(self) -> ListSportsResponse:
|
|
266
|
+
"""List the sports carried by the discovery surface."""
|
|
267
|
+
return await self._request("GET", "/v1/sports", ListSportsResponse)
|
|
268
|
+
|
|
269
|
+
async def list_competitions(self, *, sport: str | None = None) -> ListCompetitionsResponse:
|
|
270
|
+
"""List competitions, optionally filtered to one sport."""
|
|
271
|
+
return await self._request(
|
|
272
|
+
"GET", "/v1/sports/competitions", ListCompetitionsResponse, params={"sport": sport}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
async def list_events(self, *, competition_id: str) -> ListEventsResponse:
|
|
276
|
+
"""List the events in a competition."""
|
|
277
|
+
return await self._request(
|
|
278
|
+
"GET",
|
|
279
|
+
"/v1/sports/events",
|
|
280
|
+
ListEventsResponse,
|
|
281
|
+
params={"competitionId": competition_id},
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
async def list_event_markets(self, event_id: str) -> ListEventMarketsResponse:
|
|
285
|
+
"""List the markets on an event."""
|
|
286
|
+
return await self._request(
|
|
287
|
+
"GET", f"/v1/sports/events/{path_segment(event_id)}/markets", ListEventMarketsResponse
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
async def lookup_market(
|
|
291
|
+
self,
|
|
292
|
+
*,
|
|
293
|
+
token_id: str | None = None,
|
|
294
|
+
ticker: str | None = None,
|
|
295
|
+
condition_id: str | None = None,
|
|
296
|
+
slug: str | None = None,
|
|
297
|
+
) -> LookupMarketResponse:
|
|
298
|
+
"""Look up a single market by one venue-native identifier."""
|
|
299
|
+
return await self._request(
|
|
300
|
+
"GET",
|
|
301
|
+
"/v1/markets",
|
|
302
|
+
LookupMarketResponse,
|
|
303
|
+
params={
|
|
304
|
+
"tokenId": token_id,
|
|
305
|
+
"ticker": ticker,
|
|
306
|
+
"conditionId": condition_id,
|
|
307
|
+
"slug": slug,
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def close(self) -> None:
|
|
312
|
+
await self._http.aclose()
|
|
313
|
+
|
|
314
|
+
async def __aenter__(self) -> AsyncParlayX:
|
|
315
|
+
return self
|
|
316
|
+
|
|
317
|
+
async def __aexit__(
|
|
318
|
+
self,
|
|
319
|
+
exc_type: type[BaseException] | None,
|
|
320
|
+
exc: BaseException | None,
|
|
321
|
+
traceback: TracebackType | None,
|
|
322
|
+
) -> None:
|
|
323
|
+
await self.close()
|
|
File without changes
|