diff --git a/bot.py b/bot.py index ea9aebf..3b85cc8 100644 --- a/bot.py +++ b/bot.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -PolyBot v13 — Recalibrated + Safety Systems +PolyBot v14 — CLOBv2 + Safety Systems Strategy: - Brownian motion model with vol=0.12 (recalibrated from 0.08) @@ -164,7 +164,7 @@ def start(self): mp = self.strategy_config.min_prob me = self.strategy_config.min_edge print("=" * 55) - print(f" PolyBot v13 — Recalibrated (vol=0.12)") + print(f" PolyBot v14 — CLOBv2 (vol=0.12)") print(f" Mode: {'DRY RUN' if self.dry_run else '🔴 LIVE TRADING'}") print(f" Kelly: {kf*100:.0f}% fraction | " f"Bets: ${self.strategy_config.min_bet:.0f}–${self.strategy_config.max_bet:.0f}") @@ -1097,6 +1097,7 @@ def _handle_shutdown(self, signum, frame): self.price_feed.stop() if self.executor._initialized: self.executor.cancel_all() + self.executor.shutdown() # Final real balance sync if not self.dry_run and self.executor._initialized: diff --git a/executor.py b/executor.py index 10cb9d3..a015f80 100644 --- a/executor.py +++ b/executor.py @@ -12,19 +12,22 @@ the bot detects them via balance sync at the next window boundary. """ +import os +import threading import time from dataclasses import dataclass from typing import Optional -from py_clob_client.client import ClobClient -from py_clob_client.clob_types import ( +from py_clob_client_v2.client import ClobClient +from py_clob_client_v2.clob_types import ( OrderArgs, MarketOrderArgs, OrderType, BalanceAllowanceParams, AssetType, + OrderPayload, ) -from py_clob_client.constants import POLYGON +from py_clob_client_v2.constants import POLYGON FILLED = "FILLED" @@ -82,33 +85,58 @@ def __init__(self, private_key: str, safe_address: str = "", dry_run: bool = Tru self.safe_address = safe_address self.client: Optional[ClobClient] = None self._initialized = False + self._heartbeat_stop = threading.Event() + self._heartbeat_thread: Optional[threading.Thread] = None def initialize(self) -> bool: try: + host = os.getenv("CLOB_HOST", "https://clob.polymarket.com") self.client = ClobClient( - host="https://clob.polymarket.com", + host=host, key=self.private_key, chain_id=POLYGON, funder=self.safe_address if self.safe_address else None, signature_type=2 if self.safe_address else 0, ) - self.client.set_api_creds(self.client.create_or_derive_api_creds()) + self.client.set_api_creds(self.client.create_or_derive_api_key()) self._initialized = True print(f"[executor] Initialized ({'DRY RUN' if self.dry_run else 'LIVE'})") print(f"[executor] Max buy price: ${MAX_BUY_PRICE:.2f}") print(f"[executor] Address: {self.client.get_address()}") + if not self.dry_run: + self._start_heartbeat() return True except Exception as e: print(f"[executor] Init failed: {e}") return False + def _start_heartbeat(self): + """Send heartbeat every 8s — CLOB v2 cancels GTC orders after 10s without one.""" + def _run(): + while not self._heartbeat_stop.is_set(): + try: + self.client.post_heartbeat() + except Exception as e: + print(f"[executor] Heartbeat failed: {e}") + self._heartbeat_stop.wait(8) + self._heartbeat_thread = threading.Thread(target=_run, daemon=True) + self._heartbeat_thread.start() + print("[executor] Heartbeat started (8s interval)") + + def shutdown(self): + """Stop heartbeat thread cleanly.""" + self._heartbeat_stop.set() + if self._heartbeat_thread: + self._heartbeat_thread.join(timeout=5) + def get_balance(self) -> float: if not self._initialized: return 0.0 try: params = BalanceAllowanceParams(asset_type=AssetType.COLLATERAL) bal = self.client.get_balance_allowance(params) - return float(bal.get("balance", 0)) / 1e6 + balance_raw = bal.get("balance") or bal.get("pUSD") or 0 + return float(balance_raw) / 1e6 except Exception as e: print(f"[executor] Balance check failed: {e}") return 0.0 @@ -228,7 +256,7 @@ def buy(self, token_id: str, amount_usd: float, price: float = 0.0) -> OrderResu signed_order = self.client.create_order(order_args) result = self.client.post_order(signed_order, OrderType.GTC) - order_id = result.get("orderID", "") + order_id = result.get("orderID") or result.get("transactionID", "") if not order_id: return OrderResult( success=False, status=REJECTED, @@ -388,7 +416,7 @@ def sell(self, token_id: str, shares: float, price: float = 0.0) -> OrderResult: signed_order = self.client.create_market_order(order_args) result = self.client.post_order(signed_order, OrderType.GTC) - order_id = result.get("orderID", "") + order_id = result.get("orderID") or result.get("transactionID", "") # Wait for settlement time.sleep(2) @@ -489,7 +517,7 @@ def cancel_order(self, order_id: str) -> bool: if self.dry_run or not self._initialized: return True try: - self.client.cancel(order_id=order_id) + self.client.cancel_order(OrderPayload(orderID=order_id)) return True except Exception as e: print(f"[executor] Cancel failed: {e}") diff --git a/requirements.txt b/requirements.txt index b93110c..e5d58e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -py-clob-client>=0.34.5 +py-clob-client-v2>=1.0.0 python-dotenv>=1.0.0 websockets>=12.0 aiohttp>=3.9.0