uselayer 0.1.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.
Files changed (57) hide show
  1. uselayer-0.1.0/.gitignore +10 -0
  2. uselayer-0.1.0/CHANGELOG.md +24 -0
  3. uselayer-0.1.0/LICENSE +21 -0
  4. uselayer-0.1.0/PKG-INFO +190 -0
  5. uselayer-0.1.0/README.md +163 -0
  6. uselayer-0.1.0/examples/01_book_and_preview.py +22 -0
  7. uselayer-0.1.0/examples/02_paper_trade.py +26 -0
  8. uselayer-0.1.0/examples/03_rules_and_kill.py +24 -0
  9. uselayer-0.1.0/examples/04_backtest_saved_books.py +31 -0
  10. uselayer-0.1.0/examples/05_fees_by_date.py +21 -0
  11. uselayer-0.1.0/examples/06_pair_trade_backtest.py +47 -0
  12. uselayer-0.1.0/examples/_pick.py +14 -0
  13. uselayer-0.1.0/examples/recorded.json +5637 -0
  14. uselayer-0.1.0/pyproject.toml +63 -0
  15. uselayer-0.1.0/src/uselayer/AGENTS.md +67 -0
  16. uselayer-0.1.0/src/uselayer/__init__.py +69 -0
  17. uselayer-0.1.0/src/uselayer/__main__.py +43 -0
  18. uselayer-0.1.0/src/uselayer/_recorded.py +74 -0
  19. uselayer-0.1.0/src/uselayer/_switches.py +22 -0
  20. uselayer-0.1.0/src/uselayer/_version.py +1 -0
  21. uselayer-0.1.0/src/uselayer/backtest.py +91 -0
  22. uselayer-0.1.0/src/uselayer/books.py +151 -0
  23. uselayer-0.1.0/src/uselayer/calc.py +538 -0
  24. uselayer-0.1.0/src/uselayer/client.py +1146 -0
  25. uselayer-0.1.0/src/uselayer/errors.py +126 -0
  26. uselayer-0.1.0/src/uselayer/events.py +141 -0
  27. uselayer-0.1.0/src/uselayer/fees.py +123 -0
  28. uselayer-0.1.0/src/uselayer/fill.py +222 -0
  29. uselayer-0.1.0/src/uselayer/guardrails.py +643 -0
  30. uselayer-0.1.0/src/uselayer/http.py +328 -0
  31. uselayer-0.1.0/src/uselayer/layer_api.py +134 -0
  32. uselayer-0.1.0/src/uselayer/llms.txt +23 -0
  33. uselayer-0.1.0/src/uselayer/orders.py +113 -0
  34. uselayer-0.1.0/src/uselayer/paper.py +179 -0
  35. uselayer-0.1.0/src/uselayer/portfolio.py +96 -0
  36. uselayer-0.1.0/src/uselayer/py.typed +0 -0
  37. uselayer-0.1.0/src/uselayer/store.py +264 -0
  38. uselayer-0.1.0/src/uselayer/trading.py +506 -0
  39. uselayer-0.1.0/src/uselayer/venue_rules.py +211 -0
  40. uselayer-0.1.0/src/uselayer/venues/__init__.py +1 -0
  41. uselayer-0.1.0/src/uselayer/venues/base.py +139 -0
  42. uselayer-0.1.0/src/uselayer/venues/polymarket_us.py +187 -0
  43. uselayer-0.1.0/src/uselayer/venues/polymarket_us_live.py +634 -0
  44. uselayer-0.1.0/tests/conftest.py +150 -0
  45. uselayer-0.1.0/tests/test_backtest.py +71 -0
  46. uselayer-0.1.0/tests/test_examples.py +27 -0
  47. uselayer-0.1.0/tests/test_fees_and_rules.py +69 -0
  48. uselayer-0.1.0/tests/test_golden.py +97 -0
  49. uselayer-0.1.0/tests/test_guardrails.py +199 -0
  50. uselayer-0.1.0/tests/test_http.py +139 -0
  51. uselayer-0.1.0/tests/test_layer_traffic.py +68 -0
  52. uselayer-0.1.0/tests/test_live_polymarket_us.py +513 -0
  53. uselayer-0.1.0/tests/test_paper.py +140 -0
  54. uselayer-0.1.0/tests/test_public_repo.py +42 -0
  55. uselayer-0.1.0/tests/test_store.py +26 -0
  56. uselayer-0.1.0/tests/test_trade.py +220 -0
  57. uselayer-0.1.0/tests/test_types.py +76 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .mypy_cache/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ uv.lock.bak
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (unreleased)
4
+
5
+ - `Client` in paper mode (the default): orders fill against Polymarket US's live public order books
6
+ through Layer's fill model; nothing is sent to the venue.
7
+ - Live mode for Polymarket US with your own API key (`PolymarketUS`): signed orders, cancel and
8
+ cancel-all, open orders, fills, positions and balance; fresh books from the venue's WebSocket; an
9
+ unknown order outcome is looked up and never resent; a new store starts killed when the account
10
+ already has open orders or positions.
11
+ - Backtest mode: replay books you saved with `uselayer.backtest.record_books`.
12
+ - Pairs: `quote()` prices YES on one market plus NO on its twin after both fees; `trade()` places both
13
+ legs with the leg-risk guard (thinner leg first, second leg up to break-even, chase, then unwind or
14
+ report the exposure); `run(strategy, pairs)` runs the same strategy in backtest, paper or live.
15
+ - One order shape for every venue and mode, published as `schema/order.json`.
16
+ - `preview()`: the fill, fees and every rule's decision, without sending anything.
17
+ - Guardrails: max position, budget, max daily loss, allowed venues/markets/actions, expiry,
18
+ approval above a size, stop-loss and take-profit; an always-on price collar, order throttle and
19
+ kill switch (`python -m uselayer kill | resume | status`).
20
+ - Dated venue rules: fee schedules looked up by the time of the trade.
21
+ - Fee math, `profit()` and `size()` that match Layer's API to the millionth of a dollar.
22
+ - One error type, `VenueError`, with `code`, `hint`, `next` and `retryable`; retries and pacing per
23
+ venue host.
24
+ - Local SQLite store per mode; no telemetry.
uselayer-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Precious Emakenemi
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.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.5
2
+ Name: uselayer
3
+ Version: 0.1.0
4
+ Summary: Trade prediction markets with your own venue keys: one order shape, paper mode by default, guardrails, and fee math that matches Layer's API.
5
+ Project-URL: Homepage, https://uselayer.sh
6
+ Project-URL: Source, https://github.com/Dave-56/uselayer-sdk
7
+ Author: Layer
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: polymarket,prediction markets,sdk,trading
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: cryptography>=42
21
+ Requires-Dist: httpx>=0.27
22
+ Requires-Dist: pydantic>=2.7
23
+ Requires-Dist: websockets>=13
24
+ Provides-Extra: yaml
25
+ Requires-Dist: pyyaml>=6; extra == 'yaml'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # uselayer
29
+
30
+ A Python SDK for trading prediction markets with your own venue keys.
31
+
32
+ This release (0.1) trades **Polymarket US**. **Paper mode** (the default) fills orders against the
33
+ venue's real order books with simulated money and sends nothing to the venue. **Live mode** sends
34
+ orders with your own Polymarket US API key. **Backtest mode** replays books you saved.
35
+
36
+ - One order shape for every venue and mode, published as a JSON Schema (`schema/order.json`).
37
+ - Paper mode is the default. `preview()` shows what an order would do and sends nothing.
38
+ - Guardrails check every order before it's sent: position size, budget, daily loss, allowed markets,
39
+ approvals, stop-loss and take-profit. A price collar, an order throttle and a kill switch are
40
+ always on.
41
+ - Fees come from each venue's published schedule in force at the time of the trade. They match
42
+ Layer's API (`POST /v0/profit`, `POST /v0/size`) to the millionth of a dollar.
43
+ - Everything stays on your machine: a local SQLite file per mode, no telemetry.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install uselayer
49
+ ```
50
+
51
+ Python 3.11 or newer.
52
+
53
+ ## Paper trade in five lines
54
+
55
+ ```python
56
+ from uselayer import Client
57
+
58
+ client = Client() # paper mode: real books, simulated fills
59
+ m = client.markets(limit=20)[0] # open Polymarket US markets, no key needed
60
+ book = client.book(m.slug)
61
+ order = client.order(
62
+ venue="polymarket_us", market=m.slug, side="yes", price=book.outcome("yes").best_ask.price, size=5
63
+ )
64
+ print(client.preview(order)) # fill, fees, every rule's decision
65
+ print(client.send(order)) # the order, filled against the book
66
+ ```
67
+
68
+ `client.positions()`, `client.fills()` and `client.orders()` read the local store. Every fill in paper
69
+ mode is a `SimulatedFill` with `simulated=True`.
70
+
71
+ ## Live mode
72
+
73
+ ```python
74
+ from uselayer import Client, PolymarketUS
75
+
76
+ client = Client(mode="live", polymarket_us=PolymarketUS(key_id="...", secret_key_path="~/.pmus/secret"))
77
+ client.balances()["polymarket_us"].cash
78
+ order = client.buy(venue="polymarket_us", market="<slug>", side="yes", price=0.42, size=5)
79
+ client.positions() # from the venue
80
+ ```
81
+
82
+ Create the key at polymarket.us/developer. It stays on your machine: requests are signed with it
83
+ locally and only the signature is sent. Books in live mode come from the venue's WebSocket, so they
84
+ aren't cached. An order whose answer never arrives raises `outcome_unknown` and is never sent again
85
+ on its own: call `client.sync()` and check `client.orders()`.
86
+
87
+ If the local store is new but your account already has open orders or positions, live mode starts
88
+ with the kill switch on, until you run `python -m uselayer resume --mode live`.
89
+
90
+ ## Guardrails
91
+
92
+ ```python
93
+ client = Client(
94
+ rules={
95
+ "max_position": {"per_market": 200}, # $ at risk in one market
96
+ "budget": 1000, # $ at risk in total
97
+ "max_daily_loss": {"amount": 150}, # stop opening positions after this loss today
98
+ "approve_above": 100, # ask before orders above $100
99
+ "stop_loss": {"pct": 25}, # exits sent by client.monitor()
100
+ }
101
+ )
102
+ ```
103
+
104
+ Rules can also come from a YAML or JSON file: `Client(rules="guardrails.yaml")` (YAML needs
105
+ `pip install "uselayer[yaml]"`). They're fixed when the client is created.
106
+
107
+ **Kill switch.** `client.kill()` cancels resting orders and blocks new ones. From another terminal:
108
+ `python -m uselayer kill`. It stays on, even after a restart, until a person runs
109
+ `python -m uselayer resume`. The client a strategy or agent holds can't resume.
110
+
111
+ ## Pairs: both sides, with the leg-risk guard
112
+
113
+ When two markets are the same bet, buying YES on one and NO on the other pays $1 per contract
114
+ either way. `quote()` prices that after both fees; `trade()` places both legs:
115
+
116
+ ```python
117
+ q = client.quote(pair) # pair: a Match from client.matches(), or two (venue, market)
118
+ t = client.trade(pair, size=100, min_edge=0.01)
119
+ t.status # "hedged" | "missed" | "unwound" | "exposed"
120
+ ```
121
+
122
+ The thinner leg goes first, immediate-or-cancel. The other leg goes for what filled, up to its
123
+ break-even price. If it can't be completed within `chase_s`, the first leg is sold back, never below
124
+ its entry price minus `max_unwind_loss` (`on_miss="unwind"`, the default), or the open contracts are
125
+ reported (`on_miss="hold"`). Both legs pass the guardrails together before either is sent.
126
+
127
+ In this release `trade()` runs in paper and backtest mode.
128
+
129
+ ## One strategy, every mode
130
+
131
+ ```python
132
+ def strategy(client, pair, quote):
133
+ if quote.net_profit_per_contract >= 0.02:
134
+ client.trade(pair, size=100)
135
+
136
+ Client(mode="backtest", books=saved_books).run(strategy, [pair]) # the past
137
+ Client().run(strategy, [pair], iterations=60) # now, paper
138
+ ```
139
+
140
+ ## Backtest on books you saved
141
+
142
+ ```python
143
+ from uselayer import Client
144
+ from uselayer.backtest import load_books, record_books
145
+
146
+ record_books(Client(), ["<slug>"], "books.jsonl") # run on a schedule to build a history
147
+
148
+ bt = Client(mode="backtest", books=load_books("books.jsonl"))
149
+ bt.replay(lambda client, book: ...) # place orders as each book arrives
150
+ ```
151
+
152
+ The replay uses the same fill model, fees and rules as paper mode, on the replayed clock.
153
+
154
+ ## Fees by date
155
+
156
+ ```python
157
+ from datetime import UTC, datetime
158
+ from uselayer import FeeSettings, calculate_fee, rules_at
159
+
160
+ rules_at("polymarket_us", datetime.now(UTC)).source # the schedule's page
161
+ calculate_fee(
162
+ FeeSettings(venue="polymarket_us"), contracts=100, price=0.5, role="taker", at=datetime.now(UTC)
163
+ )
164
+ ```
165
+
166
+ Before the earliest schedule the SDK knows, it raises `no_venue_rules` instead of guessing.
167
+
168
+ ## What paper mode can't tell you
169
+
170
+ - **Queue position.** A paper order that rests fills as soon as a later book reaches its price. A real
171
+ one waits in line, so paper fills look at least as good as live ones.
172
+ - **Freshness without a key.** In paper mode, Polymarket US's public book is cached for up to 30
173
+ seconds. The SDK stamps each book with the venue's time and, when a copy is older than
174
+ `max_quote_age_s` (10 s by default), waits for a fresh one before using it.
175
+
176
+ ## For AI agents
177
+
178
+ `AGENTS.md` and `llms.txt` ship inside the package. Every public method has a docstring with an
179
+ example, every object has `.to_dict()`, and every error is a `VenueError` with `code`, `hint` and
180
+ `next`. The `examples/` folder runs in CI.
181
+
182
+ ## Layer
183
+
184
+ Layer (uselayer.sh) finds markets that are the same bet on different venues. With a Layer API key,
185
+ `client.matches(q="...")` returns them. The SDK sends Layer your key, the market ids Layer gave you
186
+ and your filters, and nothing else: no prices, orders, positions or venue keys.
187
+
188
+ ## License
189
+
190
+ MIT
@@ -0,0 +1,163 @@
1
+ # uselayer
2
+
3
+ A Python SDK for trading prediction markets with your own venue keys.
4
+
5
+ This release (0.1) trades **Polymarket US**. **Paper mode** (the default) fills orders against the
6
+ venue's real order books with simulated money and sends nothing to the venue. **Live mode** sends
7
+ orders with your own Polymarket US API key. **Backtest mode** replays books you saved.
8
+
9
+ - One order shape for every venue and mode, published as a JSON Schema (`schema/order.json`).
10
+ - Paper mode is the default. `preview()` shows what an order would do and sends nothing.
11
+ - Guardrails check every order before it's sent: position size, budget, daily loss, allowed markets,
12
+ approvals, stop-loss and take-profit. A price collar, an order throttle and a kill switch are
13
+ always on.
14
+ - Fees come from each venue's published schedule in force at the time of the trade. They match
15
+ Layer's API (`POST /v0/profit`, `POST /v0/size`) to the millionth of a dollar.
16
+ - Everything stays on your machine: a local SQLite file per mode, no telemetry.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install uselayer
22
+ ```
23
+
24
+ Python 3.11 or newer.
25
+
26
+ ## Paper trade in five lines
27
+
28
+ ```python
29
+ from uselayer import Client
30
+
31
+ client = Client() # paper mode: real books, simulated fills
32
+ m = client.markets(limit=20)[0] # open Polymarket US markets, no key needed
33
+ book = client.book(m.slug)
34
+ order = client.order(
35
+ venue="polymarket_us", market=m.slug, side="yes", price=book.outcome("yes").best_ask.price, size=5
36
+ )
37
+ print(client.preview(order)) # fill, fees, every rule's decision
38
+ print(client.send(order)) # the order, filled against the book
39
+ ```
40
+
41
+ `client.positions()`, `client.fills()` and `client.orders()` read the local store. Every fill in paper
42
+ mode is a `SimulatedFill` with `simulated=True`.
43
+
44
+ ## Live mode
45
+
46
+ ```python
47
+ from uselayer import Client, PolymarketUS
48
+
49
+ client = Client(mode="live", polymarket_us=PolymarketUS(key_id="...", secret_key_path="~/.pmus/secret"))
50
+ client.balances()["polymarket_us"].cash
51
+ order = client.buy(venue="polymarket_us", market="<slug>", side="yes", price=0.42, size=5)
52
+ client.positions() # from the venue
53
+ ```
54
+
55
+ Create the key at polymarket.us/developer. It stays on your machine: requests are signed with it
56
+ locally and only the signature is sent. Books in live mode come from the venue's WebSocket, so they
57
+ aren't cached. An order whose answer never arrives raises `outcome_unknown` and is never sent again
58
+ on its own: call `client.sync()` and check `client.orders()`.
59
+
60
+ If the local store is new but your account already has open orders or positions, live mode starts
61
+ with the kill switch on, until you run `python -m uselayer resume --mode live`.
62
+
63
+ ## Guardrails
64
+
65
+ ```python
66
+ client = Client(
67
+ rules={
68
+ "max_position": {"per_market": 200}, # $ at risk in one market
69
+ "budget": 1000, # $ at risk in total
70
+ "max_daily_loss": {"amount": 150}, # stop opening positions after this loss today
71
+ "approve_above": 100, # ask before orders above $100
72
+ "stop_loss": {"pct": 25}, # exits sent by client.monitor()
73
+ }
74
+ )
75
+ ```
76
+
77
+ Rules can also come from a YAML or JSON file: `Client(rules="guardrails.yaml")` (YAML needs
78
+ `pip install "uselayer[yaml]"`). They're fixed when the client is created.
79
+
80
+ **Kill switch.** `client.kill()` cancels resting orders and blocks new ones. From another terminal:
81
+ `python -m uselayer kill`. It stays on, even after a restart, until a person runs
82
+ `python -m uselayer resume`. The client a strategy or agent holds can't resume.
83
+
84
+ ## Pairs: both sides, with the leg-risk guard
85
+
86
+ When two markets are the same bet, buying YES on one and NO on the other pays $1 per contract
87
+ either way. `quote()` prices that after both fees; `trade()` places both legs:
88
+
89
+ ```python
90
+ q = client.quote(pair) # pair: a Match from client.matches(), or two (venue, market)
91
+ t = client.trade(pair, size=100, min_edge=0.01)
92
+ t.status # "hedged" | "missed" | "unwound" | "exposed"
93
+ ```
94
+
95
+ The thinner leg goes first, immediate-or-cancel. The other leg goes for what filled, up to its
96
+ break-even price. If it can't be completed within `chase_s`, the first leg is sold back, never below
97
+ its entry price minus `max_unwind_loss` (`on_miss="unwind"`, the default), or the open contracts are
98
+ reported (`on_miss="hold"`). Both legs pass the guardrails together before either is sent.
99
+
100
+ In this release `trade()` runs in paper and backtest mode.
101
+
102
+ ## One strategy, every mode
103
+
104
+ ```python
105
+ def strategy(client, pair, quote):
106
+ if quote.net_profit_per_contract >= 0.02:
107
+ client.trade(pair, size=100)
108
+
109
+ Client(mode="backtest", books=saved_books).run(strategy, [pair]) # the past
110
+ Client().run(strategy, [pair], iterations=60) # now, paper
111
+ ```
112
+
113
+ ## Backtest on books you saved
114
+
115
+ ```python
116
+ from uselayer import Client
117
+ from uselayer.backtest import load_books, record_books
118
+
119
+ record_books(Client(), ["<slug>"], "books.jsonl") # run on a schedule to build a history
120
+
121
+ bt = Client(mode="backtest", books=load_books("books.jsonl"))
122
+ bt.replay(lambda client, book: ...) # place orders as each book arrives
123
+ ```
124
+
125
+ The replay uses the same fill model, fees and rules as paper mode, on the replayed clock.
126
+
127
+ ## Fees by date
128
+
129
+ ```python
130
+ from datetime import UTC, datetime
131
+ from uselayer import FeeSettings, calculate_fee, rules_at
132
+
133
+ rules_at("polymarket_us", datetime.now(UTC)).source # the schedule's page
134
+ calculate_fee(
135
+ FeeSettings(venue="polymarket_us"), contracts=100, price=0.5, role="taker", at=datetime.now(UTC)
136
+ )
137
+ ```
138
+
139
+ Before the earliest schedule the SDK knows, it raises `no_venue_rules` instead of guessing.
140
+
141
+ ## What paper mode can't tell you
142
+
143
+ - **Queue position.** A paper order that rests fills as soon as a later book reaches its price. A real
144
+ one waits in line, so paper fills look at least as good as live ones.
145
+ - **Freshness without a key.** In paper mode, Polymarket US's public book is cached for up to 30
146
+ seconds. The SDK stamps each book with the venue's time and, when a copy is older than
147
+ `max_quote_age_s` (10 s by default), waits for a fresh one before using it.
148
+
149
+ ## For AI agents
150
+
151
+ `AGENTS.md` and `llms.txt` ship inside the package. Every public method has a docstring with an
152
+ example, every object has `.to_dict()`, and every error is a `VenueError` with `code`, `hint` and
153
+ `next`. The `examples/` folder runs in CI.
154
+
155
+ ## Layer
156
+
157
+ Layer (uselayer.sh) finds markets that are the same bet on different venues. With a Layer API key,
158
+ `client.matches(q="...")` returns them. The SDK sends Layer your key, the market ids Layer gave you
159
+ and your filters, and nothing else: no prices, orders, positions or venue keys.
160
+
161
+ ## License
162
+
163
+ MIT
@@ -0,0 +1,22 @@
1
+ """Read a market's book and preview an order. Nothing is sent.
2
+
3
+ python examples/01_book_and_preview.py
4
+ """
5
+
6
+ from _pick import pick_market
7
+
8
+ from uselayer import Client
9
+
10
+ client = Client(store=":memory:") # paper mode (the default); this store lives in memory only
11
+ slug, book = pick_market(client)
12
+ yes = book.outcome("yes")
13
+ print(f"market {slug}: best bid {yes.best_bid.price}, best ask {yes.best_ask.price}, as of {book.as_of}")
14
+
15
+ order = client.order(venue="polymarket_us", market=slug, side="yes", price=yes.best_ask.price, size=5)
16
+ preview = client.preview(order)
17
+ print(
18
+ f"allowed: {preview.allowed}, would fill {preview.est_fill.filled} @ {preview.est_fill.avg_price}, fees ${preview.fees}"
19
+ )
20
+ for d in preview.verdict.decisions:
21
+ if d.result != "allow":
22
+ print(f" {d.rule}: {d.result} — {d.reason}")
@@ -0,0 +1,26 @@
1
+ """Buy in paper mode against the live book, then check the fee by hand.
2
+
3
+ python examples/02_paper_trade.py
4
+ """
5
+
6
+ from _pick import pick_market
7
+
8
+ from uselayer import Client
9
+
10
+ client = Client(store=":memory:")
11
+ slug, book = pick_market(client)
12
+ ask = book.outcome("yes").best_ask
13
+
14
+ order = client.buy(venue="polymarket_us", market=slug, side="yes", price=ask.price, size=5)
15
+ print(f"{order.status}: {order.filled} @ {order.avg_price}, fees ${order.fees}")
16
+
17
+ coefficient = client.market(slug).fee_coefficient or 0.0695
18
+ for fill in client.fills():
19
+ by_hand = coefficient * fill.contracts * fill.price * (1 - fill.price)
20
+ print(
21
+ f" fill {fill.contracts} @ {fill.price}: fee ${fill.fee} (by hand ${by_hand:.4f}, billed to the cent)"
22
+ )
23
+ assert abs(fill.fee - by_hand) <= 0.005 + 1e-9
24
+
25
+ for p in client.positions():
26
+ print(f"position: {p.contracts} {p.side} of {p.market} at {p.avg_price} (simulated: {p.simulated})")
@@ -0,0 +1,24 @@
1
+ """Set guardrails, see one block an order, then press the kill switch.
2
+
3
+ python examples/03_rules_and_kill.py
4
+ """
5
+
6
+ from _pick import pick_market
7
+
8
+ from uselayer import Client, VenueError
9
+
10
+ client = Client(store=":memory:", rules={"max_position": {"per_market": 3}, "budget": 10})
11
+ slug, book = pick_market(client)
12
+ ask = book.outcome("yes").best_ask
13
+
14
+ big = client.order(venue="polymarket_us", market=slug, side="yes", price=ask.price, size=50)
15
+ print("preview of a big order:", client.preview(big).blocked_by) # max_position
16
+
17
+ small = client.buy(venue="polymarket_us", market=slug, side="yes", price=ask.price, size=1)
18
+ print("small order:", small.status)
19
+
20
+ client.kill() # cancels resting orders and blocks new ones (python -m uselayer kill does the same from a terminal)
21
+ try:
22
+ client.buy(venue="polymarket_us", market=slug, side="yes", price=ask.price, size=1)
23
+ except VenueError as e:
24
+ print(f"after kill: {e.code} by {e.rule}. {e.next}")
@@ -0,0 +1,31 @@
1
+ """Save a few books, then replay them in backtest mode through the same fill model and rules.
2
+
3
+ python examples/04_backtest_saved_books.py
4
+ """
5
+
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ from _pick import pick_market
10
+
11
+ from uselayer import Book, Client
12
+ from uselayer.backtest import load_books, record_books
13
+
14
+ live = Client(store=":memory:")
15
+ slug, _ = pick_market(live)
16
+ path = Path(tempfile.mkdtemp()) / "books.jsonl"
17
+ for _ in range(3):
18
+ record_books(live, [slug], path) # in real use, call this on a schedule to build a history
19
+
20
+
21
+ def strategy(client: Client, book: Book) -> None:
22
+ ask = book.outcome("yes").best_ask
23
+ if ask and not client.positions():
24
+ client.buy(venue=book.venue, market=book.market, side="yes", price=ask.price, size=2)
25
+
26
+
27
+ bt = Client(mode="backtest", books=load_books(path))
28
+ result = bt.replay(strategy)
29
+ print(f"replayed {result['books']} books, {result['fills']} simulated fills")
30
+ for p in bt.positions():
31
+ print(f"position: {p.contracts} @ {p.avg_price}, fees ${p.fees}")
@@ -0,0 +1,21 @@
1
+ """Fees come from the schedule in force at the time of the trade.
2
+
3
+ python examples/05_fees_by_date.py
4
+ """
5
+
6
+ from datetime import UTC, datetime
7
+
8
+ from uselayer import FeeSettings, calculate_fee, rules_at
9
+ from uselayer.fees import dollars
10
+
11
+ now = datetime.now(UTC)
12
+ r = rules_at("polymarket_us", now)
13
+ print(f"Polymarket US schedule from {r.effective_from.date()} ({r.source})")
14
+ fee = calculate_fee(FeeSettings(venue="polymarket_us"), contracts=100, price=0.5, role="taker", at=now)
15
+ print(f"100 contracts at $0.50, taker: ${dollars(fee)}")
16
+
17
+ for when in (datetime(2026, 7, 9, tzinfo=UTC), datetime(2026, 7, 11, tzinfo=UTC)):
18
+ s = FeeSettings(venue="polymarket", category="sports")
19
+ print(
20
+ f"Polymarket sports, {when.date()}: ${dollars(calculate_fee(s, contracts=100, price=0.5, role='taker', at=when))}"
21
+ )
@@ -0,0 +1,47 @@
1
+ """Trade both sides of a pair with the leg-risk guard, in backtest mode.
2
+
3
+ The books below are made up for the example; they aren't real prices. Two markets that are the same
4
+ bet: buying YES on one and NO on the other pays $1 per contract either way.
5
+
6
+ python examples/06_pair_trade_backtest.py
7
+ """
8
+
9
+ from datetime import UTC, datetime, timedelta
10
+
11
+ from uselayer import Book, Client, Level
12
+
13
+ t0 = datetime(2026, 10, 1, 12, 0, tzinfo=UTC)
14
+
15
+
16
+ def book(market: str, seconds: int, bid: float, ask: float, size: float = 50) -> Book:
17
+ return Book(
18
+ venue="polymarket_us",
19
+ market=market,
20
+ bids=(Level(price=bid, size=size),),
21
+ asks=(Level(price=ask, size=size),),
22
+ as_of=t0 + timedelta(seconds=seconds),
23
+ )
24
+
25
+
26
+ pair = [("polymarket_us", "example-a"), ("polymarket_us", "example-b")]
27
+ books = [
28
+ book("example-a", 0, 0.40, 0.42), # YES on A costs 0.42
29
+ book("example-b", 1, 0.60, 0.62), # NO on B costs 1 − 0.60 = 0.40
30
+ book("example-a", 60, 0.47, 0.49),
31
+ book("example-b", 61, 0.52, 0.54),
32
+ ]
33
+
34
+
35
+ def strategy(client: Client, pair: list[tuple[str, str]], quote) -> None: # type: ignore[no-untyped-def]
36
+ print(
37
+ f"quote: {quote.contracts} contracts clear the edge, {quote.net_profit_per_contract} a contract after fees"
38
+ )
39
+ if quote.net_profit_per_contract >= 0.02 and not client.positions():
40
+ t = client.trade(pair, size=20, min_edge=0.01)
41
+ print(f"trade: {t.status}, {t.hedged} hedged, ${t.locked_in} locked in after fees")
42
+
43
+
44
+ bt = Client(mode="backtest", books=books, rules={"max_position": {"per_market": 50}})
45
+ bt.run(strategy, [pair])
46
+ for p in bt.positions():
47
+ print(f"position: {p.contracts} {p.side} of {p.market} at {p.avg_price}")
@@ -0,0 +1,14 @@
1
+ """Shared by the examples: pick an open Polymarket US market with prices on both sides."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uselayer import Book, Client
6
+
7
+
8
+ def pick_market(client: Client) -> tuple[str, Book]:
9
+ for m in client.markets(limit=50):
10
+ book = client.book(m.slug)
11
+ yes = book.outcome("yes")
12
+ if yes.best_bid and yes.best_ask and 0.1 < yes.best_ask.price < 0.9 and yes.best_ask.size >= 5:
13
+ return m.slug, book
14
+ raise SystemExit("No open market with prices on both sides right now; try again later.")