533939d178
- 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
114 lines
4.3 KiB
Python
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,
|
|
)
|