Full sync - all projects, memory, configs
This commit is contained in:
362
tools/polymarket-autopilot.py
Normal file
362
tools/polymarket-autopilot.py
Normal file
@ -0,0 +1,362 @@
|
||||
#!/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__)
|
||||
Reference in New Issue
Block a user