#!/usr/bin/env python3 """ NautilusTrader Live Runner for HK Weather Prediction Market Strategy. Usage: # Paper trading (real market data, simulated execution) python -m execution.runner --paper # Live trading with real Polymarket CLOB python -m execution.runner --live """ import argparse import asyncio import signal import sys from datetime import datetime sys.path.insert(0, "/home/satoshi/hk-weather-mkt") from dotenv import load_dotenv load_dotenv() from nautilus_trader.adapters.polymarket.factories import ( PolymarketLiveDataClientFactory, PolymarketLiveExecClientFactory, ) from nautilus_trader.config import ( TradingNodeConfig, ImportableStrategyConfig, LiveDataEngineConfig, LiveExecEngineConfig, ) from nautilus_trader.live.node import TradingNode from nautilus_trader.model.identifiers import TraderId from execution import WeatherMarketConfig, build_data_client_config, build_exec_client_config, build_node_config from execution.strategy import PolymarketWeatherStrategyConfig class WeatherMarketRunner: def __init__(self, cfg: WeatherMarketConfig, live: bool = False): self.cfg = cfg self.live = live self.node: TradingNode | None = None self._running = False async def run(self): mode = "LIVE" if self.live else "PAPER" print("=" * 60) print(f" HK Weather Prediction Market — {mode} TRADING") print(f" Bankroll: ${self.cfg.bankroll_pusd:.2f} pUSD") print(f" Min edge: {self.cfg.min_edge_bps} bps") print(f" Kelly fraction: {self.cfg.kelly_fraction}") if self.live: print(f" Funder: {self.cfg.funder or '(env)'}") print(" ⚠ REAL FUNDS WILL BE USED ⚠") print("=" * 60) if self.live and not self.cfg.private_key: print("\nERROR: POLYMARKET_PK not set. Cannot trade live.") print("Use --paper for paper trading.\n") return data_config = build_data_client_config(self.cfg) exec_config = build_exec_client_config(self.cfg) if self.live else data_config node_config = build_node_config(self.cfg) strategy_config = ImportableStrategyConfig( strategy_path="execution.strategy:PolymarketWeatherStrategy", config_path="execution.strategy:PolymarketWeatherStrategyConfig", config=PolymarketWeatherStrategyConfig( bankroll_pusd=self.cfg.bankroll_pusd, min_edge_bps=self.cfg.min_edge_bps, max_position_per_market_pusd=self.cfg.max_position_per_market_pusd, kelly_fraction=self.cfg.kelly_fraction, forecast_interval_mins=self.cfg.forecast_interval_mins, search_tags=self.cfg.search_tags, min_liquidity_usdc=self.cfg.min_liquidity_usdc, ), ) self.node = TradingNode( config=TradingNodeConfig( trader_id=TraderId("HKWEATHER-001"), data_clients={ "POLYMARKET": (PolymarketLiveDataClientFactory, data_config), }, exec_clients={ "POLYMARKET": (PolymarketLiveExecClientFactory, exec_config), }, strategies=[strategy_config], timeout_disconnection=node_config.timeout_disconnection, timeout_post_stop=node_config.timeout_post_stop, timeout_reconciliation=30.0, ) ) self._setup_signals() self._running = True try: print(f"\nStarting {mode.lower()} trading node...") print("Press Ctrl+C to stop\n") await self.node.start() while self._running: await asyncio.sleep(1) except asyncio.CancelledError: pass finally: await self._cleanup() def _setup_signals(self): loop = asyncio.get_event_loop() def shutdown(sig, frame): print(f"\nReceived signal {sig}, shutting down...") self._running = False if self.node: asyncio.create_task(self._stop_node()) for sig in (signal.SIGINT, signal.SIGTERM): try: loop.add_signal_handler(sig, lambda s=sig: shutdown(s, None)) except NotImplementedError: signal.signal(sig, lambda s, f: shutdown(s, f)) async def _stop_node(self): try: if self.node: await self.node.stop() except Exception as e: print(f"Error during shutdown: {e}") async def _cleanup(self): self._running = False print(f"\n[{datetime.now():%H:%M:%S}] Runner stopped.") def main(): parser = argparse.ArgumentParser(description="HK Weather Prediction Market — NautilusTrader Runner") parser.add_argument("--paper", action="store_true", default=True, help="Paper trading mode [default]") parser.add_argument("--live", action="store_true", help="Live trading on Polymarket CLOB") parser.add_argument("--bankroll", type=float, default=None) parser.add_argument("--edge", type=int, default=None) parser.add_argument("--kelly", type=float, default=None) args = parser.parse_args() cfg = WeatherMarketConfig.from_env() if args.bankroll: cfg.bankroll_pusd = args.bankroll if args.edge: cfg.min_edge_bps = args.edge if args.kelly: cfg.kelly_fraction = args.kelly runner = WeatherMarketRunner(cfg, live=args.live) asyncio.run(runner.run()) if __name__ == "__main__": main()