Skip to main content
nautilus_paramx connects Nautilus Trader 2.0 to ParamX. Tiles become Nautilus instruments. The data client streams their books, quotes and trades, and the execution client signs orders with your trading key. The same strategy code runs:
  • paper, in Nautilus’s sandbox, against ParamX’s live books;
  • live on devnet, the paper-trading exchange with test USDC;
  • live on mainnet.
It’s a Python package on Nautilus’s out-of-tree adapter interface (nautilus_trader.live.clients), and needs nautilus_trader>=2.0.0rc5 and Python 3.12+.

Install

From a checkout of nautilus-paramx:

An account

Orders need an account whose trading key is registered. paramx-setup, this repo’s CLI, makes one:
The execution client reads the key file paramx-setup writes:
  • .paramx/keys.json on mainnet;
  • .paramx/devnet-keys.json on devnet;
  • or any path, with keys_file.
Market data needs no account.

Instruments

Tiles. Each grid tile is a BinaryOption:
  • Id: BTC-5M-20260927T2300-109000-109100.PARAMX, meaning the underlying, the grid, the window’s UTC start, and the min and max strike in USD.
  • raw_symbol: the decimal contract id.
  • Units: USDC; prices from 0 to 1, and sizes, both to 6 decimals.
  • Timing: activation_ns and expiration_ns are the window.
  • Fees: taker_fee 0.003 and maker_fee 0.
  • info: contract_id, underlying_id, grid, the strikes, and start/end in Unix seconds.
A share pays $1 if the underlying trades inside the range during the window (instruments). Underlyings. Each underlying is an IndexInstrument, BTC.PARAMX. Its info holds its grids (window length and price step), so a strategy can build tile ids itself (see nautilus_paramx.tiles and examples/mm_strategy.py). Which tiles load is set in ParamxDataClientConfig: Loading and rolling:
  • Any tile works. Subscribing to, or requesting, a tile outside the loaded set builds it from its id.
  • Rolling: every roll_interval_secs, the data client publishes the tiles of windows that have come into range.
  • Settlement: a tile you subscribe to with subscribe_instrument_close gets an InstrumentClose once it settles, priced 1 if touched and 0 if not.
The market maker quotes upcoming windows: 5m tiles up to about an hour ahead, and the next hours and days. The current window’s book is often empty.

Data

The WebSocket client:
  • reconnects with backoff, including on the server’s restart close (1012);
  • resubscribes everything;
  • reconnects if a message’s seq skips.

Execution

ParamxExecClientConfig(environment=..., keys_file=..., default_expiry_secs=3600, account_id="PARAMX-001"): a MARGIN account in USDC, with NETTING positions. Placing orders:
  • Totals: a limit’s price is per share, so the order’s totals are rounded to micro-USDC: a buy’s total down (it never pays more than its price), a sell’s up.
  • Expiry: without GTD, an order expires at its tile’s end or after default_expiry_secs, whichever is sooner.
  • Idempotent submits: the client order id is sent to ParamX, so a retried submit can’t place twice.
  • Shorts: selling past zero opens a short, which locks $1 a share. Both sides of one tile can be quoted from flat.
Cancels:
  • cancel_order signs a cancel with the order’s own nonce and expiry.
  • cancel_all_orders uses the exchange’s cancel-all, one signature for the whole tile.
  • cancel_orders uses cancel-batch.
  • A cancel takes the order off the book at once. Its signed intent stays valid until its expiry, but only the operator can submit fills, and only from the book. Short expiries keep that window small.
Batching. The exchange allows 300 requests a minute per IP. Submits and cancels that arrive within 25 ms are sent as one batch request (/orders/batch, up to 20 orders; /orders/cancel-batch, up to 200), so a requote of many orders costs a few requests. Events come from the account’s WebSocket channel:
  • Fills carry the liquidity side and the commission (0.30% of the total, for the taker only).
  • Cancels and expiries, including an IOC’s dropped remainder.
  • Cash, reported as the account state.
Restarts. With reconciliation on (.with_reconciliation(reconciliation=True)), a new node rebuilds its state from the exchange:
  • resting orders, from /v1/accounts/{a}/orders, which also returns what a cancel needs to sign;
  • positions, from /positions;
  • fills, from /fills.
Orders found this way belong to Nautilus’s EXTERNAL strategy. Cancel them with cancel_all_orders(tile, strategy_only=False), or claim their instruments with StrategyConfig(external_order_claims=[...]).

Running it

Pass the factory classes themselves, not instances. For paper trading, keep the data client and use add_simulated_exec_client(PARAMX, SandboxExecutionClientFactory(), SandboxExecutionClientConfig(venue=Venue.from_str(PARAMX), account_type=AccountType.MARGIN, ...)) (see examples/paper_mm.py).

Examples

Limits and differences

  • Tiles expire. A strategy has to move to new windows as time passes, like mm_strategy.py’s roll.
  • Settlement doesn’t close Nautilus positions. A settled tile pays out on the exchange, and the new cash arrives as an account update, but the position stays open in Nautilus. Use InstrumentClose to close your own records; the next restart’s reconciliation matches the exchange.
  • Big tiles (the web app’s multi-tile contracts) aren’t mapped.
  • Market hours: tiles entirely outside an underlying’s sessions can’t be traded, and aren’t loaded unless you ask for them by id.
  • Nautilus 2.0 Python API:
    • Strategy.cancel_order and modify_order take a client order id, and subscribe_book_deltas needs a BookType;
    • order lists come only from brackets, which ParamX doesn’t support;
    • use node.handle().stop() to stop a node from another thread.