Files
ramseshk 533939d178 Add NautilusTrader Polymarket execution layer
- Full NautilusTrader integration using BinaryOption instruments
- Polymarket CLOB data client (L2 order book, WebSocket deltas)
- Polymarket CLOB execution client (limit orders, market orders, batch ops)
- PolymarketWeatherStrategy with auto market discovery, order book subscription,
  weather model signal generation, Kelly sizing, and order placement
- Proper Polymarket precision: tick sizes, GTC/GTD limit orders, FAK/IOC market orders
- Weather category fee model (0.05% taker, 25% maker rebate)
- Paper trading mode (real market data, simulated execution)
- Live trading mode with PK/funder/env credential support
- 30s disconnection timeout + 30s post-stop delay per Polymarket docs

Run: python -m execution.runner --paper
2026-08-10 17:29:23 +08:00

114 lines
4.3 KiB
Python

"""NautilusTrader Polymarket execution layer for HK weather prediction markets.
Key characteristics per Polymarket + Nautilus docs:
- BinaryOption instruments (outcome tokens, pay 0 or 1 at resolution)
- pUSD collateral, 6 decimals, no leverage
- Tick sizes: 0.001 to 0.1 (dictates price + size precision)
- Market orders: FAK (IOC) or FOK only; GTC/GTD for resting limits
- Market BUY must use quote_quantity=True (pUSD notional)
- SELL quantities truncated to 2 decimal places
- Weather category: 0.05% taker fee + 25% maker rebate
- Recommended: 30s disconnection timeout + 30s post-stop delay
"""
import os
from dataclasses import dataclass
from typing import Optional
from dotenv import load_dotenv
load_dotenv()
from nautilus_trader.adapters.polymarket.common.constants import POLYMARKET_VENUE
from nautilus_trader.adapters.polymarket.config import PolymarketDataClientConfig, PolymarketExecClientConfig
from nautilus_trader.common.config import InstrumentProviderConfig
from nautilus_trader.config import TradingNodeConfig
@dataclass
class WeatherMarketConfig:
"""Configuration for the weather prediction market strategy."""
# Credentials
private_key: str = ""
funder: str = ""
api_key: str = ""
api_secret: str = ""
passphrase: str = ""
signature_type: int = 0 # 0=EOA, 3=DepositWallet
# Strategy
bankroll_pusd: float = 1000.0
min_edge_bps: int = 200
max_position_per_market_pusd: float = 500.0
kelly_fraction: float = 0.25
forecast_interval_mins: int = 360 # 6h
# Market discovery
search_tags: tuple = ("weather", "temperature", "hong kong", "typhoon", "precipitation", "climate")
min_liquidity_usdc: float = 100.0
# Risk
disconnect_timeout_secs: int = 30
post_stop_delay_secs: int = 30
@classmethod
def from_env(cls) -> "WeatherMarketConfig":
return cls(
private_key=os.getenv("POLYMARKET_PK", ""),
funder=os.getenv("POLYMARKET_FUNDER", ""),
api_key=os.getenv("POLYMARKET_API_KEY", ""),
api_secret=os.getenv("POLYMARKET_API_SECRET", ""),
passphrase=os.getenv("POLYMARKET_PASSPHRASE", ""),
signature_type=int(os.getenv("POLYMARKET_SIGNATURE_TYPE", "0")),
bankroll_pusd=float(os.getenv("BANKROLL_PUSD", "1000.0")),
min_edge_bps=int(os.getenv("MIN_EDGE_BPS", "200")),
kelly_fraction=float(os.getenv("KELLY_FRACTION", "0.25")),
)
def _env_or_none(key: str) -> str | None:
"""Get env var or None if not set."""
val = os.getenv(key, "")
return val if val else None
def build_data_client_config(cfg: WeatherMarketConfig) -> PolymarketDataClientConfig:
"""Build Polymarket market data client configuration."""
return PolymarketDataClientConfig(
venue=POLYMARKET_VENUE,
private_key=cfg.private_key or _env_or_none("POLYMARKET_PK"),
funder=cfg.funder or _env_or_none("POLYMARKET_FUNDER"),
api_key=cfg.api_key or _env_or_none("POLYMARKET_API_KEY"),
api_secret=cfg.api_secret or _env_or_none("POLYMARKET_API_SECRET"),
passphrase=cfg.passphrase or _env_or_none("POLYMARKET_PASSPHRASE"),
signature_type=cfg.signature_type,
instrument_provider=InstrumentProviderConfig(load_ids=[]),
update_instrument_interval_mins=30,
)
def build_exec_client_config(cfg: WeatherMarketConfig) -> PolymarketExecClientConfig:
"""Build Polymarket execution client configuration."""
return PolymarketExecClientConfig(
venue=POLYMARKET_VENUE,
private_key=cfg.private_key or _env_or_none("POLYMARKET_PK"),
funder=cfg.funder or _env_or_none("POLYMARKET_FUNDER"),
api_key=cfg.api_key or _env_or_none("POLYMARKET_API_KEY"),
api_secret=cfg.api_secret or _env_or_none("POLYMARKET_API_SECRET"),
passphrase=cfg.passphrase or _env_or_none("POLYMARKET_PASSPHRASE"),
signature_type=cfg.signature_type,
max_retries=3,
retry_delay=1.0,
instrument_provider=InstrumentProviderConfig(load_ids=[]),
)
def build_node_config(cfg: WeatherMarketConfig) -> TradingNodeConfig:
"""Build TradingNode configuration for Polymarket + weather strategy."""
return TradingNodeConfig(
timeout_disconnection=cfg.disconnect_timeout_secs,
timeout_post_stop=cfg.post_stop_delay_secs,
timeout_reconciliation=30.0,
)