Skip to content

Commit 19c88d7

Browse files
committed
Improve Polymarket fees calculation and update examples
1 parent b2beaeb commit 19c88d7

File tree

9 files changed

+134
-62
lines changed

9 files changed

+134
-62
lines changed

docs/integrations/polymarket.md

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ separately depending on the use case.
5858

5959
:::note
6060
Most users will define a configuration for a live trading node (as below),
61-
and won't need to necessarily work with these lower level components directly.
61+
and won't need to work with these lower-level components directly.
6262
:::
6363

6464
## USDC.e (PoS)
@@ -99,7 +99,7 @@ Ensure your wallet is funded with **USDC.e**, otherwise you will encounter the "
9999
### Setting allowances for Polymarket contracts
100100

101101
Before you can start trading, you need to ensure that your wallet has allowances set for Polymarket's smart contracts.
102-
You can do this by running the provided script located at `/adapters/polymarket/scripts/set_allowances.py`.
102+
You can do this by running the provided script located at `nautilus_trader/adapters/polymarket/scripts/set_allowances.py`.
103103

104104
This script is adapted from a [gist](https://gist.github.com/poly-rodr/44313920481de58d5a3f6d1f8226bd5e) created by @poly-rodr.
105105

@@ -114,7 +114,7 @@ Polymarket CLOB Exchange to interact with your funds.
114114
Before running the script, ensure the following prerequisites are met:
115115

116116
- Install the web3 Python package: `uv pip install "web3==7.12.1"`.
117-
- Have a **Polygon**-compatible wallet funded with some MATIC (used for gas fees).
117+
- Have a **Polygon**-compatible wallet funded with some POL (used for gas fees).
118118
- Set the following environment variables in your shell:
119119
- `POLYGON_PRIVATE_KEY`: Your private key for the **Polygon**-compatible wallet.
120120
- `POLYGON_PUBLIC_KEY`: Your public key for the **Polygon**-compatible wallet.
@@ -150,7 +150,7 @@ The script performs the following actions:
150150
- Connects to the Polygon network via an RPC URL (<https://polygon-rpc.com/>).
151151
- Signs and sends a transaction to approve the maximum USDC allowance for Polymarket contracts.
152152
- Sets approval for the CTF contract to manage Conditional Tokens on your behalf.
153-
- Repeats the approval process for specific addresses like the Polymarket CLOB Exchange and Neg Risk Adapter.
153+
- Repeats the approval process for specific addresses like the Polymarket CLOB Exchange and Neg Risk adapter.
154154

155155
This allows Polymarket to interact with your funds when executing trades and ensures smooth integration with the CLOB Exchange.
156156

@@ -186,7 +186,7 @@ When setting up NautilusTrader to work with Polymarket, it’s crucial to proper
186186
**Key parameters**:
187187

188188
- `private_key`: The private key for your wallet used to sign orders. The interpretation depends on your `signature_type` configuration. If not explicitly provided in the configuration, it will automatically source the `POLYMARKET_PK` environment variable.
189-
- `funder`: The **USDC.e** wallet address used for funding trades. If not provided, will source the `POLYMARKET_FUNDER` environment variable.
189+
- `funder`: The **USDC.e** funding wallet address used for funding trades. If not provided, will source the `POLYMARKET_FUNDER` environment variable.
190190
- API credentials: You will need to provide the following API credentials to interact with the Polymarket CLOB:
191191
- `api_key`: If not provided, will source the `POLYMARKET_API_KEY` environment variable.
192192
- `api_secret`: If not provided, will source the `POLYMARKET_API_SECRET` environment variable.
@@ -271,11 +271,11 @@ FAK (Fill and Kill) is Polymarket's terminology for Immediate or Cancel (IOC) se
271271

272272
### Advanced order features
273273

274-
| Feature | Binary Options | Notes |
275-
|--------------------|----------------|-------------------------------------|
276-
| Order Modification | - | Cancellation functionality only. |
277-
| Bracket/OCO Orders | - | *Not supported by Polymarket*. |
278-
| Iceberg Orders | - | *Not supported by Polymarket*. |
274+
| Feature | Binary Options | Notes |
275+
|--------------------|----------------|------------------------------------|
276+
| Order modification | - | Cancellation functionality only. |
277+
| Bracket/OCO orders | - | *Not supported by Polymarket.* |
278+
| Iceberg orders | - | *Not supported by Polymarket.* |
279279

280280
### Batch operations
281281

@@ -287,21 +287,21 @@ FAK (Fill and Kill) is Polymarket's terminology for Immediate or Cancel (IOC) se
287287

288288
### Position management
289289

290-
| Feature | Binary Options | Notes |
291-
|--------------------|----------------|-------------------------------------|
292-
| Query positions || Contract balance-based positions. |
293-
| Position mode | - | Binary outcome positions only. |
294-
| Leverage control | - | No leverage available. |
295-
| Margin mode | - | No margin trading. |
290+
| Feature | Binary Options | Notes |
291+
|------------------|----------------|-----------------------------------|
292+
| Query positions || Contract balance-based positions. |
293+
| Position mode | - | Binary outcome positions only. |
294+
| Leverage control | - | No leverage available. |
295+
| Margin mode | - | No margin trading. |
296296

297297
### Order querying
298298

299-
| Feature | Binary Options | Notes |
300-
|----------------------|----------------|-----------------------------------|
301-
| Query open orders || Active orders only. |
302-
| Query order history || Limited historical data. |
303-
| Order status updates || Real-time order state changes. |
304-
| Trade history || Execution and fill reports. |
299+
| Feature | Binary Options | Notes |
300+
|----------------------|----------------|--------------------------------|
301+
| Query open orders || Active orders only. |
302+
| Query order history || Limited historical data. |
303+
| Order status updates || Real-time order state changes. |
304+
| Trade history || Execution and fill reports. |
305305

306306
### Contingent orders
307307

@@ -356,13 +356,28 @@ Once a trade is initially matched, subsequent trade status updates will be recei
356356
NautilusTrader records the initial trade details in the `info` field of the `OrderFilled` event,
357357
with additional trade events stored in the cache as JSON under a custom key to retain this information.
358358

359+
## Fees
360+
361+
Polymarket charges **zero fees** on most markets. The exception is **15-minute crypto markets**:
362+
363+
| Trade Type | Fee Deducted In | Rate Range |
364+
|------------|-----------------|-------------|
365+
| Buy | Tokens | 0.2% - 1.6% |
366+
| Sell | USDC | 0.8% - 3.7% |
367+
368+
Fees are rounded to 4 decimal places (0.0001 USDC minimum). Market makers receive daily rebates from collected taker fees.
369+
370+
:::note
371+
For the latest rates, see Polymarket's [Fees](https://docs.polymarket.com/polymarket-learn/trading/fees) and [Maker Rebates](https://docs.polymarket.com/polymarket-learn/trading/maker-rebates-program) documentation.
372+
:::
373+
359374
## Reconciliation
360375

361376
The Polymarket API returns either all **active** (open) orders or specific orders when queried by the
362377
Polymarket order ID (`venue_order_id`). The execution reconciliation procedure for Polymarket is as follows:
363378

364379
- Generate order reports for all instruments with active (open) orders, as reported by Polymarket.
365-
- Generate position reports from contract balances reported by Polymarket, *for instruments available in the cache*.
380+
- Generate position reports from contract balances reported by Polymarket, for instruments available in the cache.
366381
- Compare these reports with Nautilus execution state.
367382
- Generate missing orders to bring Nautilus execution state in line with positions reported by Polymarket.
368383

@@ -402,19 +417,19 @@ When you attempt to subscribe to 501 or more instruments on a single WebSocket c
402417
- You will **not** receive the initial order book snapshot for each instrument.
403418
- You will only receive subsequent order book updates.
404419

405-
To handle this limitation, NautilusTrader automatically manages WebSocket connections:
420+
NautilusTrader automatically manages WebSocket connections to handle this limitation:
406421

407-
- When the subscription count exceeds 500 instruments, the adapter **automatically creates additional WebSocket connections**.
422+
- When the subscription count exceeds 500 instruments, the adapter automatically creates additional WebSocket connections.
408423
- Each connection maintains up to 500 instrument subscriptions.
409-
- This protection ensures you receive complete order book data (including initial snapshots) for all subscribed instruments.
424+
- This ensures you receive complete order book data (including initial snapshots) for all subscribed instruments.
410425

411426
:::tip
412427
If you need to subscribe to a large number of instruments (e.g., 5000+), the adapter will automatically distribute these subscriptions across multiple WebSocket connections, with each connection handling up to 500 instruments.
413428
:::
414429

415430
## Limitations and considerations
416431

417-
The following limitations and considerations are currently known:
432+
The following limitations are currently known:
418433

419434
- Order signing via the Polymarket Python client is slow, taking around one second.
420435
- Post-only orders are not supported.
@@ -502,15 +517,13 @@ All data loader methods are **asynchronous** and must be called with `await`.
502517

503518
```python
504519
import asyncio
505-
from datetime import UTC, datetime, timedelta
506520

507521
from nautilus_trader.adapters.polymarket import PolymarketDataLoader
508522
from nautilus_trader.adapters.polymarket import parse_polymarket_instrument
509-
from nautilus_trader.core.datetime import millis_to_nanos
510523

511524
async def load_market_data():
512525
# Discovery methods are static - no instance needed
513-
market = await PolymarketDataLoader.find_market_by_slug("fed-rate-hike-in-2025")
526+
market = await PolymarketDataLoader.find_market_by_slug("gta-vi-released-before-june-2026")
514527
condition_id = market["conditionId"]
515528

516529
market_details = await PolymarketDataLoader.fetch_market_details(condition_id)
@@ -640,12 +653,12 @@ from nautilus_trader.model.objects import Money
640653

641654
async def run_backtest():
642655
# Initialize loader and fetch market data
643-
loader = await PolymarketDataLoader.from_market_slug("fed-rate-hike-in-2025")
656+
loader = await PolymarketDataLoader.from_market_slug("gta-vi-released-before-june-2026")
644657
instrument = loader.instrument
645658

646-
# Fetch historical data
647-
start = pd.Timestamp("2025-10-30", tz="UTC")
648-
end = pd.Timestamp("2025-10-31", tz="UTC")
659+
# Fetch historical data (last 24 hours)
660+
end = pd.Timestamp.now(tz="UTC")
661+
start = end - pd.Timedelta(hours=24)
649662

650663
deltas = await loader.load_orderbook_snapshots(
651664
start=start,
@@ -705,7 +718,7 @@ from nautilus_trader.adapters.polymarket import get_polymarket_instrument_id
705718

706719
# Create NautilusTrader InstrumentId from Polymarket identifiers
707720
instrument_id = get_polymarket_instrument_id(
708-
condition_id="0x4319532e181605cb15b1bd677759a3bc7f7394b2fdf145195b700eeaedfd5221",
709-
token_id="60487116984468020978247225474488676749601001829886755968952521846780452448915"
721+
condition_id="0xcccb7e7613a087c132b69cbf3a02bece3fdcb824c1da54ae79acc8d4a562d902",
722+
token_id="8441400852834915183759801017793514978104486628517653995211751018945988243154"
710723
)
711724
```

examples/backtest/polymarket_simple_quoter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
# python nautilus_trader/adapters/polymarket/scripts/active_markets.py
5353
# To find BTC/ETH UpDown markets specifically, run:
5454
# python nautilus_trader/adapters/polymarket/scripts/list_updown_markets.py
55-
MARKET_SLUG = "fed-rate-hike-in-2025"
55+
MARKET_SLUG = "gta-vi-released-before-june-2026"
5656

5757

5858
async def run_backtest(

examples/live/polymarket/polymarket_data_tester.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333

3434
# To find active markets run `python nautilus_trader/adapters/polymarket/scripts/active_markets.py`
3535

36-
# Slug: fed-rate-hike-in-2025
36+
# Slug: gta-vi-released-before-june-2026
3737
# Active: True
38-
# Condition ID: 0x4319532e181605cb15b1bd677759a3bc7f7394b2fdf145195b700eeaedfd5221
39-
# Token IDs: 60487116984468020978247225474488676749601001829886755968952521846780452448915,
40-
# 81104637750588840860328515305303028259865221573278091453716127842023614249200
41-
# Link: https://polymarket.com/event/fed-rate-hike-in-2025
42-
condition_id = "0x4319532e181605cb15b1bd677759a3bc7f7394b2fdf145195b700eeaedfd5221"
43-
token_id = "60487116984468020978247225474488676749601001829886755968952521846780452448915"
38+
# Condition ID: 0xcccb7e7613a087c132b69cbf3a02bece3fdcb824c1da54ae79acc8d4a562d902
39+
# Token IDs: 8441400852834915183759801017793514978104486628517653995211751018945988243154,
40+
# 109289569086508934142323222102974769075074494425163878721602922903101062859033
41+
# Link: https://polymarket.com/event/gta-vi-released-before-june-2026
42+
condition_id = "0xcccb7e7613a087c132b69cbf3a02bece3fdcb824c1da54ae79acc8d4a562d902"
43+
token_id = "8441400852834915183759801017793514978104486628517653995211751018945988243154"
4444

4545
instrument_ids = [
4646
get_polymarket_instrument_id(condition_id, token_id),
@@ -84,10 +84,11 @@
8484
# Configure and initialize the tester
8585
config_tester = DataTesterConfig(
8686
instrument_ids=instrument_ids,
87-
subscribe_book_deltas=False,
87+
# subscribe_book_deltas=True,
8888
subscribe_book_at_interval=True,
89-
subscribe_quotes=True,
90-
subscribe_trades=True,
89+
# subscribe_quotes=True,
90+
# subscribe_trades=True,
91+
book_interval_ms=10,
9192
can_unsubscribe=False, # Polymarket does not support unsubscribing from ws streams
9293
)
9394
tester = DataTester(config=config_tester)

examples/live/polymarket/polymarket_exec_tester.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@
4545

4646
# To find active markets run `python nautilus_trader/adapters/polymarket/scripts/active_markets.py`
4747

48-
# Slug: fed-rate-hike-in-2025
48+
# Slug: gta-vi-released-before-june-2026
4949
# Active: True
50-
# Condition ID: 0x4319532e181605cb15b1bd677759a3bc7f7394b2fdf145195b700eeaedfd5221
51-
# Token IDs: 60487116984468020978247225474488676749601001829886755968952521846780452448915,
52-
# 81104637750588840860328515305303028259865221573278091453716127842023614249200
53-
# Link: https://polymarket.com/event/fed-rate-hike-in-2025
54-
condition_id = "0x4319532e181605cb15b1bd677759a3bc7f7394b2fdf145195b700eeaedfd5221"
55-
token_id = "60487116984468020978247225474488676749601001829886755968952521846780452448915"
50+
# Condition ID: 0xcccb7e7613a087c132b69cbf3a02bece3fdcb824c1da54ae79acc8d4a562d902
51+
# Token IDs: 8441400852834915183759801017793514978104486628517653995211751018945988243154,
52+
# 109289569086508934142323222102974769075074494425163878721602922903101062859033
53+
# Link: https://polymarket.com/event/gta-vi-released-before-june-2026
54+
condition_id = "0xcccb7e7613a087c132b69cbf3a02bece3fdcb824c1da54ae79acc8d4a562d902"
55+
token_id = "8441400852834915183759801017793514978104486628517653995211751018945988243154"
5656

5757
instrument_id = get_polymarket_instrument_id(condition_id, token_id)
5858

nautilus_trader/adapters/polymarket/common/parsing.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from nautilus_trader.adapters.polymarket.common.symbol import get_polymarket_instrument_id
2727
from nautilus_trader.adapters.polymarket.common.symbol import get_polymarket_token_id
2828
from nautilus_trader.adapters.polymarket.schemas.book import PolymarketTickSizeChange
29+
from nautilus_trader.core.stats import basis_points_as_percentage
2930
from nautilus_trader.model.currencies import USDC_POS
3031
from nautilus_trader.model.enums import AssetClass
3132
from nautilus_trader.model.enums import LiquiditySide
@@ -209,3 +210,32 @@ def update_instrument(
209210
ts_init=ts_init,
210211
info=instrument.info,
211212
)
213+
214+
215+
def calculate_commission(
216+
quantity: Decimal,
217+
price: Decimal,
218+
fee_rate_bps: Decimal,
219+
) -> float:
220+
"""
221+
Calculate commission from trade parameters and fee rate.
222+
223+
Polymarket rounds fees to 4 decimal places (0.0001 USDC minimum).
224+
225+
Parameters
226+
----------
227+
quantity : Decimal
228+
The fill quantity.
229+
price : Decimal
230+
The fill price.
231+
fee_rate_bps : Decimal
232+
The fee rate in basis points.
233+
234+
Returns
235+
-------
236+
float
237+
The commission amount rounded to 4 decimal places.
238+
239+
"""
240+
commission = float(quantity * price) * basis_points_as_percentage(fee_rate_bps)
241+
return round(commission, 4)

nautilus_trader/adapters/polymarket/execution.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from nautilus_trader.adapters.polymarket.common.credentials import PolymarketWebSocketAuth
4141
from nautilus_trader.adapters.polymarket.common.enums import PolymarketEventType
4242
from nautilus_trader.adapters.polymarket.common.enums import PolymarketTradeStatus
43+
from nautilus_trader.adapters.polymarket.common.parsing import calculate_commission
4344
from nautilus_trader.adapters.polymarket.common.parsing import validate_ethereum_address
4445
from nautilus_trader.adapters.polymarket.common.symbol import get_polymarket_condition_id
4546
from nautilus_trader.adapters.polymarket.common.symbol import get_polymarket_instrument_id
@@ -66,7 +67,6 @@
6667
from nautilus_trader.core.datetime import secs_to_nanos
6768
from nautilus_trader.core.nautilus_pyo3 import HttpClient
6869
from nautilus_trader.core.nautilus_pyo3 import HttpResponse
69-
from nautilus_trader.core.stats import basis_points_as_percentage
7070
from nautilus_trader.core.uuid import UUID4
7171
from nautilus_trader.execution.messages import BatchCancelOrders
7272
from nautilus_trader.execution.messages import CancelAllOrders
@@ -1468,9 +1468,7 @@ def _handle_user_trade_in_ws_trade_msg(
14681468

14691469
last_qty = instrument.make_qty(msg.last_qty(order_id))
14701470
last_px = instrument.make_price(msg.last_px(order_id))
1471-
commission = float(last_qty * last_px) * basis_points_as_percentage(
1472-
float(msg.get_fee_rate_bps(order_id)),
1473-
)
1471+
commission = calculate_commission(last_qty, last_px, msg.get_fee_rate_bps(order_id))
14741472
ts_event = secs_to_nanos(int(msg.match_time))
14751473

14761474
self.generate_order_filled(

nautilus_trader/adapters/polymarket/schemas/trade.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020

2121
from nautilus_trader.adapters.polymarket.common.enums import PolymarketLiquiditySide
2222
from nautilus_trader.adapters.polymarket.common.enums import PolymarketOrderSide
23+
from nautilus_trader.adapters.polymarket.common.parsing import calculate_commission
2324
from nautilus_trader.adapters.polymarket.common.parsing import determine_order_side
2425
from nautilus_trader.adapters.polymarket.schemas.user import PolymarketMakerOrder
2526
from nautilus_trader.core.datetime import secs_to_nanos
26-
from nautilus_trader.core.stats import basis_points_as_percentage
2727
from nautilus_trader.core.uuid import UUID4
2828
from nautilus_trader.execution.reports import FillReport
2929
from nautilus_trader.model.currencies import USDC_POS
@@ -156,7 +156,7 @@ def parse_to_fill_report(
156156
last_qty = instrument.make_qty(self.last_qty(filled_user_order_id))
157157
last_px = instrument.make_price(self.last_px(filled_user_order_id))
158158
fee_rate_bps = self.get_fee_rate_bps(filled_user_order_id)
159-
commission = float(last_qty * last_px) * basis_points_as_percentage(fee_rate_bps)
159+
commission = calculate_commission(last_qty, last_px, fee_rate_bps)
160160

161161
return FillReport(
162162
account_id=account_id,

0 commit comments

Comments
 (0)