parlayx 0.0.0__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.
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ dist/
parlayx-0.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ParlayX, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
parlayx-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.5
2
+ Name: parlayx
3
+ Version: 0.0.0
4
+ Summary: Official Python client for the ParlayX public API.
5
+ Project-URL: Homepage, https://docs.parlayx.com
6
+ Project-URL: Documentation, https://docs.parlayx.com
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: cryptography<51,>=45
11
+ Requires-Dist: httpx<1,>=0.28
12
+ Requires-Dist: pydantic<3,>=2.9
13
+ Provides-Extra: stream
14
+ Requires-Dist: websockets<18,>=15; extra == 'stream'
15
+ Description-Content-Type: text/markdown
16
+
17
+ # parlayx
18
+
19
+ Official Python client for the [ParlayX](https://parlayx.com) public API, a single trading interface over aggregated prediction markets.
20
+
21
+ ## Documentation
22
+
23
+ Full guides, authentication, and API reference live at **[docs.parlayx.com](https://docs.parlayx.com)**.
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ pip install parlayx
29
+ ```
30
+
31
+ Requires Python 3.11 or newer. Requests are signed with your Ed25519 key, so the client runs server-side: the private key stays in your own process and only the key id, a timestamp, and the signature go on the wire.
32
+
33
+ ## Usage
34
+
35
+ ```python
36
+ from parlayx import ParlayX
37
+
38
+ with ParlayX.from_env() as client:
39
+ who = client.whoami()
40
+ competitions = client.list_competitions(sport="spt_baseball")
41
+ balance = client.kalshi.get_balance()
42
+ ```
43
+
44
+ `from_env()` reads `PARLAYX_KEY_ID` and `PARLAYX_PRIVATE_KEY_HEX`; pass them to `ParlayX(...)` directly if you load them another way.
45
+
46
+ Orders, positions and balances are venue-scoped, because the venues address markets differently:
47
+
48
+ ```python
49
+ from parlayx import KalshiOrderRequest
50
+
51
+ order = client.kalshi.submit_order(
52
+ KalshiOrderRequest(
53
+ ticker="KXMLBGAME-26SEP01-NYY",
54
+ side="YES",
55
+ action="BUY",
56
+ count=10,
57
+ price_cents=45,
58
+ )
59
+ )
60
+ print(order.order_id, order.status)
61
+ ```
62
+
63
+ Fields accept either spelling: `price_cents` or `priceCents`. The wire always carries the API's own camelCase.
64
+
65
+ Each of `client.polymarket` and `client.kalshi` carries the same seven methods: `submit_order`, `list_orders`, `get_order`, `cancel_order`, `get_order_fills`, `list_positions` and `get_balance`.
66
+
67
+ ### Idempotency
68
+
69
+ `submit_order` attaches a fresh idempotency key per call, so retrying a call that failed submits a **second order**. Pass your own key to make a specific retry safe:
70
+
71
+ ```python
72
+ client.kalshi.submit_order(order, idempotency_key="my-retry-key")
73
+ ```
74
+
75
+ ### Errors
76
+
77
+ Every non-2xx response raises `RequestError`, carrying a stable `code`, the HTTP `status`, a `message` and the parsed `body`:
78
+
79
+ ```python
80
+ from parlayx import ApiErrorCode, RequestError
81
+
82
+ try:
83
+ client.polymarket.get_balance()
84
+ except RequestError as error:
85
+ if error.code == ApiErrorCode.insufficient_balance:
86
+ ...
87
+ ```
88
+
89
+ ### Pagination
90
+
91
+ Order listings are cursored. The paginators follow `nextPageToken` to exhaustion:
92
+
93
+ ```python
94
+ from parlayx import paginate_kalshi_orders
95
+
96
+ for order in paginate_kalshi_orders(client, status="OPEN"):
97
+ print(order.order_id, order.status)
98
+ ```
99
+
100
+ Positions and fills are not cursored and return their full result in one call.
101
+
102
+ ### Async
103
+
104
+ `AsyncParlayX` is the same surface with `await`, and `apaginate_kalshi_orders` and `apaginate_polymarket_orders` are the async paginators:
105
+
106
+ ```python
107
+ from parlayx import AsyncParlayX
108
+
109
+ async with AsyncParlayX.from_env() as client:
110
+ who = await client.whoami()
111
+ ```
112
+
113
+ ### Market data stream
114
+
115
+ The stream is a separate import and needs the extra:
116
+
117
+ ```sh
118
+ pip install "parlayx[stream]"
119
+ ```
120
+
121
+ It authenticates with the same signing key, re-signing the handshake on every connect, and replays your subscriptions across reconnects so you subscribe once:
122
+
123
+ ```python
124
+ from parlayx.stream import StreamClient
125
+
126
+ async with StreamClient.from_env() as stream:
127
+ await stream.subscribe([{"venue": "polymarket", "tokenId": "97840505..."}])
128
+ async for frame in stream:
129
+ if frame.type == "snapshot":
130
+ print(frame.seq, frame.bids[:3], frame.asks[:3])
131
+ elif frame.type == "delta":
132
+ for change in frame.changes:
133
+ ... # size 0 removes the level, anything else sets it
134
+ ```
135
+
136
+ `channels` defaults to `["book"]`; pass `["book", "trade"]` for the trade tape as well.
137
+
138
+ Connection lifecycle is reported through callbacks rather than frames, because it describes the connection rather than the market:
139
+
140
+ ```python
141
+ StreamClient.from_env(
142
+ on_reconnecting=lambda event: log.info("reconnecting, attempt %s", event.attempt),
143
+ on_terminated=lambda event: log.warning("stream stopped: %s", event.reason),
144
+ on_unparseable=lambda raw: log.warning("dropped an unrecognised frame: %s", raw),
145
+ )
146
+ ```
147
+
148
+ A terminated stream does not reconnect; build a new client to resume.
149
+
150
+ ### One inconsistency worth knowing
151
+
152
+ 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.
153
+
154
+ ## License
155
+
156
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,140 @@
1
+ # parlayx
2
+
3
+ Official Python client for the [ParlayX](https://parlayx.com) public API, a single trading interface over aggregated prediction markets.
4
+
5
+ ## Documentation
6
+
7
+ Full guides, authentication, and API reference live at **[docs.parlayx.com](https://docs.parlayx.com)**.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pip install parlayx
13
+ ```
14
+
15
+ Requires Python 3.11 or newer. Requests are signed with your Ed25519 key, so the client runs server-side: the private key stays in your own process and only the key id, a timestamp, and the signature go on the wire.
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ from parlayx import ParlayX
21
+
22
+ with ParlayX.from_env() as client:
23
+ who = client.whoami()
24
+ competitions = client.list_competitions(sport="spt_baseball")
25
+ balance = client.kalshi.get_balance()
26
+ ```
27
+
28
+ `from_env()` reads `PARLAYX_KEY_ID` and `PARLAYX_PRIVATE_KEY_HEX`; pass them to `ParlayX(...)` directly if you load them another way.
29
+
30
+ Orders, positions and balances are venue-scoped, because the venues address markets differently:
31
+
32
+ ```python
33
+ from parlayx import KalshiOrderRequest
34
+
35
+ order = client.kalshi.submit_order(
36
+ KalshiOrderRequest(
37
+ ticker="KXMLBGAME-26SEP01-NYY",
38
+ side="YES",
39
+ action="BUY",
40
+ count=10,
41
+ price_cents=45,
42
+ )
43
+ )
44
+ print(order.order_id, order.status)
45
+ ```
46
+
47
+ Fields accept either spelling: `price_cents` or `priceCents`. The wire always carries the API's own camelCase.
48
+
49
+ Each of `client.polymarket` and `client.kalshi` carries the same seven methods: `submit_order`, `list_orders`, `get_order`, `cancel_order`, `get_order_fills`, `list_positions` and `get_balance`.
50
+
51
+ ### Idempotency
52
+
53
+ `submit_order` attaches a fresh idempotency key per call, so retrying a call that failed submits a **second order**. Pass your own key to make a specific retry safe:
54
+
55
+ ```python
56
+ client.kalshi.submit_order(order, idempotency_key="my-retry-key")
57
+ ```
58
+
59
+ ### Errors
60
+
61
+ Every non-2xx response raises `RequestError`, carrying a stable `code`, the HTTP `status`, a `message` and the parsed `body`:
62
+
63
+ ```python
64
+ from parlayx import ApiErrorCode, RequestError
65
+
66
+ try:
67
+ client.polymarket.get_balance()
68
+ except RequestError as error:
69
+ if error.code == ApiErrorCode.insufficient_balance:
70
+ ...
71
+ ```
72
+
73
+ ### Pagination
74
+
75
+ Order listings are cursored. The paginators follow `nextPageToken` to exhaustion:
76
+
77
+ ```python
78
+ from parlayx import paginate_kalshi_orders
79
+
80
+ for order in paginate_kalshi_orders(client, status="OPEN"):
81
+ print(order.order_id, order.status)
82
+ ```
83
+
84
+ Positions and fills are not cursored and return their full result in one call.
85
+
86
+ ### Async
87
+
88
+ `AsyncParlayX` is the same surface with `await`, and `apaginate_kalshi_orders` and `apaginate_polymarket_orders` are the async paginators:
89
+
90
+ ```python
91
+ from parlayx import AsyncParlayX
92
+
93
+ async with AsyncParlayX.from_env() as client:
94
+ who = await client.whoami()
95
+ ```
96
+
97
+ ### Market data stream
98
+
99
+ The stream is a separate import and needs the extra:
100
+
101
+ ```sh
102
+ pip install "parlayx[stream]"
103
+ ```
104
+
105
+ It authenticates with the same signing key, re-signing the handshake on every connect, and replays your subscriptions across reconnects so you subscribe once:
106
+
107
+ ```python
108
+ from parlayx.stream import StreamClient
109
+
110
+ async with StreamClient.from_env() as stream:
111
+ await stream.subscribe([{"venue": "polymarket", "tokenId": "97840505..."}])
112
+ async for frame in stream:
113
+ if frame.type == "snapshot":
114
+ print(frame.seq, frame.bids[:3], frame.asks[:3])
115
+ elif frame.type == "delta":
116
+ for change in frame.changes:
117
+ ... # size 0 removes the level, anything else sets it
118
+ ```
119
+
120
+ `channels` defaults to `["book"]`; pass `["book", "trade"]` for the trade tape as well.
121
+
122
+ Connection lifecycle is reported through callbacks rather than frames, because it describes the connection rather than the market:
123
+
124
+ ```python
125
+ StreamClient.from_env(
126
+ on_reconnecting=lambda event: log.info("reconnecting, attempt %s", event.attempt),
127
+ on_terminated=lambda event: log.warning("stream stopped: %s", event.reason),
128
+ on_unparseable=lambda raw: log.warning("dropped an unrecognised frame: %s", raw),
129
+ )
130
+ ```
131
+
132
+ A terminated stream does not reconnect; build a new client to resume.
133
+
134
+ ### One inconsistency worth knowing
135
+
136
+ 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.
137
+
138
+ ## License
139
+
140
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,78 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "parlayx"
7
+ version = "0.0.0"
8
+ description = "Official Python client for the ParlayX public API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = ["cryptography>=45,<51", "httpx>=0.28,<1", "pydantic>=2.9,<3"]
14
+
15
+ # The stream client is the only part that needs a WebSocket library, so a REST-only
16
+ # consumer does not pull it in.
17
+ [project.optional-dependencies]
18
+ stream = ["websockets>=15,<18"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://docs.parlayx.com"
22
+ Documentation = "https://docs.parlayx.com"
23
+
24
+ [dependency-groups]
25
+ # datamodel-code-generator is pinned exactly, not ranged. Its output is committed, so a
26
+ # patch release that changed formatting would show up as an unexplained diff on a branch
27
+ # that touched nothing.
28
+ dev = [
29
+ "datamodel-code-generator==0.76.1",
30
+ "parlayx[stream]",
31
+ "mypy>=1.14,<2",
32
+ "pytest>=8,<10",
33
+ "pytest-timeout>=2.3,<3",
34
+ "pytest-asyncio>=0.24,<2",
35
+ "ruff>=0.14,<1",
36
+ ]
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/parlayx"]
40
+
41
+ # Kept out of the source distribution. The test suite reads fixtures from outside
42
+ # this directory and so cannot run standalone; shipping it would hand a reader a
43
+ # suite that only fails. The rest is build wiring that means nothing on its own.
44
+ [tool.hatch.build.targets.sdist]
45
+ exclude = [".gitignore", ".python-version", "project.json", "uv.lock", "tests"]
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py311"
50
+ # The generated models are formatted by the generator (black + isort, pinned in the
51
+ # py:generate flags) and carry the spec's own field descriptions verbatim, some of which
52
+ # exceed the line length. Linting or reformatting them here would fight the drift gate:
53
+ # any fix is reverted by the next regeneration.
54
+ extend-exclude = ["src/parlayx/_generated"]
55
+
56
+ [tool.ruff.lint]
57
+ select = ["E", "F", "I", "N", "UP", "B", "SIM", "RUF"]
58
+
59
+ [tool.mypy]
60
+ python_version = "3.11"
61
+ strict = true
62
+ files = ["src", "tests"]
63
+
64
+ # The generator writes a dict literal as the default for a model-typed field. Pydantic
65
+ # coerces it to the model at runtime because the field carries validate_default=True, so
66
+ # the value is never a dict, but mypy reads the literal as a bad assignment. Everything
67
+ # else in the generated models stays checked.
68
+ [[tool.mypy.overrides]]
69
+ module = "parlayx._generated.*"
70
+ disable_error_code = ["assignment"]
71
+
72
+ [tool.pytest.ini_options]
73
+ testpaths = ["tests"]
74
+ # A broken stream client hangs rather than failing: the consumer waits on a queue that
75
+ # will never receive another frame. Without a deadline that reads as a stuck CI job
76
+ # instead of a red test.
77
+ timeout = 30
78
+ asyncio_mode = "auto"
@@ -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
+ ]