Files
polymarket-skills/polymarket-live-executor/scripts/execute_live.py
T
Polymarket Skills Builder 068b2adc75 Add 6 Polymarket trading skills with paper trading engine
Composable Agent Skills (SKILL.md format) for Polymarket prediction market
trading. Includes scanner, analyzer, monitor, paper trader, strategy advisor,
and live executor. All tested against live Polymarket APIs. Security audited
with all HIGH/MEDIUM findings resolved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 07:25:07 +00:00

391 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""Execute live trades on Polymarket with mandatory human confirmation.
SAFETY: Every trade requires interactive confirmation. No autonomous execution.
Requires environment variables:
POLYMARKET_PRIVATE_KEY - Burner wallet private key (NEVER main wallet)
POLYMARKET_CONFIRM=true - Safety gate (must be exactly "true")
Optional environment variables:
POLYMARKET_MAX_SIZE - Max $ per trade (default: 10)
POLYMARKET_DAILY_LOSS_LIMIT - Max daily loss in $ (default: 50)
All trades are logged to ~/.polymarket-live/trades.log
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import (
ApiCreds,
OrderArgs,
MarketOrderArgs,
OrderType,
)
CLOB_HOST = "https://clob.polymarket.com"
CHAIN_ID = 137 # Polygon mainnet
LOG_DIR = Path.home() / ".polymarket-live"
LOG_FILE = LOG_DIR / "trades.log"
def check_safety_gates() -> tuple[bool, str]:
"""Verify all safety gates are in place. Returns (ok, message)."""
key = os.environ.get("POLYMARKET_PRIVATE_KEY", "")
if not key:
return False, (
"POLYMARKET_PRIVATE_KEY not set.\n"
"Set it to your BURNER wallet private key (never your main wallet).\n"
"See references/security.md for setup instructions."
)
confirm = os.environ.get("POLYMARKET_CONFIRM", "")
if confirm != "true":
return False, (
"POLYMARKET_CONFIRM is not set to 'true'.\n"
"This safety gate prevents accidental trade execution.\n"
"Set POLYMARKET_CONFIRM=true when you are ready for live trading."
)
return True, "OK"
def get_max_size() -> float:
"""Get maximum position size from env or default."""
try:
return float(os.environ.get("POLYMARKET_MAX_SIZE", "10"))
except ValueError:
return 10.0
def get_daily_loss_limit() -> float:
"""Get daily loss limit from env or default."""
try:
return float(os.environ.get("POLYMARKET_DAILY_LOSS_LIMIT", "50"))
except ValueError:
return 50.0
def get_daily_spending() -> float:
"""Calculate total spending today from the trade log."""
if not LOG_FILE.exists():
return 0.0
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
total = 0.0
for line in LOG_FILE.read_text().splitlines():
try:
entry = json.loads(line)
if entry.get("timestamp", "").startswith(today) and entry.get("status") == "EXECUTED":
total += float(entry.get("cost_usd", 0))
except (json.JSONDecodeError, ValueError):
continue
return total
def log_trade(entry: dict):
"""Append a trade entry to the log file."""
LOG_DIR.mkdir(parents=True, exist_ok=True)
with open(LOG_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
def create_authenticated_client() -> ClobClient:
"""Create an L2-authenticated ClobClient."""
key = os.environ["POLYMARKET_PRIVATE_KEY"]
# Initialize L1 client
client = ClobClient(CLOB_HOST, chain_id=CHAIN_ID, key=key)
# Derive L2 API credentials
creds = client.create_or_derive_api_creds()
client.set_api_creds(creds)
return client
def get_orderbook_context(client: ClobClient, token_id: str) -> dict:
"""Fetch order book context for display."""
try:
book = client.get_order_book(token_id)
bids = [(float(b.price), float(b.size)) for b in (book.bids or [])]
asks = [(float(a.price), float(a.size)) for a in (book.asks or [])]
bids.sort(key=lambda x: x[0], reverse=True)
asks.sort(key=lambda x: x[0])
return {
"best_bid": bids[0] if bids else None,
"best_ask": asks[0] if asks else None,
"bid_depth_5": sum(s for _, s in bids[:5]),
"ask_depth_5": sum(s for _, s in asks[:5]),
"spread": (asks[0][0] - bids[0][0]) if (bids and asks) else None,
}
except Exception as e:
return {"error": str(e)}
def display_trade_confirmation(
side: str,
token_id: str,
size: float | None,
amount: float | None,
price: float | None,
is_market: bool,
context: dict,
max_size: float,
daily_spent: float,
daily_limit: float,
) -> str:
"""Display full trade details and return confirmation prompt text."""
lines = []
lines.append("")
lines.append("=" * 60)
lines.append(" LIVE TRADE CONFIRMATION REQUIRED")
lines.append("=" * 60)
lines.append("")
lines.append(f" Side: {side}")
lines.append(f" Token ID: {token_id[:40]}...")
if is_market:
lines.append(f" Order Type: MARKET (Fill-or-Kill)")
lines.append(f" Amount: ${amount:.2f} USD")
else:
lines.append(f" Order Type: LIMIT (Good-til-Cancelled)")
lines.append(f" Size: {size:.2f} shares")
lines.append(f" Price: ${price:.4f} per share")
est_cost = size * price
lines.append(f" Est. Cost: ${est_cost:.2f} USD")
lines.append("")
lines.append(" Order Book Context:")
if "error" in context:
lines.append(f" ERROR: {context['error']}")
else:
bb = context.get("best_bid")
ba = context.get("best_ask")
lines.append(f" Best Bid: ${bb[0]:.4f} ({bb[1]:.0f} shares)" if bb else " Best Bid: N/A")
lines.append(f" Best Ask: ${ba[0]:.4f} ({ba[1]:.0f} shares)" if ba else " Best Ask: N/A")
spread = context.get("spread")
lines.append(f" Spread: ${spread:.4f}" if spread is not None else " Spread: N/A")
lines.append(f" Bid Depth (5 lvls): {context.get('bid_depth_5', 0):,.0f} shares")
lines.append(f" Ask Depth (5 lvls): {context.get('ask_depth_5', 0):,.0f} shares")
lines.append("")
lines.append(" Risk Controls:")
lines.append(f" Max trade size: ${max_size:.2f}")
lines.append(f" Daily spent: ${daily_spent:.2f} / ${daily_limit:.2f}")
remaining = daily_limit - daily_spent
trade_cost = amount if is_market else (size * price if size and price else 0)
if trade_cost > remaining:
lines.append(f" WARNING: Trade (${trade_cost:.2f}) exceeds remaining daily budget (${remaining:.2f})")
lines.append("")
lines.append("=" * 60)
return "\n".join(lines)
def execute_limit_order(
client: ClobClient,
token_id: str,
side: str,
size: float,
price: float,
) -> dict:
"""Create and post a limit order."""
order_args = OrderArgs(
token_id=token_id,
price=price,
size=size,
side=side,
)
signed_order = client.create_order(order_args)
result = client.post_order(signed_order, orderType=OrderType.GTC)
return result
def execute_market_order(
client: ClobClient,
token_id: str,
side: str,
amount: float,
) -> dict:
"""Create and post a market order."""
order_args = MarketOrderArgs(
token_id=token_id,
amount=amount,
side=side,
)
signed_order = client.create_market_order(order_args)
result = client.post_order(signed_order, orderType=OrderType.FOK)
return result
def main():
parser = argparse.ArgumentParser(
description="Execute a live trade on Polymarket (requires confirmation)"
)
parser.add_argument("--token-id", required=True, help="CLOB token ID")
parser.add_argument(
"--side", required=True, choices=["BUY", "SELL"],
help="Trade side: BUY or SELL"
)
parser.add_argument("--size", type=float, help="Number of shares (for limit orders)")
parser.add_argument("--price", type=float, help="Limit price per share")
parser.add_argument("--amount", type=float, help="USD amount (for market orders)")
parser.add_argument("--market", action="store_true", help="Market order (FOK)")
args = parser.parse_args()
# Validate arguments
if args.market:
if not args.amount:
print("ERROR: --amount required for market orders", file=sys.stderr)
sys.exit(1)
else:
if not args.size or not args.price:
print("ERROR: --size and --price required for limit orders", file=sys.stderr)
sys.exit(1)
# Check safety gates
ok, msg = check_safety_gates()
if not ok:
print(f"SAFETY GATE FAILED:\n{msg}", file=sys.stderr)
sys.exit(1)
# Check position size limits
max_size = get_max_size()
trade_cost = args.amount if args.market else (args.size * args.price)
if trade_cost > max_size:
print(
f"BLOCKED: Trade cost ${trade_cost:.2f} exceeds max size ${max_size:.2f}.\n"
f"Increase POLYMARKET_MAX_SIZE if intentional.",
file=sys.stderr,
)
sys.exit(1)
# Check daily loss limit
daily_limit = get_daily_loss_limit()
daily_spent = get_daily_spending()
if daily_spent + trade_cost > daily_limit:
print(
f"BLOCKED: Daily spending ${daily_spent:.2f} + this trade ${trade_cost:.2f} "
f"= ${daily_spent + trade_cost:.2f} exceeds daily limit ${daily_limit:.2f}.\n"
f"Increase POLYMARKET_DAILY_LOSS_LIMIT or wait until tomorrow.",
file=sys.stderr,
)
sys.exit(1)
# Create authenticated client
try:
client = create_authenticated_client()
except Exception as e:
print(f"ERROR creating authenticated client: {e}", file=sys.stderr)
print("Check POLYMARKET_PRIVATE_KEY and network connectivity.", file=sys.stderr)
sys.exit(1)
# Get order book context
context = get_orderbook_context(client, args.token_id)
# Display confirmation
confirmation = display_trade_confirmation(
side=args.side,
token_id=args.token_id,
size=args.size,
amount=args.amount,
price=args.price,
is_market=args.market,
context=context,
max_size=max_size,
daily_spent=daily_spent,
daily_limit=daily_limit,
)
print(confirmation)
# Ask for confirmation
try:
response = input("\n Type 'yes' to execute this trade: ").strip().lower()
except (EOFError, KeyboardInterrupt):
print("\nTrade cancelled.")
log_trade({
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "CANCELLED",
"reason": "User did not confirm",
"token_id": args.token_id,
"side": args.side,
"size": args.size,
"price": args.price,
"amount": args.amount,
"is_market": args.market,
})
sys.exit(0)
if response != "yes":
print("Trade cancelled. You must type exactly 'yes' to confirm.")
log_trade({
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "CANCELLED",
"reason": f"User typed: {response!r}",
"token_id": args.token_id,
"side": args.side,
"size": args.size,
"price": args.price,
"amount": args.amount,
"is_market": args.market,
})
sys.exit(0)
# Execute the trade
print("\nExecuting trade...")
try:
if args.market:
result = execute_market_order(client, args.token_id, args.side, args.amount)
else:
result = execute_limit_order(client, args.token_id, args.side, args.size, args.price)
print(f"\nTrade submitted successfully!")
print(json.dumps(result, indent=2, default=str))
log_trade({
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "EXECUTED",
"token_id": args.token_id,
"side": args.side,
"size": args.size,
"price": args.price,
"amount": args.amount,
"is_market": args.market,
"cost_usd": trade_cost,
"result": result,
})
except Exception as e:
print(f"\nTrade FAILED: {e}", file=sys.stderr)
log_trade({
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "FAILED",
"error": str(e),
"token_id": args.token_id,
"side": args.side,
"size": args.size,
"price": args.price,
"amount": args.amount,
"is_market": args.market,
"cost_usd": trade_cost,
})
sys.exit(1)
if __name__ == "__main__":
main()