polysim-sdk 0.2.0__tar.gz → 0.2.1__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,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: polysim-sdk
3
+ Version: 0.2.1
4
+ Summary: Deprecated alias — the PolySimulator Python SDK is now published as 'polysimulator'. Installs it for you.
5
+ Project-URL: Homepage, https://polysimulator.com
6
+ Project-URL: Documentation, https://docs.polysimulator.com
7
+ Project-URL: Repository, https://github.com/Bavariance/polysimulator
8
+ Author: Bavariance
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Keywords: api-sdk,paper-trading,polymarket,polysimulator,trading
13
+ Classifier: Development Status :: 7 - Inactive
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Office/Business :: Financial
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: polysimulator>=0.2.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # polysim-sdk → renamed to `polysimulator`
22
+
23
+ **This package has been renamed.** The PolySimulator Python SDK is now published as
24
+ **[`polysimulator`](https://pypi.org/project/polysimulator/)**.
25
+
26
+ ```bash
27
+ pip install polysimulator
28
+ ```
29
+
30
+ `polysim-sdk` is now a thin alias that pulls in `polysimulator` automatically, so
31
+ existing installs keep working. **The import paths are unchanged:**
32
+
33
+ ```python
34
+ from polysim_sdk import PolySimClient # native client (sync + async)
35
+ from polysim_clob_client.client import ClobClient # py-clob-client drop-in
36
+ ```
37
+
38
+ Please switch your dependency from `polysim-sdk` to `polysimulator`.
39
+
40
+ - Docs: https://docs.polysimulator.com
41
+ - Source: https://github.com/Bavariance/polysimulator
@@ -0,0 +1,21 @@
1
+ # polysim-sdk → renamed to `polysimulator`
2
+
3
+ **This package has been renamed.** The PolySimulator Python SDK is now published as
4
+ **[`polysimulator`](https://pypi.org/project/polysimulator/)**.
5
+
6
+ ```bash
7
+ pip install polysimulator
8
+ ```
9
+
10
+ `polysim-sdk` is now a thin alias that pulls in `polysimulator` automatically, so
11
+ existing installs keep working. **The import paths are unchanged:**
12
+
13
+ ```python
14
+ from polysim_sdk import PolySimClient # native client (sync + async)
15
+ from polysim_clob_client.client import ClobClient # py-clob-client drop-in
16
+ ```
17
+
18
+ Please switch your dependency from `polysim-sdk` to `polysimulator`.
19
+
20
+ - Docs: https://docs.polysimulator.com
21
+ - Source: https://github.com/Bavariance/polysimulator
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "polysim-sdk"
7
+ version = "0.2.1"
8
+ description = "Deprecated alias — the PolySimulator Python SDK is now published as 'polysimulator'. Installs it for you."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE", "NOTICE"]
13
+ authors = [{ name = "Bavariance" }]
14
+ keywords = ["polymarket", "polysimulator", "trading", "paper-trading", "api-sdk"]
15
+ classifiers = [
16
+ "Development Status :: 7 - Inactive",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Office/Business :: Financial",
20
+ ]
21
+ dependencies = ["polysimulator>=0.2.0"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://polysimulator.com"
25
+ Documentation = "https://docs.polysimulator.com"
26
+ Repository = "https://github.com/Bavariance/polysimulator"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ bypass-selection = true
30
+
31
+ [tool.hatch.build.targets.sdist]
32
+ include = ["README.md", "LICENSE", "NOTICE", "pyproject.toml"]
@@ -1,18 +0,0 @@
1
- # Python build / packaging artifacts
2
- dist/
3
- build/
4
- *.egg-info/
5
- __pycache__/
6
- *.py[cod]
7
-
8
- # Virtualenv
9
- .venv/
10
- venv/
11
-
12
- # Tooling caches
13
- .mypy_cache/
14
- .pytest_cache/
15
- .ruff_cache/
16
-
17
- # Local throwaway smoke scripts (never committed — may contain wiring to live keys)
18
- _smoke_*.py
@@ -1,503 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: polysim-sdk
3
- Version: 0.2.0
4
- Summary: Python SDK for the PolySimulator paper-trading API, with py-clob-client drop-in parity
5
- Project-URL: Homepage, https://polysimulator.com
6
- Project-URL: Documentation, https://api.polysimulator.com/docs
7
- Project-URL: Repository, https://github.com/Bavariance/polysimulator
8
- Author: Bavariance
9
- License-Expression: Apache-2.0
10
- License-File: LICENSE
11
- License-File: NOTICE
12
- Keywords: api-sdk,paper-trading,polymarket,polysimulator,trading
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Topic :: Office/Business :: Financial
20
- Requires-Python: >=3.10
21
- Requires-Dist: httpx<1,>=0.27
22
- Requires-Dist: websockets<17,>=12
23
- Provides-Extra: dev
24
- Requires-Dist: build>=1.0; extra == 'dev'
25
- Requires-Dist: mypy>=1.10; extra == 'dev'
26
- Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
27
- Requires-Dist: pytest>=8; extra == 'dev'
28
- Requires-Dist: respx>=0.21; extra == 'dev'
29
- Requires-Dist: ruff>=0.6; extra == 'dev'
30
- Requires-Dist: twine>=5.0; extra == 'dev'
31
- Description-Content-Type: text/markdown
32
-
33
- # PolySimulator Python SDK
34
-
35
- The official Python client for the [PolySimulator](https://polysimulator.com)
36
- paper-trading API. **One package, two import surfaces:**
37
-
38
- | Import surface | Use it when |
39
- |---|---|
40
- | `polysim_sdk` | You're starting fresh. A clean, modern client — sync **and** async, WebSocket streaming, pagination iterators, typed exceptions. |
41
- | `polysim_clob_client` | You already have a bot written against Polymarket's [`py-clob-client`](https://github.com/Polymarket/py-clob-client). This is a **drop-in mirror** — port by changing the import path, the host, and the auth call. |
42
-
43
- PolySimulator is paper trading, so there is **no on-chain anything**: no private
44
- key, no `chain_id`, no `funder`, no `signature_type`, no EIP-712 signing, no USDC
45
- allowance/approval transactions, no web3/Polygon RPC. The SDK depends only on
46
- `httpx` and `websockets`. Authentication is a single `ps_live_*` API key sent as
47
- the `X-API-Key` header.
48
-
49
- ---
50
-
51
- ## Install
52
-
53
- Targets Python 3.10+.
54
-
55
- ```bash
56
- pip install polysim-sdk
57
- ```
58
-
59
- From a checkout of this directory (for development):
60
-
61
- ```bash
62
- python -m venv .venv && source .venv/bin/activate
63
- pip install -e ".[dev]" # editable install + pytest/ruff/mypy/build/twine
64
- ```
65
-
66
- Provide your key via the `POLYSIM_API_KEY` environment variable (recommended) or
67
- pass it to the constructor. To target staging instead of production:
68
-
69
- ```bash
70
- export POLYSIM_API_KEY="ps_live_…"
71
- export POLYSIM_BASE_URL="https://staging-api.polysimulator.com"
72
- ```
73
-
74
- ---
75
-
76
- ## Quickstart — native `polysim_sdk`
77
-
78
- ```python
79
- from polysim_sdk import PolySimClient
80
-
81
- with PolySimClient() as client: # POLYSIM_API_KEY from env
82
- me = client.me()
83
- print(me["balance"]) # your trading balance (a JSON string)
84
-
85
- market = client.list_markets(limit=1, hot_only=True)[0]
86
- cid = market["condition_id"]
87
-
88
- book = client.get_book(cid) # by condition id (+ outcome=/depth=)
89
- asks = book.get("asks") or [] # levels: [{"price","size"}, ...]
90
- best_ask = min((float(a["price"]) for a in asks), default=None)
91
- print("best ask:", best_ask) # lowest ask = best price to buy at
92
-
93
- # …or by outcome-token id, the Polymarket-native way:
94
- # token_book = client.get_book_by_token("<outcome-token-id>")
95
-
96
- fill = client.place_order(
97
- market_id=cid,
98
- side="BUY",
99
- outcome="YES",
100
- quantity=10,
101
- order_type="market",
102
- price="0.99", # worst-acceptable fill (YES < $1 → "any fill")
103
- )
104
- print(f"filled {fill['status']} @ {fill.get('price')}")
105
- ```
106
-
107
- Async is the same API with `await`:
108
-
109
- ```python
110
- import asyncio
111
- from polysim_sdk import AsyncPolySimClient
112
-
113
- async def main():
114
- async with AsyncPolySimClient() as client:
115
- me = await client.me()
116
- print(me["balance"])
117
-
118
- asyncio.run(main())
119
- ```
120
-
121
- ---
122
-
123
- ## Finding BTC Up/Down markets
124
-
125
- Short-horizon **crypto Up/Down** markets — "is BTC/ETH/SOL/… up or down over
126
- this window" resolving on a 5-minute … daily horizon — are the platform's
127
- most-traded product. They have a dedicated endpoint and do **not** show up
128
- through `list_markets(q="btc")` (free-text search is the `q` filter). Use
129
- `list_updown` / `get_updown`:
130
-
131
- ```python
132
- with PolySimClient() as client:
133
- # Currently-tradeable BTC 5-minute windows, filtered server-side:
134
- live = client.list_updown(asset="BTC", interval="5M", live=True)
135
- window = live[0]
136
- print(window["slug"]) # btc-updown-5m-1781400900
137
- print(window["time_range"]) # "9:35PM-9:40PM ET"
138
- print(window["group_item_threshold"]) # resolution strike (None until the window opens)
139
-
140
- # Outcome tokens live in the nested markets[] array (JSON-string fields):
141
- nested = window["markets"][0]
142
- print(nested["outcomes"]) # '["Up", "Down"]'
143
- print(nested["outcome_prices"]) # '["0.595", "0.405"]'
144
- cid = window["condition_id"]
145
- ```
146
-
147
- `interval` is one of `5M` / `15M` / `1H` / `4H` / `daily` (**uppercase**).
148
- `live=True` keeps only `active and not closed and not resolved` windows.
149
-
150
- The **live underlying spot price** plus the available assets/intervals come from
151
- the full payload via `get_updown`:
152
-
153
- ```python
154
- payload = client.get_updown(asset="BTC")
155
- btc = (payload.get("crypto_prices") or {}).get("BTC") or {}
156
- print(btc.get("price")) # 64588.0 — live BTC/USD (coingecko)
157
- print(payload["available_intervals"]) # ['5M', '15M', '1H', '4H', 'daily']
158
- print(payload["interval_counts"]) # {'5M': 169, '15M': 402, ...}
159
- ```
160
-
161
- > The spot price can be momentarily absent right at a window boundary (the
162
- > payload is cached and the feed can lag a second or two), so read it
163
- > defensively with `.get(...)` rather than chained `[...]` indexing.
164
-
165
- ### Pricing an Up/Down market
166
-
167
- The raw order book (`get_book`) on an Up/Down market is a **synthetic
168
- placeholder** — its `asks[0]` sits near ~0.99 and is not a real resting quote.
169
- For price, use the market's own fields instead:
170
-
171
- - `window["live_price"]` → `{"buy": 0.595, "sell": 0.405, ...}` (current marketable prices)
172
- - `nested["outcome_prices"]` → the latest Up/Down implied probabilities
173
- - the realised `price` returned by `place_order` — the SDK fills against the real
174
- internal book and reports the true fill price, `fee`, and `book_walk_levels`
175
-
176
- A market order with a worst-price cap still fills correctly regardless; just
177
- don't read `get_book(...)["asks"][0]` as a tradeable quote on these markets.
178
-
179
- > **Drop-in `polysim_clob_client` users:** `get_midpoint`, `get_price`,
180
- > `get_spread` and `calculate_market_price` read the outcome token's **synthetic
181
- > ~0.99 ladder** on Up/Down markets — they are *not* the underlying asset price.
182
- > For the asset, use the native reads below.
183
-
184
- ### Up/Down for HFT: strike, spot, and a push feed
185
-
186
- The two numbers an Up/Down strategy actually trades on — the **strike**
187
- ("price to beat") and the **live underlying spot** — have dedicated reads on the
188
- native client, plus a push stream and a few pure helpers:
189
-
190
- ```python
191
- from polysim_sdk import PolySimClient
192
- from polysim_sdk import updown
193
-
194
- with PolySimClient() as client:
195
- live = client.list_updown(asset="BTC", interval="5M", live=True)
196
- window = updown.next_to_expire(live) or live[0] # the contract closing soonest
197
- cid = window["condition_id"]
198
-
199
- strike = client.get_price_to_beat(cid)["price"] # GET /prices/ptb/{cid}; 404 = not set yet
200
- spot = client.get_spot("BTC")["price"] # GET /prices/live/BTC — live underlying
201
- # all spots in one call: client.get_spots()["prices"]
202
-
203
- print(updown.ptb_distance(spot, strike)) # signed price units; >0 ⇒ "Up" in the money
204
- print(updown.ptb_distance_bps(spot, strike)) # same, in bps of the strike
205
- print(updown.seconds_to_expiry(window)) # time left on the window
206
- ```
207
-
208
- `get_price_to_beat` raises `ApiError` with `status_code == 404` while a window's
209
- strike is still pending — that is "not set yet", not "market absent". It accepts
210
- a `condition_id` **or** an outcome-token id. Its `source` field tells you the
211
- provenance: `polymarket_open_price` / `polymarket_scrape` / `gamma_event_metadata`
212
- (Polymarket's own reported strike) and `chainlink_onchain` / `chainlink_timeline`
213
- (the resolution oracle) are the settlement strike; `gamma_api` / `cryptocompare`
214
- are best-effort fallbacks — treat them as indicative.
215
-
216
- The `polysim_sdk.updown` helpers are pure functions over the row dicts (no
217
- network, tolerant of missing fields): `seconds_to_expiry`, `is_window_open`,
218
- `price_to_beat` (reads the strike off a row you already have), `ptb_distance` /
219
- `ptb_distance_bps`, `open_windows`, `next_to_expire`, plus the `ASSETS` /
220
- `INTERVALS` vocab tuples. For a **streaming** tap on the underlying, see
221
- [SSE: live underlying spot](#sse-live-underlying-spot) below.
222
-
223
- ---
224
-
225
- ## Migrating from `py-clob-client`
226
-
227
- If you have a Polymarket bot, you change **three things** and delete the on-chain
228
- prelude. Everything else — method names, argument shapes, return shapes — stays.
229
-
230
- ```diff
231
- - from py_clob_client.client import ClobClient
232
- - from py_clob_client.clob_types import OrderArgs, OrderType
233
- + from polysim_clob_client.client import ClobClient
234
- + from polysim_clob_client.clob_types import OrderArgs, OrderType
235
-
236
- - client = ClobClient(
237
- - host="https://clob.polymarket.com",
238
- - key=PRIVATE_KEY, # your wallet's private key
239
- - chain_id=POLYGON,
240
- - signature_type=1,
241
- - funder=PROXY_WALLET_ADDRESS,
242
- - )
243
- - client.set_api_creds(client.create_or_derive_api_creds())
244
- - # ...plus USDC allowance/approval txns, web3 setup, etc.
245
- + client = ClobClient(
246
- + host="https://api.polysimulator.com",
247
- + key="ps_live_…", # your PolySimulator API key
248
- + )
249
-
250
- # unchanged from here on:
251
- order = client.create_order(OrderArgs(token_id=tid, price=0.55, size=10, side="BUY"))
252
- resp = client.post_order(order)
253
- ```
254
-
255
- ### What maps how
256
-
257
- Every method maps by one of three strategies:
258
-
259
- - **mirror** — identical behaviour, delegated to the HTTP core.
260
- - **adapt** — translated onto the PolySim REST surface (e.g. `get_midpoint`,
261
- `get_price`, `get_spread` are computed from `GET /v1/markets/{id}/book`).
262
- - **stub-noop** — on-chain machinery with no analog (allowances, signing,
263
- scoring, builder auth). These return a benign canned value and **make no
264
- network call**; each says so in its docstring so nothing breaks silently.
265
-
266
- ### `token_id` ↔ `(market_id, outcome)`
267
-
268
- `py-clob-client` addresses a single outcome token by `token_id`; PolySimulator
269
- addresses a **market plus an outcome**. The seam: a bare `token_id` is treated as
270
- the market id with outcome `YES`. Append `":NO"` or `":YES"` to target the other
271
- side explicitly.
272
-
273
- ```python
274
- client.create_order(OrderArgs(token_id="0xMARKET", ...)) # → market 0xMARKET, YES
275
- client.create_order(OrderArgs(token_id="0xMARKET:NO", ...)) # → market 0xMARKET, NO
276
- ```
277
-
278
- **Reads are true-token-parity; writes use the market+outcome model.** This is an
279
- intentional asymmetry:
280
-
281
- - **Book/quote reads** — `get_order_book`, `get_midpoint`, `get_price`,
282
- `get_spread`, `get_tick_size`, `get_neg_risk`, `get_last_trade_price`,
283
- `calculate_market_price` — send a **bare** `token_id` to the token-native
284
- endpoint `GET /v1/book?token_id=...`. This matches Polymarket's CLOB book
285
- reads exactly: pass the real outcome-token id you already use with
286
- `py-clob-client` and the quote comes back for *that* token, no `:YES`/`:NO`
287
- needed. The `condition_id:OUTCOME` colon form still works and routes to the
288
- condition-id book endpoint (threading the outcome through), but it is a
289
- PolySimulator convenience extension, not the parity path.
290
- - **Order writes** — `create_order` / `create_market_order` / `post_order` —
291
- resolve the `token_id` through the market+outcome seam above and submit to the
292
- rich `POST /v1/orders` endpoint (with slippage/impact/position telemetry in the
293
- response). A *bare* token id on the write path is read as a `market_id` with
294
- the `YES` outcome; if that id isn't a valid market, the server rejects the
295
- order loudly — it never silently places a different one.
296
-
297
- If you only ever pass real outcome-token ids (the `py-clob-client` norm), reads
298
- "just work" with full parity. The `:YES`/`:NO` suffix exists for callers who
299
- prefer to address PolySimulator markets by condition id directly.
300
-
301
- ### Orders are never signed
302
-
303
- `create_order` / `create_market_order` return a plain `dict` with **no
304
- `signature` field** — there is nothing to sign. `post_order` serialises it
305
- straight to `POST /v1/orders`. The recommended one-call path is
306
- `create_and_post_order(...)`.
307
-
308
- ### Auth assertions
309
-
310
- The three `py-clob-client` auth levels collapse into one. `assert_level_1_auth()`
311
- and `assert_builder_auth()` are no-ops; `assert_level_2_auth()` raises
312
- `PolyApiException` only if no API key is configured.
313
-
314
- ---
315
-
316
- ## Pagination
317
-
318
- The native client exposes Python iterators that page transparently:
319
-
320
- ```python
321
- from polysim_sdk import PolySimClient
322
- from polysim_sdk.pagination import iter_markets, iter_orders
323
-
324
- with PolySimClient() as client:
325
- for market in iter_markets(client, hot_only=True):
326
- ...
327
- for order in iter_orders(client, status="OPEN"):
328
- ...
329
- ```
330
-
331
- The drop-in surface preserves `py-clob-client`'s base64 cursor protocol, so the
332
- classic loop terminates correctly:
333
-
334
- ```python
335
- from polysim_clob_client.constants import END_CURSOR
336
-
337
- cursor = ""
338
- while cursor != END_CURSOR:
339
- page = client.get_markets(cursor)
340
- handle(page["data"])
341
- cursor = page["next_cursor"]
342
- ```
343
-
344
- ---
345
-
346
- ## WebSocket streaming
347
-
348
- ```python
349
- from polysim_sdk import PolySimClient
350
- from polysim_sdk.ws import prices_stream, executions_stream
351
-
352
- with PolySimClient() as client:
353
- for event in prices_stream(client, ["0xMARKET_A", "0xMARKET_B"]):
354
- print(event) # blocks; reconnects with a fresh token + backoff
355
- ```
356
-
357
- Async generators (`aprices_stream`, `aexecutions_stream`) are available for
358
- `async for`. The SDK mints a short-lived WS JWT per connection automatically.
359
-
360
- ### SSE: live underlying spot
361
-
362
- The WS channel above carries **market** prices. The live **underlying spot**
363
- (BTC/ETH/SOL/… ticks — the number an Up/Down bet resolves against) comes from the
364
- public, unauthenticated `/prices/stream` Server-Sent-Events firehose instead:
365
-
366
- ```python
367
- from polysim_sdk import PolySimClient
368
- from polysim_sdk.sse import spot_stream
369
-
370
- with PolySimClient() as client:
371
- for event in spot_stream(client, ["BTC", "ETH"], crypto_source="chainlink"):
372
- if event["event"] == "crypto_price":
373
- tick = event["data"]
374
- print(tick["symbol"], tick["price"], tick["source"])
375
- ```
376
-
377
- Each event is `{"event": <type>, "data": <payload>}`. The spot-bearing types are
378
- `crypto_price` and `crypto_price_batch`; `keepalive` arrives every ~5 s. Pass
379
- `crypto_source="chainlink"` (authoritative for 5m/15m settlement) or `"binance"`
380
- (denser ~10 Hz ticks); omit for both. With `condition_ids=[...]` the same stream
381
- also delivers `snapshot` / `market_price` / `orderbook` events. The async twin
382
- `aspot_stream` is available for `async for`. Both reconnect with exponential
383
- backoff and a per-connection cache-buster (so a CDN can't coalesce subscribers).
384
-
385
- ---
386
-
387
- ## Errors
388
-
389
- Catch `PolySimError` to handle every SDK-raised error; `PolyApiException` (the
390
- drop-in alias) is the same class.
391
-
392
- ```python
393
- from polysim_sdk.exceptions import (
394
- EdgeBlockedError,
395
- PolySimError,
396
- RateLimitError,
397
- ValidationError,
398
- )
399
-
400
- try:
401
- fill = client.place_order(...)
402
- except RateLimitError as exc:
403
- print(f"backing off for {exc.retry_after}s")
404
- except ValidationError as exc:
405
- print(f"bad request: {exc.code} → {exc}")
406
- except EdgeBlockedError as exc:
407
- print(f"blocked at the CDN edge — check your User-Agent: {exc}")
408
- except PolySimError as exc:
409
- print(f"other API error ({exc.status_code}): {exc}")
410
- ```
411
-
412
- The client paces itself (a 50 ms floor between requests) and backs off on
413
- `429`/`425`/`5xx` using `Retry-After`. Opt out with `floor_interval=0.0` and
414
- `max_retries=0` to handle pacing yourself.
415
-
416
- ### Troubleshooting: `error code: 1010` / blocked User-Agent
417
-
418
- If a raw HTTP call returns **HTTP 403** with the body `error code: 1010`, the
419
- request was blocked at the CDN edge — **not** by the API. The edge rejects
420
- Python's stdlib `urllib` default User-Agent (`Python-urllib/x.y`). `requests`,
421
- `httpx`, `aiohttp`, and **this SDK** all send User-Agents that pass, so you only
422
- hit this with raw `urllib` or a custom client that forwards the stdlib UA.
423
-
424
- This SDK sends a branded `User-Agent: polysim-sdk/<version>` and turns any edge
425
- block into a clear `EdgeBlockedError` (an `ApiError`, `code="EDGE_BLOCKED"`). If
426
- you must call the API without the SDK, set your own header:
427
-
428
- ```python
429
- # raw urllib — set a User-Agent so the edge lets you through
430
- import urllib.request, json
431
- req = urllib.request.Request(
432
- "https://api.polysimulator.com/v1/markets?limit=1",
433
- headers={"User-Agent": "my-app/1.0", "X-API-Key": "ps_live_..."},
434
- )
435
- data = json.load(urllib.request.urlopen(req))
436
- ```
437
-
438
- ---
439
-
440
- ## Rate limits
441
-
442
- Authoritative values are returned live by `GET /v1/keys/tiers` (`client.tiers()`).
443
- At the time of writing:
444
-
445
- | Tier | rps | rpm | WS conns | Batch | API balance |
446
- |---|---|---|---|---|---|
447
- | Free | 2 | 120 | 1 | 1 | none — read only |
448
- | Pro | 10 | 600 | 3 | 5 | $10K |
449
- | Pro+ | 30 | 1800 | 10 | 10 | $25K |
450
- | Enterprise | 100 | 6000 | 50 | 25 | custom |
451
-
452
- `free` allows 2 requests/second up to 120/minute and is read-only (no API
453
- balance). Use `pro` or above for any continuously-running bot. The table is
454
- illustrative — always trust `client.tiers()` for the live values.
455
-
456
- ---
457
-
458
- ## Beta caveats
459
-
460
- During the closed-cohort phase, a few sim behaviours differ from Polymarket.
461
- These are known limitations, not bugs:
462
-
463
- 1. **Shared API balance across keys.** Every key a user owns draws on the same
464
- balance pool today (the `balance` field returned by `me()`). Treat it as
465
- user-scoped, not key-scoped.
466
- 2. **Maker/taker heuristic.** `is_maker = (time_in_force == "GTC")`. A GTC limit
467
- that crosses on submit is booked as a $0-fee maker in the sim; on Polymarket
468
- it would be a taker. We over-count makers by design until the classifier is
469
- tightened.
470
- 3. **Orderbook freshness.** The book cache TTL is 300 s. For markets Polymarket
471
- updates less often than that, a fill may revert to displayed midpoint; a ±15%
472
- sanity guard bounds the drift. High-volume markets always fill at real ask/bid.
473
-
474
- ---
475
-
476
- ## What's included
477
-
478
- | Path | Purpose |
479
- |---|---|
480
- | `polysim_sdk/` | Native client: `PolySimClient` (sync) + `AsyncPolySimClient` (async), `ws`, `sse` (underlying-spot stream), `updown` (Up/Down row helpers), `pagination`, `exceptions`, `constants`. |
481
- | `polysim_clob_client/` | `py-clob-client` drop-in: `ClobClient` + matching `clob_types`, `constants`, `order_builder`, `exceptions`. |
482
- | `scripts/01_balance_and_market.py` | Smallest end-to-end native demo. |
483
- | `scripts/02_clob_dropin_demo.py` | The `py-clob-client` drop-in surface end-to-end. |
484
- | `scripts/03_async_concurrent.py` | Concurrent reads via the async client. |
485
- | `scripts/04_btc_updown.py` | Discover the live BTC Up/Down window: spot, strike, prices, tokens. |
486
- | `tests/` | respx-mocked unit tests for both surfaces (no creds, no network). |
487
-
488
- Run the test suite with `pytest`. Lint/type with `ruff check . && mypy polysim_sdk polysim_clob_client`.
489
-
490
- ---
491
-
492
- ## License
493
-
494
- [Apache License 2.0](./LICENSE). You may use, modify, and redistribute this
495
- SDK — including in commercial and closed-source projects — provided you keep
496
- the license and attribution notices (see [`NOTICE`](./NOTICE)). Apache-2.0 also
497
- grants an explicit patent license, which is why many companies prefer it over
498
- MIT for dependencies.
499
-
500
- The `polysim_clob_client` package mirrors the *public surface* of
501
- [`py-clob-client`](https://github.com/Polymarket/py-clob-client) (MIT) for
502
- drop-in compatibility; no upstream source is bundled. This project is not
503
- affiliated with or endorsed by Polymarket.