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:
co-authored by
Claude Opus 4.6
parent
6a03bbe2e5
commit
068b2adc75
Executable
+73
@@ -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()
|
||||
Executable
+139
@@ -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()
|
||||
Executable
+142
@@ -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()
|
||||
Reference in New Issue
Block a user