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>
This commit is contained in:
Polymarket Skills Builder
2026-02-26 07:25:07 +00:00
co-authored by Claude Opus 4.6
parent 6a03bbe2e5
commit 068b2adc75
35 changed files with 7842 additions and 1 deletions
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Fetch the full order book for a Polymarket token from the CLOB API."""
import argparse
import json
import sys
from py_clob_client.client import ClobClient
CLOB_HOST = "https://clob.polymarket.com"
def fetch_orderbook(token_id, depth=10):
"""Fetch order book for a token and return structured data."""
client = ClobClient(CLOB_HOST)
ob = client.get_order_book(token_id)
bids = [{"price": float(b.price), "size": float(b.size)} for b in ob.bids]
asks = [{"price": float(a.price), "size": float(a.size)} for a in ob.asks]
# Sort: bids descending by price, asks ascending by price
bids.sort(key=lambda x: x["price"], reverse=True)
asks.sort(key=lambda x: x["price"])
best_bid = bids[0]["price"] if bids else 0.0
best_ask = asks[0]["price"] if asks else 1.0
spread = round(best_ask - best_bid, 6)
midpoint = round((best_ask + best_bid) / 2, 6)
bid_depth = round(sum(b["size"] for b in bids), 2)
ask_depth = round(sum(a["size"] for a in asks), 2)
return {
"market": ob.market,
"asset_id": ob.asset_id,
"bids": bids[:depth],
"asks": asks[:depth],
"spread": spread,
"midpoint": midpoint,
"best_bid": best_bid,
"best_ask": best_ask,
"bid_depth": bid_depth,
"ask_depth": ask_depth,
"total_bid_levels": len(bids),
"total_ask_levels": len(asks),
}
def main():
parser = argparse.ArgumentParser(
description="Fetch order book for a Polymarket token"
)
parser.add_argument(
"--token-id", type=str, required=True,
help="CLOB token ID (from scan_markets.py output)"
)
parser.add_argument(
"--depth", type=int, default=10,
help="Number of price levels to show (default 10)"
)
args = parser.parse_args()
try:
result = fetch_orderbook(args.token_id, depth=args.depth)
print(json.dumps(result, indent=2))
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""Fetch current prices, midpoints, and spreads for Polymarket tokens."""
import argparse
import json
import sys
import requests
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import BookParams
CLOB_HOST = "https://clob.polymarket.com"
GAMMA_API = "https://gamma-api.polymarket.com"
def resolve_slug_to_token_ids(slug):
"""Look up a market by slug and return its token IDs."""
resp = requests.get(
f"{GAMMA_API}/markets",
params={"slug": slug, "limit": 1},
timeout=30,
)
resp.raise_for_status()
markets = resp.json()
if not markets:
return []
market = markets[0]
try:
return json.loads(market.get("clobTokenIds", "[]"))
except (json.JSONDecodeError, TypeError):
return []
def fetch_prices(token_ids):
"""Fetch prices for a list of token IDs using the CLOB API."""
client = ClobClient(CLOB_HOST)
if len(token_ids) == 1:
tid = token_ids[0]
mid = client.get_midpoint(tid)
spread = client.get_spread(tid)
last = client.get_last_trade_price(tid)
buy_price = client.get_price(tid, "BUY")
sell_price = client.get_price(tid, "SELL")
return [{
"token_id": tid,
"midpoint": float(mid.get("mid", 0)),
"best_bid": float(buy_price.get("price", 0)),
"best_ask": float(sell_price.get("price", 0)),
"spread": float(spread.get("spread", 0)),
"last_trade_price": float(last.get("price", 0)),
"last_trade_side": last.get("side", ""),
}]
# Batch mode for multiple tokens
params = [BookParams(token_id=tid) for tid in token_ids]
midpoints = client.get_midpoints(params)
spreads = client.get_spreads(params)
last_trades_raw = client.get_last_trades_prices(params)
# last_trades_prices returns a list of dicts with token_id key, not a dict
last_trades_by_id = {}
if isinstance(last_trades_raw, list):
for item in last_trades_raw:
if isinstance(item, dict) and "token_id" in item:
last_trades_by_id[item["token_id"]] = item
elif isinstance(last_trades_raw, dict):
last_trades_by_id = last_trades_raw
results = []
for tid in token_ids:
mid_val = midpoints.get(tid, "0")
spread_val = spreads.get(tid, "0")
last_info = last_trades_by_id.get(tid, {})
# Get individual bid/ask prices
try:
buy_price = client.get_price(tid, "BUY")
sell_price = client.get_price(tid, "SELL")
best_bid = float(buy_price.get("price", 0))
best_ask = float(sell_price.get("price", 0))
except Exception:
best_bid = 0.0
best_ask = 0.0
results.append({
"token_id": tid,
"midpoint": float(mid_val) if mid_val else 0.0,
"best_bid": best_bid,
"best_ask": best_ask,
"spread": float(spread_val) if spread_val else 0.0,
"last_trade_price": float(last_info.get("price", 0)) if isinstance(last_info, dict) else 0.0,
"last_trade_side": last_info.get("side", "") if isinstance(last_info, dict) else "",
})
return results
def main():
parser = argparse.ArgumentParser(
description="Get current prices for Polymarket tokens"
)
parser.add_argument(
"--token-id", type=str, action="append", default=None,
help="CLOB token ID (can be specified multiple times)"
)
parser.add_argument(
"--market-slug", type=str, default=None,
help="Market slug to look up token IDs automatically"
)
args = parser.parse_args()
token_ids = args.token_id or []
if args.market_slug:
slug_ids = resolve_slug_to_token_ids(args.market_slug)
if not slug_ids:
print(json.dumps({"error": f"No tokens found for slug: {args.market_slug}"}),
file=sys.stderr)
sys.exit(1)
token_ids.extend(slug_ids)
if not token_ids:
print(json.dumps({"error": "Provide --token-id or --market-slug"}),
file=sys.stderr)
sys.exit(1)
try:
results = fetch_prices(token_ids)
print(json.dumps(results, indent=2))
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""Scan and search active Polymarket prediction markets via the Gamma API."""
import argparse
import json
import re
import sys
import requests
GAMMA_API = "https://gamma-api.polymarket.com"
MAX_TEXT_LEN = 200
def sanitize_text(text):
"""Strip control characters and limit length. Market text is user-generated."""
if not text:
return ""
text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text)
if len(text) > MAX_TEXT_LEN:
text = text[:MAX_TEXT_LEN] + "..."
return text
def fetch_markets(limit=20, category=None, search=None, min_volume=0,
sort_by="volume24hr", ascending=False):
"""Fetch active markets from Gamma API with filtering and sorting."""
params = {
"limit": min(limit, 100),
"active": "true",
"closed": "false",
"order": sort_by,
"ascending": str(ascending).lower(),
}
if category:
params["tag_slug"] = category.lower()
resp = requests.get(f"{GAMMA_API}/markets", params=params, timeout=30)
resp.raise_for_status()
raw_markets = resp.json()
results = []
for m in raw_markets:
vol_24h = float(m.get("volume24hr", 0) or 0)
if vol_24h < min_volume:
continue
# Parse JSON-encoded fields
try:
outcomes = json.loads(m.get("outcomes", "[]"))
except (json.JSONDecodeError, TypeError):
outcomes = []
try:
outcome_prices = json.loads(m.get("outcomePrices", "[]"))
outcome_prices = [float(p) for p in outcome_prices]
except (json.JSONDecodeError, TypeError, ValueError):
outcome_prices = []
try:
token_ids = json.loads(m.get("clobTokenIds", "[]"))
except (json.JSONDecodeError, TypeError):
token_ids = []
# Apply keyword search filter
if search:
question = (m.get("question", "") or "").lower()
description = (m.get("description", "") or "").lower()
search_lower = search.lower()
if search_lower not in question and search_lower not in description:
continue
market = {
"question": sanitize_text(m.get("question", "")),
"slug": m.get("slug", ""),
"url": f"https://polymarket.com/event/{m.get('slug', '')}",
"outcomes": [sanitize_text(o) for o in outcomes],
"outcome_prices": outcome_prices,
"token_ids": token_ids,
"volume_24h": vol_24h,
"volume_total": float(m.get("volumeNum", 0) or 0),
"liquidity": float(m.get("liquidityNum", 0) or 0),
"end_date": m.get("endDate", ""),
"active": m.get("active", False),
"accepting_orders": m.get("acceptingOrders", False),
}
results.append(market)
return results
def main():
parser = argparse.ArgumentParser(
description="Scan active Polymarket prediction markets"
)
parser.add_argument(
"--limit", type=int, default=20,
help="Number of markets to return (max 100, default 20)"
)
parser.add_argument(
"--category", type=str, default=None,
help="Filter by tag/category (e.g. crypto, politics, sports)"
)
parser.add_argument(
"--search", type=str, default=None,
help="Search keyword in market question/description"
)
parser.add_argument(
"--min-volume", type=float, default=0,
help="Minimum 24h volume in USD (default 0)"
)
parser.add_argument(
"--sort-by", type=str, default="volume24hr",
choices=["volume24hr", "liquidity", "endDate", "startDate"],
help="Sort field (default: volume24hr)"
)
parser.add_argument(
"--ascending", action="store_true",
help="Sort ascending instead of descending"
)
args = parser.parse_args()
try:
markets = fetch_markets(
limit=args.limit,
category=args.category,
search=args.search,
min_volume=args.min_volume,
sort_by=args.sort_by,
ascending=args.ascending,
)
print(json.dumps(markets, indent=2))
except requests.RequestException as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()