#!/usr/bin/env python3 """ Polymarket Autopilot — Paper trading with TAIL/BONDING/SPREAD strategies. Fetches live Polymarket data, runs strategy signals, manages a paper portfolio, and generates daily summaries. Inspired by MoonDev's approach. Usage: python3 polymarket-autopilot.py scan # Scan markets for opportunities python3 polymarket-autopilot.py trade # Run strategies & execute paper trades python3 polymarket-autopilot.py portfolio # Show current paper portfolio python3 polymarket-autopilot.py summary # Daily performance summary python3 polymarket-autopilot.py cron # Full cycle (scan + trade + log) Strategies: TAIL — Buy extreme tail events (< 10% probability) for asymmetric upside SPREAD — Exploit price gaps between related markets MOMENTUM — Ride probability momentum (big moves in last 24h) """ import json import sys import time from datetime import datetime, timezone from pathlib import Path from urllib.request import urlopen, Request from urllib.error import URLError, HTTPError DATA_DIR = Path(__file__).parent.parent / "data" / "polymarket" DATA_DIR.mkdir(parents=True, exist_ok=True) PORTFOLIO_FILE = DATA_DIR / "portfolio.json" TRADES_FILE = DATA_DIR / "trades.jsonl" CONFIG_FILE = DATA_DIR / "config.json" POLYMARKET_API = "https://gamma-api.polymarket.com" DEFAULT_CONFIG = { "starting_balance": 10000, "max_position_pct": 5, # Max 5% of portfolio per trade "tail_threshold": 0.10, # Buy events under 10% probability "momentum_threshold": 0.15, # 15% probability change = momentum signal "min_volume": 10000, # Minimum market volume "strategies_enabled": ["tail", "momentum"], } def load_config(): if CONFIG_FILE.exists(): return json.loads(CONFIG_FILE.read_text()) CONFIG_FILE.write_text(json.dumps(DEFAULT_CONFIG, indent=2)) return DEFAULT_CONFIG def load_portfolio(): if PORTFOLIO_FILE.exists(): return json.loads(PORTFOLIO_FILE.read_text()) portfolio = { "balance": load_config()["starting_balance"], "positions": [], "total_trades": 0, "pnl": 0, "created": datetime.now().isoformat(), } save_portfolio(portfolio) return portfolio def save_portfolio(portfolio): portfolio["updated"] = datetime.now().isoformat() PORTFOLIO_FILE.write_text(json.dumps(portfolio, indent=2)) def log_trade(trade): with open(TRADES_FILE, "a") as f: f.write(json.dumps(trade) + "\n") def fetch_json(url): req = Request(url, headers={'User-Agent': 'polymarket-autopilot/1.0'}) try: with urlopen(req, timeout=15) as resp: return json.loads(resp.read().decode('utf-8')) except (HTTPError, URLError) as e: print(f" ❌ API error: {e}") return None def fetch_markets(limit=50, active=True): """Fetch markets from Polymarket CLOB API.""" url = f"{POLYMARKET_API}/markets?limit={limit}&active={str(active).lower()}&order=volume&ascending=false" data = fetch_json(url) if not data: return [] markets = [] for m in (data if isinstance(data, list) else data.get("data", data.get("markets", []))): if isinstance(m, dict): markets.append({ "id": m.get("id", m.get("condition_id", "")), "question": m.get("question", m.get("title", "")), "volume": float(m.get("volume", m.get("volumeNum", 0)) or 0), "liquidity": float(m.get("liquidity", 0) or 0), "outcomes": m.get("outcomes", []), "outcomePrices": m.get("outcomePrices", m.get("outcome_prices", [])), "end_date": m.get("end_date_iso", m.get("endDate", "")), "active": m.get("active", True), "slug": m.get("slug", ""), }) return markets def parse_prices(market): """Parse outcome prices into float list.""" prices = market.get("outcomePrices", []) if isinstance(prices, str): try: prices = json.loads(prices) except: prices = [] return [float(p) for p in prices if p] def strategy_tail(markets, config): """TAIL: Find extreme low-probability events for asymmetric bets.""" signals = [] threshold = config.get("tail_threshold", 0.10) for m in markets: prices = parse_prices(m) outcomes = m.get("outcomes", []) if isinstance(outcomes, str): try: outcomes = json.loads(outcomes) except: outcomes = [] for i, price in enumerate(prices): if 0.01 < price < threshold and m["volume"] > config.get("min_volume", 10000): outcome_name = outcomes[i] if i < len(outcomes) else f"Outcome {i}" signals.append({ "strategy": "TAIL", "market": m["question"][:80], "outcome": outcome_name, "price": price, "potential_return": f"{(1/price - 1)*100:.0f}%", "volume": m["volume"], "market_id": m["id"], "action": "BUY", }) return sorted(signals, key=lambda x: x["price"])[:10] def strategy_momentum(markets, config): """MOMENTUM: Find markets with big recent probability moves.""" # Note: Would need historical price data for true momentum. # For now, flag high-volume active markets near decision points. signals = [] for m in markets: prices = parse_prices(m) outcomes = m.get("outcomes", []) if isinstance(outcomes, str): try: outcomes = json.loads(outcomes) except: outcomes = [] for i, price in enumerate(prices): # Markets near tipping points (40-60%) with high volume if 0.40 < price < 0.60 and m["volume"] > config.get("min_volume", 10000) * 5: outcome_name = outcomes[i] if i < len(outcomes) else f"Outcome {i}" signals.append({ "strategy": "MOMENTUM", "market": m["question"][:80], "outcome": outcome_name, "price": price, "volume": m["volume"], "market_id": m["id"], "action": "WATCH", }) return sorted(signals, key=lambda x: x["volume"], reverse=True)[:10] def scan_markets(): config = load_config() print(f"\n🔍 Polymarket Scanner — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("=" * 60) print(" Fetching markets...") markets = fetch_markets(limit=100) if not markets: print(" ❌ Could not fetch markets") return [] print(f" 📊 Found {len(markets)} active markets") all_signals = [] if "tail" in config.get("strategies_enabled", []): print("\n🎯 TAIL Strategy (low-probability events):") tail_signals = strategy_tail(markets, config) for s in tail_signals: print(f" 💰 [{s['price']:.1%}] {s['market']}") print(f" {s['outcome']} → Potential: {s['potential_return']}") all_signals.extend(tail_signals) if not tail_signals: print(" No tail signals found.") if "momentum" in config.get("strategies_enabled", []): print("\n📈 MOMENTUM Strategy (high-volume tipping points):") mom_signals = strategy_momentum(markets, config) for s in mom_signals: print(f" 👀 [{s['price']:.1%}] {s['market']}") print(f" Vol: ${s['volume']:,.0f}") all_signals.extend(mom_signals) if not mom_signals: print(" No momentum signals found.") # Save signals (DATA_DIR / "latest-signals.json").write_text(json.dumps({ "timestamp": datetime.now().isoformat(), "signals": all_signals, "markets_scanned": len(markets), }, indent=2)) return all_signals def execute_paper_trades(signals): """Execute paper trades based on signals.""" config = load_config() portfolio = load_portfolio() max_position = portfolio["balance"] * (config.get("max_position_pct", 5) / 100) trades_made = 0 for signal in signals: if signal["action"] != "BUY": continue if portfolio["balance"] < 100: break # Check if we already have a position in this market existing = [p for p in portfolio["positions"] if p["market_id"] == signal["market_id"]] if existing: continue size = min(max_position, portfolio["balance"] * 0.03) # 3% per trade shares = size / signal["price"] trade = { "ts": datetime.now().isoformat(), "strategy": signal["strategy"], "market": signal["market"], "outcome": signal["outcome"], "action": "BUY", "price": signal["price"], "size": round(size, 2), "shares": round(shares, 2), "market_id": signal["market_id"], } portfolio["balance"] -= size portfolio["positions"].append({ "market_id": signal["market_id"], "market": signal["market"], "outcome": signal["outcome"], "entry_price": signal["price"], "shares": round(shares, 2), "cost": round(size, 2), "strategy": signal["strategy"], "opened": datetime.now().isoformat(), }) portfolio["total_trades"] += 1 log_trade(trade) trades_made += 1 print(f" 📝 PAPER BUY: {signal['outcome']} @ {signal['price']:.1%} (${size:.0f})") save_portfolio(portfolio) return trades_made def show_portfolio(): portfolio = load_portfolio() print(f"\n💼 Paper Portfolio — {datetime.now().strftime('%Y-%m-%d %H:%M')}") print("=" * 60) print(f" 💰 Cash Balance: ${portfolio['balance']:,.2f}") print(f" 📊 Total Trades: {portfolio['total_trades']}") print(f" 📈 P&L: ${portfolio['pnl']:,.2f}") if portfolio["positions"]: print(f"\n Open Positions ({len(portfolio['positions'])}):") for p in portfolio["positions"]: print(f" [{p['strategy']}] {p['market'][:60]}") print(f" {p['outcome']} | Entry: {p['entry_price']:.1%} | Cost: ${p['cost']:.0f} | Shares: {p['shares']:.0f}") else: print("\n No open positions.") def daily_summary(): portfolio = load_portfolio() trades_today = [] if TRADES_FILE.exists(): today = datetime.now().strftime('%Y-%m-%d') for line in TRADES_FILE.read_text().strip().split('\n'): if line: t = json.loads(line) if t["ts"].startswith(today): trades_today.append(t) total_invested = sum(p["cost"] for p in portfolio["positions"]) summary = { "date": datetime.now().strftime('%Y-%m-%d'), "balance": portfolio["balance"], "positions": len(portfolio["positions"]), "total_invested": total_invested, "portfolio_value": portfolio["balance"] + total_invested, "trades_today": len(trades_today), "total_trades": portfolio["total_trades"], } print(f"\n📊 Daily Summary — {summary['date']}") print("=" * 40) print(f" Portfolio Value: ${summary['portfolio_value']:,.2f}") print(f" Cash: ${summary['balance']:,.2f}") print(f" Invested: ${summary['total_invested']:,.2f}") print(f" Positions: {summary['positions']}") print(f" Trades Today: {summary['trades_today']}") (DATA_DIR / "daily-summary.json").write_text(json.dumps(summary, indent=2)) return summary def run_cron(): """Full cron cycle: scan + trade + log.""" print("🤖 Polymarket Autopilot — Cron Cycle") signals = scan_markets() if signals: buy_signals = [s for s in signals if s["action"] == "BUY"] if buy_signals: print(f"\n💸 Executing {len(buy_signals)} paper trades...") execute_paper_trades(buy_signals) daily_summary() if __name__ == "__main__": cmd = sys.argv[1] if len(sys.argv) > 1 else "scan" if cmd == "scan": scan_markets() elif cmd == "trade": signals = scan_markets() execute_paper_trades([s for s in signals if s["action"] == "BUY"]) elif cmd == "portfolio": show_portfolio() elif cmd == "summary": daily_summary() elif cmd == "cron": run_cron() else: print(__doc__)