uselayer
A Python SDK for trading prediction markets with your own venue keys.
This release (0.1) trades Polymarket US. Paper mode (the default) fills orders against the venue's real order books with simulated money and sends nothing to the venue. Live mode sends orders with your own Polymarket US API key. Backtest mode replays books you saved.
- One order shape for every venue and mode, published as a JSON Schema (
schema/order.json). - Paper mode is the default.
preview()shows what an order would do and sends nothing. - Guardrails check every order before it's sent: position size, budget, daily loss, allowed markets, approvals, stop-loss and take-profit. A price collar, an order throttle and a kill switch are always on.
- Fees come from each venue's published schedule in force at the time of the trade. They match
Layer's API (
POST /v0/profit,POST /v0/size) to the millionth of a dollar. - Everything stays on your machine: a local SQLite file per mode, no telemetry.
Install
pip install uselayer
Python 3.11 or newer.
Paper trade in five lines
from uselayer import Client
client = Client() # paper mode: real books, simulated fills
m = client.markets(limit=20)[0] # open Polymarket US markets, no key needed
book = client.book(m.slug)
order = client.order(
venue="polymarket_us", market=m.slug, side="yes", price=book.outcome("yes").best_ask.price, size=5
)
print(client.preview(order)) # fill, fees, every rule's decision
print(client.send(order)) # the order, filled against the book
client.positions(), client.fills() and client.orders() read the local store. Every fill in paper
mode is a SimulatedFill with simulated=True.
Live mode
from uselayer import Client, PolymarketUS
client = Client(mode="live", polymarket_us=PolymarketUS(key_id="...", secret_key_path="~/.pmus/secret"))
client.balances()["polymarket_us"].cash
order = client.buy(venue="polymarket_us", market="<slug>", side="yes", price=0.42, size=5)
client.positions() # from the venue
Create the key at polymarket.us/developer. It stays on your machine: requests are signed with it
locally and only the signature is sent. Books in live mode come from the venue's WebSocket, so they
aren't cached. An order whose answer never arrives raises outcome_unknown and is never sent again
on its own: call client.sync() and check client.orders().
If the local store is new but your account already has open orders or positions, live mode starts
with the kill switch on, until you run python -m uselayer resume --mode live.
Guardrails
client = Client(
rules={
"max_position": {"per_market": 200}, # $ at risk in one market
"budget": 1000, # $ at risk in total
"max_daily_loss": {"amount": 150}, # stop opening positions after this loss today
"approve_above": 100, # ask before orders above $100
"stop_loss": {"pct": 25}, # exits sent by client.monitor()
}
)
Rules can also come from a YAML or JSON file: Client(rules="guardrails.yaml") (YAML needs
pip install "uselayer[yaml]"). They're fixed when the client is created.
Kill switch. client.kill() cancels resting orders and blocks new ones. From another terminal:
python -m uselayer kill. It stays on, even after a restart, until a person runs
python -m uselayer resume. The client a strategy or agent holds can't resume.
Pairs: both sides, with the leg-risk guard
When two markets are the same bet, buying YES on one and NO on the other pays $1 per contract
either way. quote() prices that after both fees; trade() places both legs:
q = client.quote(pair) # pair: a Match from client.matches(), or two (venue, market)
t = client.trade(pair, size=100, min_edge=0.01)
t.status # "hedged" | "missed" | "unwound" | "exposed"
The thinner leg goes first, immediate-or-cancel. The other leg goes for what filled, up to its
break-even price. If it can't be completed within chase_s, the first leg is sold back, never below
its entry price minus max_unwind_loss (on_miss="unwind", the default), or the open contracts are
reported (on_miss="hold"). Both legs pass the guardrails together before either is sent.
In this release trade() runs in paper and backtest mode.
One strategy, every mode
def strategy(client, pair, quote):
if quote.net_profit_per_contract >= 0.02:
client.trade(pair, size=100)
Client(mode="backtest", books=saved_books).run(strategy, [pair]) # the past
Client().run(strategy, [pair], iterations=60) # now, paper
Backtest on books you saved
from uselayer import Client
from uselayer.backtest import load_books, record_books
record_books(Client(), ["<slug>"], "books.jsonl") # run on a schedule to build a history
bt = Client(mode="backtest", books=load_books("books.jsonl"))
bt.replay(lambda client, book: ...) # place orders as each book arrives
The replay uses the same fill model, fees and rules as paper mode, on the replayed clock.
Fees by date
from datetime import UTC, datetime
from uselayer import FeeSettings, calculate_fee, rules_at
rules_at("polymarket_us", datetime.now(UTC)).source # the schedule's page
calculate_fee(
FeeSettings(venue="polymarket_us"), contracts=100, price=0.5, role="taker", at=datetime.now(UTC)
)
Before the earliest schedule the SDK knows, it raises no_venue_rules instead of guessing.
What paper mode can't tell you
- Queue position. A paper order that rests fills as soon as a later book reaches its price. A real one waits in line, so paper fills look at least as good as live ones.
- Freshness without a key. In paper mode, Polymarket US's public book is cached for up to 30
seconds. The SDK stamps each book with the venue's time and, when a copy is older than
max_quote_age_s(10 s by default), waits for a fresh one before using it.
For AI agents
AGENTS.md and llms.txt ship inside the package. Every public method has a docstring with an
example, every object has .to_dict(), and every error is a VenueError with code, hint and
next. The examples/ folder runs in CI.
Layer
Layer (uselayer.sh) finds markets that are the same bet on different venues. With a Layer API key,
client.matches(q="...") returns them. The SDK sends Layer your key, the market ids Layer gave you
and your filters, and nothing else: no prices, orders, positions or venue keys.
License
MIT
Metadata
Release files for uselayer 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| uselayer-0.1.0.tar.gz | 97.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uselayer-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 180.9 kB
Release files / uselayer-0.1.0.tar.gz
| Download URL | uselayer-0.1.0.tar.gz |
|---|---|
| Size | 97.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
688828be633d12f58875ac2dcab872d5a74472213b9ed204c71a8e4686d465de
|
|
BLAKE2b-256 checksum How to use checksums |
873e98a5640e9bedda7d893749f30d74c48d329ba69d244c0ada852ff568b5ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / uselayer-0.1.0-py3-none-any.whl
| Download URL | uselayer-0.1.0-py3-none-any.whl |
|---|---|
| Size | 83.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
95b19ad6bf9b6a8b45dab9e9b58a0ab0f2db9dd8773c8253aa16f73be010318c
|
|
BLAKE2b-256 checksum How to use checksums |
5bf3f98317839c9bce265d4757bee3a8afba48d66c42fd7f64181bbc99f3ab35
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|