Files
workspace/tools/polymarket-autopilot.py

363 lines
12 KiB
Python

#!/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__)