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>
143 lines
4.3 KiB
Python
Executable File
143 lines
4.3 KiB
Python
Executable File
#!/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()
|