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
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""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,
|
||||
)
|
||||
Reference in New Issue
Block a user