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,160 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user