Skill: bibaofeng/prediction-market-arbitrage-api (version 1.0.0)

--- name: prediction-market-arbitrage-api description: "Find arbitrage opportunities across Polymarket and Kalshi prediction markets via AIsa API. Scan sports markets for cross-platform price discrepancies, compare real-time odds, verify orderbook liquidity. Use when user asks about: prediction market arbitrage, cross-platform price differences, sports betting arbitrage, odds comparison, risk-free profit, market inefficiencies." license: MIT metadata: openclaw: emoji: "⚖️" requires: bins: [python3] env: [AISA_API_KEY] primaryEnv: AISA_API_KEY --- # Prediction Market Arbitrage Api Find arbitrage opportunities across [Polymarket](https://polymarket.com) and [Kalshi](https://kalshi.com) via [AIsa API](https://aisa.one). ## Setup ```bash export AISA_API_KEY="your-key" ``` Get a key at [aisa.one](https://aisa.one) ($0.01/query, pay-as-you-go). ## Workflow Finding arbitrage involves these steps: 1. **Scan matching markets** across platforms (`scan` for bulk, `match` for specific) 2. **Review spreads** — the tool calculates price differences automatically 3. **Verify liquidity** — use `prediction_market_client.py orderbooks` to check depth before acting ## Quick Examples ### Scan a sport for arbitrage ```bash # Scan all NBA markets on a date — shows spreads automatically python3 {baseDir}/scripts/arbitrage_finder.py scan nba --date 2025-04-01 ``` ### Analyze a specific market ```bash # By Polymarket slug python3 {baseDir}/scripts/arbitrage_finder.py match --polymarket-slug <slug> # By Kalshi ticker python3 {baseDir}/scripts/arbitrage_finder.py match --kalshi-ticker <ticker> ``` ### Verify liquidity before acting ```bash # Check orderbook depth on both sides python3 {baseDir}/scripts/prediction_market_client.py polymarket orderbooks --token-id <id> python3 {baseDir}/scripts/prediction_market_client.py kalshi orderbooks --ticker <ticker> ``` ## Commands ### arbitrage_finder.py — Automated Detection ```bash python3 {baseDir}/scripts/arbitrage_finder.py scan <sport> --date <YYYY-MM-DD> [--min-spread <pct>] [--min-liquidity <usd>] [--json] python3 {baseDir}/scripts/arbitrage_finder.py match --polymarket-slug <slug> [--min-spread <pct>] [--min-liquidity <usd>] [--json] python3 {baseDir}/scripts/arbitrage_finder.py match --kalshi-ticker <ticker> [--min-spread <pct>] [--min-liquidity <usd>] [--json] ``` Sports: `nba`, `nfl`, `mlb`, `nhl`, `soccer`, `tennis`. ### prediction_market_client.py — Raw Market Data Use for manual price checks and deeper analysis. ```bash # Search markets python3 {baseDir}/scripts/prediction_market_client.py polymarket markets --search <kw> --status open --limit 5 python3 {baseDir}/scripts/prediction_market_client.py kalshi markets --search <kw> --status open --limit 5 # Get prices (use token_id / market_ticker from markets output) python3 {baseDir}/scripts/prediction_market_client.py polymarket price <token_id> python3 {baseDir}/scripts/prediction_market_client.py kalshi price <market_ticker> # Cross-platform sports matching python3 {baseDir}/scripts/prediction_market_client.py sports by-date <sport> --date <YYYY-MM-DD> python3 {baseDir}/scripts/prediction_market_client.py sports matching (--polymarket-slug <slug> | --kalshi-ticker <ticker>) # Orderbook depth python3 {baseDir}/scripts/prediction_market_client.py polymarket orderbooks --token-id <id> python3 {baseDir}/scripts/prediction_market_client.py kalshi orderbooks --ticker <ticker> ``` ## Understanding Arbitrage Arbitrage exists when the combined cost of all mutually exclusive outcomes across platforms is less than `1.0`: > "Yes" at `0.40` on Polymarket + "No" at `0.55` on Kalshi = cost `0.95`, guaranteed payout `1.00` → 5.3% profit. Always verify orderbook depth — a spread without liquidity is not executable. ## Security & Permissions **Requires:** `AISA_API_KEY` environment variable. All operations are **read-only** via HTTPS GET to `api.aisa.one`. Arbitrage spreads are calculated locally. No trades executed, no wallets connected, no personal data sent beyond the API key. Every response includes `usage.cost` and `usage.credits_remaining`. Full docs: [AIsa API Reference](https://docs.aisa.one/reference/).

path: scripts/arbitrage_finder.py
content: #!/usr/bin/env python3

"""
OpenClaw Prediction Market Arbitrage Finder
Automated cross-platform arbitrage detection across Polymarket and Kalshi.
Powered by AIsa.

Workflow (mirrors SKILL_ARBITRAGE.md):
  1. Find matching markets across platforms
  2. Compare real-time prices to calculate spreads
  3. Verify orderbook liquidity to confirm actionability

Usage:
  python arbitrage_finder.py scan <sport> --date <YYYY-MM-DD> [--min-spread <pct>] [--min-liquidity <usd>] [--json]
  python arbitrage_finder.py match --polymarket-slug <slug> [--min-spread <pct>] [--min-liquidity <usd>] [--json]
  python arbitrage_finder.py match --kalshi-ticker <ticker> [--min-spread <pct>] [--min-liquidity <usd>] [--json]

Examples:
  python arbitrage_finder.py scan nba --date 2025-03-30
  python arbitrage_finder.py scan nba --date 2025-03-30 --min-spread 3.0 --min-liquidity 500
  python arbitrage_finder.py match --polymarket-slug nba-lakers-vs-celtics
  python arbitrage_finder.py match --kalshi-ticker KXNBA-25-LAL-BOS
"""

import argparse
import json
import os
import sys
import urllib.request
import urllib.parse
import urllib.error
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple


# ──────────────────────────────────────────────────────────────────────────────
# API Client
# ──────────────────────────────────────────────────────────────────────────────

class AIsaClient:
    """Lightweight client for the AIsa prediction market API."""

    BASE_URL = "https://api.aisa.one/apis/v1"

    def __init__(self, [REDACTED_SECRET] = None):
        self.[REDACTED_SECRET] or os.environ.get("AISA_API_KEY")
        if not self.[REDACTED_SECRET]"Error: AISA_API_KEY is required.")
            print("Set it via:  export AISA_API_KEY=\"your-key\"")
            print("Sign up at:  https://aisa.one")
            sys.exit(1)

    def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        url = f"{self.BASE_URL}{path}"
        if params:
            qs = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
            if qs:
                url = f"{url}?{qs}"

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "User-Agent": "OpenClaw-ArbitrageFinder/1.0",
        }
        req = urllib.request.Request(url, headers=headers, method="GET")

        try:
            with urllib.request.urlopen(req, timeout=60) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            body = e.read().decode("utf-8")
            try:
                return json.loads(body)
            except json.JSONDecodeError:
                return {"error": {"code": str(e.code), "message": body}}
        except urllib.error.URLError as e:
            return {"error": {"code": "NETWORK_ERROR", "message": str(e.reason)}}

    def _get_multi(self, path: str, params_list: List[Tuple[str, str]]) -> Dict[str, Any]:
        """GET with repeated query params (e.g. multiple slugs/tickers)."""
        qs = "&".join(f"{urllib.parse.quote(k)}={urllib.parse.quote(v)}" for k, v in params_list)
        url = f"{self.BASE_URL}{path}?{qs}" if qs else f"{self.BASE_URL}{path}"

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "User-Agent": "OpenClaw-ArbitrageFinder/1.0",
        }
        req = urllib.request.Request(url, headers=headers, method="GET")

        try:
            with urllib.request.urlopen(req, timeout=60) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            body = e.read().decode("utf-8")
            try:
                return json.loads(body)
            except json.JSONDecodeError:
                return {"error": {"code": str(e.code), "message": body}}
        except urllib.error.URLError as e:
            return {"error": {"code": "NETWORK_ERROR", "message": str(e.reason)}}

    # ── Step 1: Find matching markets ────────────────────────────────────────

    def matching_sports_by_date(self, sport: str, date: str) -> Dict[str, Any]:
        """Find matching sports markets across platforms for a given date."""
        return self._get(f"/matching-markets/sports/{sport}", {"date": date})

    def matching_sports_by_slug(self, slugs: List[str]) -> Dict[str, Any]:
        """Find matching markets using Polymarket slug(s)."""
        params = [("polymarket_market_slug", s) for s in slugs]
        return self._get_multi("/matching-markets/sports", params)

    def matching_sports_by_ticker(self, tickers: List[str]) -> Dict[str, Any]:
        """Find matching markets using Kalshi event ticker(s)."""
        params = [("kalshi_event_ticker", t) for t in tickers]
        return self._get_multi("/matching-markets/sports", params)

    # ── Step 2: Compare prices ───────────────────────────────────────────────

    def polymarket_price(self, token_id: str) -> Dict[str, Any]:
        """Get current price for a Polymarket token (from side_a.id or side_b.id)."""
        return self._get(f"/polymarket/market-price/{token_id}")

    def kalshi_price(self, market_ticker: str) -> Dict[str, Any]:
        """Get current price for a Kalshi market (from market_ticker)."""
        return self._get(f"/kalshi/market-price/{market_ticker}")

    # ── Step 3: Verify liquidity ─────────────────────────────────────────────

    def polymarket_orderbook(self, token_id: str) -> Dict[str, Any]:
        """Get latest orderbook snapshot for a Polymarket token."""
        return self._get("/polymarket/orderbooks", {"token_id": token_id})

    def kalshi_orderbook(self, ticker: str) -> Dict[str, Any]:
        """Get latest orderbook snapshot for a Kalshi market."""
        return self._get("/kalshi/orderbooks", {"ticker": ticker})


# ──────────────────────────────────────────────────────────────────────────────
# Arbitrage Analysis
# ──────────────────────────────────────────────────────────────────────────────

def extract_polymarket_price(price_resp: Dict[str, Any]) -> Optional[float]:
    """Extract the Yes price from a Polymarket market-price response."""
    if "error" in price_resp:
        return None
    price = price_resp.get("price")
    if price is not None:
        return float(price)
    # Some responses nest under a data key
    data = price_resp.get("data", {})
    if isinstance(data, dict):
        p = data.get("price") or data.get("mid") or data.get("last")
        if p is not None:
            return float(p)
    return None


def extract_kalshi_price(price_resp: Dict[str, Any]) -> Optional[float]:
    """Extract the Yes price from a Kalshi market-price response.
    Kalshi prices are in cents (0-100), so we normalize to 0-1."""
    if "error" in price_resp:
        return None
    # Try direct fields
    for key in ("yes_price", "price", "last_price", "yes_ask"):
        val = price_resp.get(key)
        if val is not None:
            v = float(val)
            return v / 100.0 if v > 1.0 else v
    data = price_resp.get("data", {})
    if isinstance(data, dict):
        for key in ("yes_price", "price", "last_price", "yes_ask"):
            val = data.get(key)
            if val is not None:
                v = float(val)
                return v / 100.0 if v > 1.0 else v
    return None


def compute_orderbook_liquidity(ob_resp: Dict[str, Any]) -> Optional[float]:
    """Estimate total USD liquidity from an orderbook response.
    Sums the top-of-book bid and ask sizes as a rough liquidity measure."""
    if "error" in ob_resp:
        return None

    total = 0.0

    # Handle various response shapes
    orderbook = ob_resp
    if "data" in ob_resp and isinstance(ob_resp["data"], dict):
        orderbook = ob_resp["data"]
    if "orderbook" in orderbook and isinstance(orderbook["orderbook"], dict):
        orderbook = orderbook["orderbook"]

    for side_key in ("bids", "asks"):
        levels = orderbook.get(side_key, [])
        if isinstance(levels, list):
            for level in levels:
                if isinstance(level, dict):
                    size = float(level.get("size", 0) or level.get("quantity", 0) or 0)
                    price = float(level.get("price", 0) or 0)
                    total += size * price if price <= 1.0 else size * (price / 100.0)
                elif isinstance(level, (list, tuple)) and len(level) >= 2:
                    price, size = float(level[0]), float(level[1])
                    total += size * price if price <= 1.0 else size * (price / 100.0)

    return total if total > 0 else None


def calculate_spread(poly_yes: float, kalshi_yes: float) -> Dict[str, Any]:
    """Calculate arbitrage spread between two platforms.

    An arbitrage exists when you can buy 'Yes' on one platform and 'No' on
    the other for a combined cost below 1.0.

    Returns details for the best arbitrage direction."""

    # Direction A: Buy Yes on Polymarket + Buy No on Kalshi
    cost_a = poly_yes + (1.0 - kalshi_yes)
    profit_a = 1.0 - cost_a

    # Direction B: Buy Yes on Kalshi + Buy No on Polymarket
    cost_b = kalshi_yes + (1.0 - poly_yes)
    profit_b = 1.0 - cost_b

    if profit_a >= profit_b:
        return {
            "spread_pct": round(profit_a * 100, 2),
            "profit_per_dollar": round(profit_a, 4),
            "direction": "Buy YES on Polymarket + Buy NO on Kalshi",
            "buy_yes_platform": "Polymarket",
            "buy_yes_price": round(poly_yes, 4),
            "buy_no_platform": "Kalshi",
            "buy_no_price": round(1.0 - kalshi_yes, 4),
            "total_cost": round(cost_a, 4),
        }
    else:
        return {
            "spread_pct": round(profit_b * 100, 2),
            "profit_per_dollar": round(profit_b, 4),
            "direction": "Buy YES on Kalshi + Buy NO on Polymarket",
            "buy_yes_platform": "Kalshi",
            "buy_yes_price": round(kalshi_yes, 4),
            "buy_no_platform": "Polymarket",
            "buy_no_price": round(1.0 - poly_yes, 4),
            "total_cost": round(cost_b, 4),
        }


# ──────────────────────────────────────────────────────────────────────────────
# Matching-Market Parsers
# ──────────────────────────────────────────────────────────────────────────────

def parse_matched_pairs(resp: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Extract matched market pairs from the /matching-markets response.
    Adapts to multiple possible response shapes."""

    pairs = []

    # Try top-level list
    items = resp if isinstance(resp, list) else resp.get("data", resp.get("matches", resp.get("markets", [])))
    if isinstance(items, dict):
        items = items.get("data", items.get("matches", items.get("markets", [])))
    if not isinstance(items, list):
        items = [items] if isinstance(items, dict) else []

    for item in items:
        if not isinstance(item, dict):
            continue

        pair: Dict[str, Any] = {"title": item.get("title", "Unknown Event")}

        # Polymarket side
        poly = item.get("polymarket", item.get("polymarket_market", {}))
        if isinstance(poly, dict):
            pair["polymarket"] = {
                "market_slug": poly.get("market_slug", ""),
                "token_id_yes": poly.get("side_a", {}).get("id", "") if isinstance(poly.get("side_a"), dict) else "",
                "token_id_no": poly.get("side_b", {}).get("id", "") if isinstance(poly.get("side_b"), dict) else "",
                "condition_id": poly.get("condition_id", ""),
            }
        elif isinstance(poly, list) and poly:
            p = poly[0]
            pair["polymarket"] = {
                "market_slug": p.get("market_slug", ""),
                "token_id_yes": p.get("side_a", {}).get("id", "") if isinstance(p.get("side_a"), dict) else "",
                "token_id_no": p.get("side_b", {}).get("id", "") if isinstance(p.get("side_b"), dict) else "",
                "condition_id": p.get("condition_id", ""),
            }

        # Kalshi side
        kal = item.get("kalshi", item.get("kalshi_market", {}))
        if isinstance(kal, dict):
            pair["kalshi"] = {
                "market_ticker": kal.get("market_ticker", ""),
                "event_ticker": kal.get("event_ticker", ""),
            }
        elif isinstance(kal, list) and kal:
            k = kal[0]
            pair["kalshi"] = {
                "market_ticker": k.get("market_ticker", ""),
                "event_ticker": k.get("event_ticker", ""),
            }

        if "polymarket" in pair and "kalshi" in pair:
            pairs.append(pair)

    return pairs


# ──────────────────────────────────────────────────────────────────────────────
# Core Scan Logic
# ──────────────────────────────────────────────────────────────────────────────

def analyze_pair(
    client: AIsaClient,
    pair: Dict[str, Any],
    min_spread: float,
    min_liquidity: float,
) -> Optional[Dict[str, Any]]:
    """Run the full arbitrage workflow on a single matched pair."""

    title = pair.get("title", "Unknown")
    poly = pair.get("polymarket", {})
    kal = pair.get("kalshi", {})

    token_id = poly.get("token_id_yes", "")
    market_ticker = kal.get("market_ticker", "")

    if not token_id or not market_ticker:
        return None

    # Step 2: Compare prices
    poly_resp = client.polymarket_price(token_id)
    kal_resp = client.kalshi_price(market_ticker)

    poly_yes = extract_polymarket_price(poly_resp)
    kal_yes = extract_kalshi_price(kal_resp)

    if poly_yes is None or kal_yes is None:
        return None

    spread = calculate_spread(poly_yes, kal_yes)

    if spread["spread_pct"] < min_spread:
        return None

    # Step 3: Verify liquidity
    poly_ob = client.polymarket_orderbook(token_id)
    kal_ob = client.kalshi_orderbook(market_ticker)

    poly_liq = compute_orderbook_liquidity(poly_ob)
    kal_liq = compute_orderbook_liquidity(kal_ob)

    effective_liq = min(poly_liq or 0, kal_liq or 0)

    result = {
        "title": title,
        "polymarket_slug": poly.get("market_slug", ""),
        "kalshi_ticker": market_ticker,
        "polymarket_yes_price": poly_yes,
        "kalshi_yes_price": kal_yes,
        **spread,
        "polymarket_liquidity_usd": round(poly_liq, 2) if poly_liq else None,
        "kalshi_liquidity_usd": round(kal_liq, 2) if kal_liq else None,
        "effective_liquidity_usd": round(effective_liq, 2),
    }

    if min_liquidity > 0 and effective_liq < min_liquidity:
        result["actionable"] = False
        result["reason"] = f"Insufficient liquidity (${effective_liq:.2f} < ${min_liquidity:.2f})"
    else:
        result["actionable"] = spread["spread_pct"] > 0
        if not result["actionable"]:
            result["reason"] = "No positive spread"

    return result


def run_scan(
    client: AIsaClient,
    sport: str,
    date: str,
    min_spread: float,
    min_liquidity: float,
) -> List[Dict[str, Any]]:
    """Scan all matching markets for a sport on a given date."""

    print(f"\n{'='*70}")
    print(f"  Arbitrage Scan: {sport.upper()} on {date}")
    print(f"  Filters: min spread >= {min_spread}%  |  min liquidity >= ${min_liquidity:.0f}")
    print(f"{'='*70}\n")

    # Step 1: Find matching markets
    print("[1/3] Finding matching markets across Polymarket and Kalshi...")
    resp = client.matching_sports_by_date(sport, date)

    if "error" in resp:
        print(f"  Error: {resp['error']}")
        return []

    pairs = parse_matched_pairs(resp)
    print(f"  Found {len(pairs)} matched market pair(s).\n")

    if not pairs:
        return []

    # Steps 2 & 3: Compare prices and verify liquidity for each pair
    results = []
    for i, pair in enumerate(pairs, 1):
        title = pair.get("title", "Unknown")
        print(f"[2/3] Analyzing pair {i}/{len(pairs)}: {title}")

        result = analyze_pair(client, pair, min_spread, min_liquidity)
        if result:
            results.append(result)
            _print_opportunity(result)
        else:
            print(f"  -> Skipped (no price data or spread below {min_spread}%)\n")

    return results


def run_match(
    client: AIsaClient,
    polymarket_slug: Optional[str],
    kalshi_ticker: Optional[str],
    min_spread: float,
    min_liquidity: float,
) -> List[Dict[str, Any]]:
    """Analyze a specific matched market by slug or ticker."""

    print(f"\n{'='*70}")
    print(f"  Arbitrage Match Analysis")
    if polymarket_slug:
        print(f"  Polymarket slug: {polymarket_slug}")
    if kalshi_ticker:
        print(f"  Kalshi ticker:   {kalshi_ticker}")
    print(f"  Filters: min spread >= {min_spread}%  |  min liquidity >= ${min_liquidity:.0f}")
    print(f"{'='*70}\n")

    # Step 1: Find matching markets
    print("[1/3] Finding matching markets...")
    if polymarket_slug:
        resp = client.matching_sports_by_slug([polymarket_slug])
    else:
        resp = client.matching_sports_by_ticker([kalshi_ticker])

    if "error" in resp:
        print(f"  Error: {resp['error']}")
        return []

    pairs = parse_matched_pairs(resp)
    print(f"  Found {len(pairs)} matched market pair(s).\n")

    if not pairs:
        return []

    results = []
    for i, pair in enumerate(pairs, 1):
        title = pair.get("title", "Unknown")
        print(f"[2/3] Analyzing pair {i}/{len(pairs)}: {title}")

        result = analyze_pair(client, pair, min_spread, min_liquidity)
        if result:
            results.append(result)
            _print_opportunity(result)
        else:
            print(f"  -> Skipped (no price data or spread below {min_spread}%)\n")

    return results


# ──────────────────────────────────────────────────────────────────────────────
# Display
# ──────────────────────────────────────────────────────────────────────────────

def _print_opportunity(r: Dict[str, Any]) -> None:
    """Pretty-print a single arbitrage opportunity."""
    actionable = r.get("actionable", False)
    tag = "ACTIONABLE" if actionable else "NOT ACTIONABLE"
    marker = ">>>" if actionable else "   "

    print(f"\n  {marker} [{tag}] {r['title']}")
    print(f"      Polymarket YES: {r['polymarket_yes_price']:.4f}  |  Kalshi YES: {r['kalshi_yes_price']:.4f}")
    print(f"      Spread:         {r['spread_pct']:+.2f}%  ({r['direction']})")
    print(f"      Total cost:     {r['total_cost']:.4f}  |  Profit/dollar: {r['profit_per_dollar']:.4f}")

    poly_liq = r.get("polymarket_liquidity_usd")
    kal_liq = r.get("kalshi_liquidity_usd")
    eff_liq = r.get("effective_liquidity_usd", 0)
    print(f"      Liquidity:      Polymarket ${poly_liq or 'N/A'}  |  Kalshi ${kal_liq or 'N/A'}  |  Effective ${eff_liq}")

    if not actionable and "reason" in r:
        print(f"      Reason:         {r['reason']}")
    print()


def print_summary(results: List[Dict[str, Any]]) -> None:
    """Print a summary table of all results."""
    if not results:
        print("\nNo arbitrage opportunities found matching your criteria.\n")
        return

    actionable = [r for r in results if r.get("actionable")]
    print(f"\n{'='*70}")
    print(f"  SUMMARY: {len(results)} opportunity(ies) analyzed, {len(actionable)} actionable")
    print(f"{'='*70}")

    if actionable:
        print(f"\n  {'Event':<35} {'Spread':>8} {'Liquidity':>12} {'Direction'}")
        print(f"  {'-'*35} {'-'*8} {'-'*12} {'-'*40}")
        for r in sorted(actionable, key=lambda x: x["spread_pct"], reverse=True):
            title = r["title"][:33] + ".." if len(r["title"]) > 35 else r["title"]
            spread = f"{r['spread_pct']:+.2f}%"
            liq = f"${r.get('effective_liquidity_usd', 0):,.0f}"
            direction = r["direction"]
            print(f"  {title:<35} {spread:>8} {liq:>12} {direction}")
    print()


# ──────────────────────────────────────────────────────────────────────────────
# CLI
# ──────────────────────────────────────────────────────────────────────────────

def main():
    parser = argparse.ArgumentParser(
        description="OpenClaw Prediction Market Arbitrage Finder - Detect cross-platform price discrepancies",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s scan nba --date 2025-03-30
  %(prog)s scan nba --date 2025-03-30 --min-spread 3.0 --min-liquidity 500
  %(prog)s match --polymarket-slug nba-lakers-vs-celtics
  %(prog)s match --kalshi-ticker KXNBA-25-LAL-BOS
""",
    )

    subparsers = parser.add_subparsers(dest="command", help="Command")

    # scan
    scan_parser = subparsers.add_parser(
        "scan",
        help="Scan all matching markets for a sport on a given date",
    )
    scan_parser.add_argument(
        "sport",
        choices=["nba", "nfl", "mlb", "nhl", "soccer", "tennis"],
        help="Sport to scan",
    )
    scan_parser.add_argument("--date", required=True, help="Date in YYYY-MM-DD format")
    scan_parser.add_argument("--min-spread", type=float, default=0.0, help="Minimum spread %% to report (default: 0)")
    scan_parser.add_argument("--min-liquidity", type=float, default=0.0, help="Minimum effective liquidity in USD (default: 0)")
    scan_parser.add_argument("--json", action="store_true", help="Output results as JSON")

    # match
    match_parser = subparsers.add_parser(
        "match",
        help="Analyze a specific market by Polymarket slug or Kalshi ticker",
    )
    match_group = match_parser.add_mutually_exclusive_group(required=True)
    match_group.add_argument("--polymarket-slug", help="Polymarket market slug")
    match_group.add_argument("--kalshi-ticker", help="Kalshi event ticker")
    match_parser.add_argument("--min-spread", type=float, default=0.0, help="Minimum spread %% to report (default: 0)")
    match_parser.add_argument("--min-liquidity", type=float, default=0.0, help="Minimum effective liquidity in USD (default: 0)")
    match_parser.add_argument("--json", action="store_true", help="Output results as JSON")

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(1)

    client = AIsaClient()

    if args.command == "scan":
        results = run_scan(client, args.sport, args.date, args.min_spread, args.min_liquidity)
    elif args.command == "match":
        results = run_match(client, args.polymarket_slug, args.kalshi_ticker, args.min_spread, args.min_liquidity)
    else:
        parser.print_help()
        sys.exit(1)

    if args.json:
        print(json.dumps(results, indent=2, ensure_ascii=False))
    else:
        print_summary(results)

    sys.exit(0 if results else 1)


if __name__ == "__main__":
    main()


path: scripts/prediction_market_client.py
content: #!/usr/bin/env python3

"""
Cross-Platform Prediction Market Data - AIsa API Client
Prediction market data from Polymarket and Kalshi for autonomous agents.
Powered by AIsa.

ID Lookup:
  Most endpoints require an ID that comes from the /markets response.
  Always query markets first, then pass the relevant ID to downstream endpoints.

  - Polymarket token_id:    from side_a.id or side_b.id in /polymarket/markets response
  - Polymarket condition_id: from condition_id in /polymarket/markets response
  - Kalshi market_ticker:   from market_ticker in /kalshi/markets response

Usage:
  # Polymarket
  python prediction_market_client.py polymarket markets [--search <kw>] [--status open|closed] [--min-volume <n>] [--limit <n>]
  python prediction_market_client.py polymarket price <token_id> [--at-time <unix_ts>]
  python prediction_market_client.py polymarket activity --user <wallet> [--market-slug <slug>] [--limit <n>]
  python prediction_market_client.py polymarket orders [--market-slug <slug>] [--token-id <id>] [--user <wallet>] [--limit <n>]
  python prediction_market_client.py polymarket orderbooks --token-id <id> [--start <ms>] [--end <ms>] [--limit <n>]
  python prediction_market_client.py polymarket candlesticks <condition_id> --start <unix_ts> --end <unix_ts> [--interval 1|60|1440]
  python prediction_market_client.py polymarket positions <wallet_address> [--limit <n>]
  python prediction_market_client.py polymarket wallet (--eoa <addr> | --proxy <addr>) [--with-metrics] [--start <unix_ts>] [--end <unix_ts>]
  python prediction_market_client.py polymarket pnl <wallet_address> --granularity <day|week|month> [--start <unix_ts>] [--end <unix_ts>]

  # Kalshi
  python prediction_market_client.py kalshi markets [--search <kw>] [--status open|closed] [--min-volume <n>] [--limit <n>]
  python prediction_market_client.py kalshi price <market_ticker> [--at-time <unix_ts>]
  python prediction_market_client.py kalshi trades [--ticker <ticker>] [--start <unix_ts>] [--end <unix_ts>] [--limit <n>]
  python prediction_market_client.py kalshi orderbooks --ticker <ticker> [--start <ms>] [--end <ms>] [--limit <n>]

  # Cross-platform sports
  python prediction_market_client.py sports matching (--polymarket-slug <slug> | --kalshi-ticker <ticker>)
  python prediction_market_client.py sports by-date <sport> --date <YYYY-MM-DD>
"""

import argparse
import json
import os
import sys
import urllib.request
import urllib.parse
import urllib.error
from typing import Any, Dict, List, Optional


class PredictionMarketClient:
    """Cross-Platform Prediction Market Data - AIsa API Client."""

    BASE_URL = "https://api.aisa.one/apis/v1"

    def __init__(self, [REDACTED_SECRET] = None):
        self.[REDACTED_SECRET] or os.environ.get("AISA_API_KEY")
        if not self.api_key:
            raise ValueError(
                "AISA_API_KEY is required. Set it via environment variable or pass to constructor."
            )

    def _request(
        self,
        endpoint: str,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        url = f"{self.BASE_URL}{endpoint}"

        if params:
            query_string = urllib.parse.urlencode(
                {k: v for k, v in params.items() if v is not None}
            )
            url = f"{url}?{query_string}"

        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "User-Agent": "OpenClaw-PredictionMarket/1.0",
        }

        req = urllib.request.Request(url, headers=headers, method="GET")

        try:
            with urllib.request.urlopen(req, timeout=60) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            error_body = e.read().decode("utf-8")
            try:
                return json.loads(error_body)
            except json.JSONDecodeError:
                return {"success": False, "error": {"code": str(e.code), "message": error_body}}
        except urllib.error.URLError as e:
            return {"success": False, "error": {"code": "NETWORK_ERROR", "message": str(e.reason)}}

    # ==================== Polymarket ====================

    def polymarket_markets(
        self,
        search: Optional[str] = None,
        market_slug: Optional[List[str]] = None,
        event_slug: Optional[List[str]] = None,
        condition_id: Optional[List[str]] = None,
        tags: Optional[List[str]] = None,
        status: Optional[str] = None,
        min_volume: Optional[float] = None,
        limit: int = 10,
        offset: int = 0,
        start_time: Optional[int] = None,
        end_time: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Search/filter Polymarket markets.

        Use this endpoint first to obtain IDs needed by other endpoints:
          - token_id:     from side_a.id or side_b.id in the response
          - condition_id: from condition_id in the response
        """
        params: Dict[str, Any] = {"limit": limit, "offset": offset}
        if search:
            params["search"] = search
        if status:
            params["status"] = status
        if min_volume is not None:
            params["min_volume"] = min_volume
        if start_time:
            params["start_time"] = start_time
        if end_time:
            params["end_time"] = end_time
        url = f"{self.BASE_URL}/polymarket/markets"
        parts = []
        for k, v in params.items():
            if v is not None:
                parts.append(f"{urllib.parse.quote(str(k))}={urllib.parse.quote(str(v))}")
        for slug in (market_slug or []):
            parts.append(f"market_slug={urllib.parse.quote(slug)}")
        for slug in (event_slug or []):
            parts.append(f"event_slug={urllib.parse.quote(slug)}")
        for cid in (condition_id or []):
            parts.append(f"condition_id={urllib.parse.quote(cid)}")
        for tag in (tags or []):
            parts.append(f"tags={urllib.parse.quote(tag)}")
        if parts:
            url = f"{url}?{'&'.join(parts)}"
        return self._raw_get(url)

    def polymarket_price(self, token_id: str, at_time: Optional[int] = None) -> Dict[str, Any]:
        """Get current (or historical) price for a Polymarket token.

        token_id comes from side_a.id or side_b.id in /polymarket/markets response.
        """
        return self._request(f"/polymarket/market-price/{token_id}", params={"at_time": at_time})

    def polymarket_activity(self, user: str, market_slug: Optional[str] = None,
                            condition_id: Optional[str] = None, start_time: Optional[int] = None,
                            end_time: Optional[int] = None, limit: int = 100, offset: int = 0) -> Dict[str, Any]:
        """Get activity for a Polymarket wallet address."""
        return self._request("/polymarket/activity", params={
            "user": user, "market_slug": market_slug, "condition_id": condition_id,
            "start_time": start_time, "end_time": end_time, "limit": limit, "offset": offset,
        })

    def polymarket_orders(self, market_slug: Optional[str] = None, condition_id: Optional[str] = None,
                          token_id: Optional[str] = None, user: Optional[str] = None,
                          start_time: Optional[int] = None, end_time: Optional[int] = None,
                          limit: int = 100, offset: int = 0) -> Dict[str, Any]:
        """Get Polymarket trade history."""
        return self._request("/polymarket/orders", params={
            "market_slug": market_slug, "condition_id": condition_id, "token_id": token_id,
            "user": user, "start_time": start_time, "end_time": end_time, "limit": limit, "offset": offset,
        })

    def polymarket_orderbooks(self, token_id: str, start_time: Optional[int] = None,
                              end_time: Optional[int] = None, limit: int = 100,
                              pagination_key: Optional[str] = None) -> Dict[str, Any]:
        """Get Polymarket orderbook snapshots for a token.

        token_id comes from side_a.id or side_b.id in /polymarket/markets response.
        """
        return self._request("/polymarket/orderbooks", params={
            "token_id": token_id, "start_time": start_time, "end_time": end_time,
            "limit": limit, "pagination_key": pagination_key,
        })

    def polymarket_candlesticks(self, condition_id: str, start_time: int,
                                end_time: int, interval: int = 1) -> Dict[str, Any]:
        """Get Polymarket candlestick data. interval: 1=1m, 60=1h, 1440=1d.

        condition_id comes from /polymarket/markets response.
        """
        return self._request(f"/polymarket/candlesticks/{condition_id}", params={
            "start_time": start_time, "end_time": end_time, "interval": interval,
        })

    def polymarket_positions(self, wallet_address: str, limit: int = 100,
                             pagination_key: Optional[str] = None) -> Dict[str, Any]:
        """Get Polymarket positions for a proxy wallet."""
        return self._request(f"/polymarket/positions/wallet/{wallet_address}", params={
            "limit": limit, "pagination_key": pagination_key,
        })

    def polymarket_wallet(self, eoa: Optional[str] = None, proxy: Optional[str] = None,
                          with_metrics: bool = False, start_time: Optional[int] = None,
                          end_time: Optional[int] = None) -> Dict[str, Any]:
        """Get Polymarket wallet info. Provide either eoa or proxy."""
        return self._request("/polymarket/wallet", params={
            "eoa": eoa, "proxy": proxy,
            "with_metrics": "true" if with_metrics else None,
            "start_time": start_time, "end_time": end_time,
        })

    def polymarket_pnl(self, wallet_address: str, granularity: str,
                       start_time: Optional[int] = None, end_time: Optional[int] = None) -> Dict[str, Any]:
        """Get realized P&L for a Polymarket wallet."""
        return self._request(f"/polymarket/wallet/pnl/{wallet_address}", params={
            "granularity": granularity, "start_time": start_time, "end_time": end_time,
        })

    # ==================== Kalshi ====================

    def kalshi_markets(self, search: Optional[str] = None, market_ticker: Optional[List[str]] = None,
                       event_ticker: Optional[List[str]] = None, status: Optional[str] = None,
                       min_volume: Optional[float] = None, limit: int = 10, offset: int = 0) -> Dict[str, Any]:
        """Search/filter Kalshi markets.

        Use this endpoint first to obtain IDs needed by other endpoints:
          - market_ticker: from market_ticker in the response
        """
        url = f"{self.BASE_URL}/kalshi/markets"
        parts = []
        if search:
            parts.append(f"search={urllib.parse.quote(search)}")
        if status:
            parts.append(f"status={urllib.parse.quote(status)}")
        if min_volume is not None:
            parts.append(f"min_volume={min_volume}")
        parts.append(f"limit={limit}")
        parts.append(f"offset={offset}")
        for t in (market_ticker or []):
            parts.append(f"market_ticker={urllib.parse.quote(t)}")
        for t in (event_ticker or []):
            parts.append(f"event_ticker={urllib.parse.quote(t)}")
        if parts:
            url = f"{url}?{'&'.join(parts)}"
        return self._raw_get(url)

    def kalshi_price(self, market_ticker: str, at_time: Optional[int] = None) -> Dict[str, Any]:
        """Get current (or historical) price for a Kalshi market.

        market_ticker comes from /kalshi/markets response.
        """
        return self._request(f"/kalshi/market-price/{market_ticker}", params={"at_time": at_time})

    def kalshi_trades(self, ticker: Optional[str] = None, start_time: Optional[int] = None,
                      end_time: Optional[int] = None, limit: int = 100, offset: int = 0) -> Dict[str, Any]:
        """Get Kalshi trade history.

        ticker (market_ticker) comes from /kalshi/markets response.
        """
        return self._request("/kalshi/trades", params={
            "ticker": ticker, "start_time": start_time, "end_time": end_time,
            "limit": limit, "offset": offset,
        })

    def kalshi_orderbooks(self, ticker: str, start_time: Optional[int] = None,
                          end_time: Optional[int] = None, limit: int = 100) -> Dict[str, Any]:
        """Get Kalshi orderbook snapshots.

        ticker (market_ticker) comes from /kalshi/markets response.
        """
        return self._request("/kalshi/orderbooks", params={
            "ticker": ticker, "start_time": start_time, "end_time": end_time, "limit": limit,
        })

    # ==================== Cross-Platform ====================

    def sports_matching(self, polymarket_slugs: Optional[List[str]] = None,
                        kalshi_tickers: Optional[List[str]] = None) -> Dict[str, Any]:
        """Find equivalent sports markets across Polymarket and Kalshi.

        Provide either polymarket_market_slug(s) or kalshi_event_ticker(s)
        to find the matching market on the other platform.
        """
        url = f"{self.BASE_URL}/matching-markets/sports"
        parts = []
        for slug in (polymarket_slugs or []):
            parts.append(f"polymarket_market_slug={urllib.parse.quote(slug)}")
        for ticker in (kalshi_tickers or []):
            parts.append(f"kalshi_event_ticker={urllib.parse.quote(ticker)}")
        if parts:
            url = f"{url}?{'&'.join(parts)}"
        return self._raw_get(url)

    def sports_by_date(self, sport: str, date: str) -> Dict[str, Any]:
        """Find matching sports markets across platforms by sport and date (YYYY-MM-DD).

        Supported sports: nba, nfl, mlb, nhl, soccer, tennis.
        """
        return self._request(f"/matching-markets/sports/{sport}", params={"date": date})

    def _raw_get(self, url: str) -> Dict[str, Any]:
        """Make a GET request to a fully constructed URL."""
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "User-Agent": "OpenClaw-PredictionMarket/1.0",
        }
        req = urllib.request.Request(url, headers=headers, method="GET")
        try:
            with urllib.request.urlopen(req, timeout=60) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as e:
            error_body = e.read().decode("utf-8")
            try:
                return json.loads(error_body)
            except json.JSONDecodeError:
                return {"success": False, "error": {"code": str(e.code), "message": error_body}}
        except urllib.error.URLError as e:
            return {"success": False, "error": {"code": "NETWORK_ERROR", "message": str(e.reason)}}


def main():
    parser = argparse.ArgumentParser(
        description="Cross-Platform Prediction Market Data - Polymarket & Kalshi via AIsa API",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
ID Lookup:
  Most endpoints require an ID from the /markets response. Query markets first.
    Polymarket token_id:     side_a.id or side_b.id from /polymarket/markets
    Polymarket condition_id: condition_id from /polymarket/markets
    Kalshi market_ticker:    market_ticker from /kalshi/markets

Examples:
  %(prog)s polymarket markets --search "election" --status open
  %(prog)s polymarket price <token_id>
  %(prog)s polymarket activity --user <wallet>
  %(prog)s polymarket orders --market-slug <slug> --limit 50
  %(prog)s polymarket orderbooks --token-id <id>
  %(prog)s polymarket candlesticks <condition_id> --start 1700000000 --end 1700086400 --interval 60
  %(prog)s polymarket positions <wallet_address>
  %(prog)s polymarket wallet --eoa <addr> --with-metrics
  %(prog)s polymarket pnl <wallet_address> --granularity day
  %(prog)s kalshi markets --search "fed rate" --status open
  %(prog)s kalshi price <market_ticker>
  %(prog)s kalshi trades --ticker <ticker>
  %(prog)s kalshi orderbooks --ticker <ticker>
  %(prog)s sports matching --polymarket-slug <slug>
  %(prog)s sports by-date nba --date 2025-03-01
"""
    )

    subparsers = parser.add_subparsers(dest="platform", help="Platform")

    # Polymarket
    pm = subparsers.add_parser("polymarket", help="Polymarket commands")
    pm_sub = pm.add_subparsers(dest="command", help="Command")

    pm_markets = pm_sub.add_parser("markets", help="Search/filter Polymarket markets")
    pm_markets.add_argument("--search")
    pm_markets.add_argument("--market-slug", nargs="+")
    pm_markets.add_argument("--event-slug", nargs="+")
    pm_markets.add_argument("--condition-id", nargs="+")
    pm_markets.add_argument("--tags", nargs="+")
    pm_markets.add_argument("--status", choices=["open", "closed"])
    pm_markets.add_argument("--min-volume", type=float)
    pm_markets.add_argument("--limit", type=int, default=10)
    pm_markets.add_argument("--offset", type=int, default=0)
    pm_markets.add_argument("--start-time", type=int)
    pm_markets.add_argument("--end-time", type=int)

    pm_price = pm_sub.add_parser("price", help="Get price for a Polymarket token")
    pm_price.add_argument("token_id", help="token_id from side_a.id or side_b.id in /polymarket/markets")
    pm_price.add_argument("--at-time", type=int, help="Unix timestamp for historical price")

    pm_activity = pm_sub.add_parser("activity", help="Get activity for a Polymarket wallet")
    pm_activity.add_argument("--user", required=True, help="Wallet address")
    pm_activity.add_argument("--market-slug")
    pm_activity.add_argument("--condition-id")
    pm_activity.add_argument("--start-time", type=int)
    pm_activity.add_argument("--end-time", type=int)
    pm_activity.add_argument("--limit", type=int, default=100)
    pm_activity.add_argument("--offset", type=int, default=0)

    pm_orders = pm_sub.add_parser("orders", help="Get Polymarket trade history")
    pm_orders.add_argument("--market-slug")
    pm_orders.add_argument("--condition-id")
    pm_orders.add_argument("--token-id")
    pm_orders.add_argument("--user")
    pm_orders.add_argument("--start-time", type=int)
    pm_orders.add_argument("--end-time", type=int)
    pm_orders.add_argument("--limit", type=int, default=100)
    pm_orders.add_argument("--offset", type=int, default=0)

    pm_ob = pm_sub.add_parser("orderbooks", help="Get Polymarket orderbook snapshots")
    pm_ob.add_argument("--token-id", required=True, help="token_id from side_a.id or side_b.id in /polymarket/markets")
    pm_ob.add_argument("--start", type=int, dest="start_time")
    pm_ob.add_argument("--end", type=int, dest="end_time")
    pm_ob.add_argument("--limit", type=int, default=100)
    pm_ob.add_argument("--pagination-key")

    pm_candles = pm_sub.add_parser("candlesticks", help="Get Polymarket candlestick data")
    pm_candles.add_argument("condition_id", help="condition_id from /polymarket/markets")
    pm_candles.add_argument("--start", type=int, required=True, dest="start_time")
    pm_candles.add_argument("--end", type=int, required=True, dest="end_time")
    pm_candles.add_argument("--interval", type=int, default=1, choices=[1, 60, 1440],
                            help="1=1min, 60=1hr, 1440=1day")

    pm_pos = pm_sub.add_parser("positions", help="Get Polymarket positions for a wallet")
    pm_pos.add_argument("wallet_address", help="Proxy wallet address")
    pm_pos.add_argument("--limit", type=int, default=100)
    pm_pos.add_argument("--pagination-key")

    pm_wallet = pm_sub.add_parser("wallet", help="Get Polymarket wallet info")
    pm_wallet_group = pm_wallet.add_mutually_exclusive_group(required=True)
    pm_wallet_group.add_argument("--eoa", help="EOA wallet address")
    pm_wallet_group.add_argument("--proxy", help="Proxy wallet address")
    pm_wallet.add_argument("--with-metrics", action="store_true")
    pm_wallet.add_argument("--start-time", type=int)
    pm_wallet.add_argument("--end-time", type=int)

    pm_pnl = pm_sub.add_parser("pnl", help="Get Polymarket wallet P&L")
    pm_pnl.add_argument("wallet_address", help="Wallet address")
    pm_pnl.add_argument("--granularity", required=True, choices=["day", "week", "month"])
    pm_pnl.add_argument("--start-time", type=int)
    pm_pnl.add_argument("--end-time", type=int)

    # Kalshi
    ks = subparsers.add_parser("kalshi", help="Kalshi commands")
    ks_sub = ks.add_subparsers(dest="command", help="Command")

    ks_markets = ks_sub.add_parser("markets", help="Search/filter Kalshi markets")
    ks_markets.add_argument("--search")
    ks_markets.add_argument("--market-ticker", nargs="+")
    ks_markets.add_argument("--event-ticker", nargs="+")
    ks_markets.add_argument("--status", choices=["open", "closed"])
    ks_markets.add_argument("--min-volume", type=float)
    ks_markets.add_argument("--limit", type=int, default=10)
    ks_markets.add_argument("--offset", type=int, default=0)

    ks_price = ks_sub.add_parser("price", help="Get price for a Kalshi market")
    ks_price.add_argument("market_ticker", help="market_ticker from /kalshi/markets")
    ks_price.add_argument("--at-time", type=int, help="Unix timestamp for historical price")

    ks_trades = ks_sub.add_parser("trades", help="Get Kalshi trade history")
    ks_trades.add_argument("--ticker", help="market_ticker from /kalshi/markets")
    ks_trades.add_argument("--start-time", type=int)
    ks_trades.add_argument("--end-time", type=int)
    ks_trades.add_argument("--limit", type=int, default=100)
    ks_trades.add_argument("--offset", type=int, default=0)

    ks_ob = ks_sub.add_parser("orderbooks", help="Get Kalshi orderbook snapshots")
    ks_ob.add_argument("--ticker", required=True, help="market_ticker from /kalshi/markets")
    ks_ob.add_argument("--start", type=int, dest="start_time")
    ks_ob.add_argument("--end", type=int, dest="end_time")
    ks_ob.add_argument("--limit", type=int, default=100)

    # Sports (Cross-Platform)
    sp = subparsers.add_parser("sports", help="Cross-platform sports market commands")
    sp_sub = sp.add_subparsers(dest="command", help="Command")

    sp_match = sp_sub.add_parser("matching", help="Find equivalent markets across platforms")
    sp_match_group = sp_match.add_mutually_exclusive_group(required=True)
    sp_match_group.add_argument("--polymarket-slug", nargs="+", help="Polymarket market slug(s)")
    sp_match_group.add_argument("--kalshi-ticker", nargs="+", help="Kalshi event ticker(s)")

    sp_date = sp_sub.add_parser("by-date", help="Find matching sports markets by date")
    sp_date.add_argument("sport", choices=["nba", "nfl", "mlb", "nhl", "soccer", "tennis"])
    sp_date.add_argument("--date", required=True, help="Date in YYYY-MM-DD format")

    args = parser.parse_args()

    if not args.platform:
        parser.print_help()
        sys.exit(1)

    if not args.command:
        if args.platform == "polymarket":
            pm.print_help()
        elif args.platform == "kalshi":
            ks.print_help()
        elif args.platform == "sports":
            sp.print_help()
        sys.exit(1)

    try:
        client = PredictionMarketClient()
    except ValueError as e:
        print(json.dumps({"success": False, "error": {"code": "AUTH_ERROR", "message": str(e)}}))
        sys.exit(1)

    result = None

    if args.platform == "polymarket":
        if args.command == "markets":
            result = client.polymarket_markets(
                search=args.search, market_slug=args.market_slug, event_slug=args.event_slug,
                condition_id=args.condition_id, tags=args.tags, status=args.status,
                min_volume=args.min_volume, limit=args.limit, offset=args.offset,
                start_time=args.start_time, end_time=args.end_time,
            )
        elif args.command == "price":
            result = client.polymarket_price(args.token_id, at_time=args.at_time)
        elif args.command == "activity":
            result = client.polymarket_activity(
                user=args.user, market_slug=args.market_slug, condition_id=args.condition_id,
                start_time=args.start_time, end_time=args.end_time, limit=args.limit, offset=args.offset,
            )
        elif args.command == "orders":
            result = client.polymarket_orders(
                market_slug=args.market_slug, condition_id=args.condition_id, token_id=args.token_id,
                user=args.user, start_time=args.start_time, end_time=args.end_time,
                limit=args.limit, offset=args.offset,
            )
        elif args.command == "orderbooks":
            result = client.polymarket_orderbooks(
                token_id=args.token_id, start_time=args.start_time, end_time=args.end_time,
                limit=args.limit, pagination_key=args.pagination_key,
            )
        elif args.command == "candlesticks":
            result = client.polymarket_candlesticks(
                condition_id=args.condition_id, start_time=args.start_time,
                end_time=args.end_time, interval=args.interval,
            )
        elif args.command == "positions":
            result = client.polymarket_positions(
                wallet_address=args.wallet_address, limit=args.limit,
                pagination_key=args.pagination_key,
            )
        elif args.command == "wallet":
            result = client.polymarket_wallet(
                eoa=args.eoa, proxy=args.proxy, with_metrics=args.with_metrics,
                start_time=args.start_time, end_time=args.end_time,
            )
        elif args.command == "pnl":
            result = client.polymarket_pnl(
                wallet_address=args.wallet_address, granularity=args.granularity,
                start_time=args.start_time, end_time=args.end_time,
            )

    elif args.platform == "kalshi":
        if args.command == "markets":
            result = client.kalshi_markets(
                search=args.search, market_ticker=args.market_ticker, event_ticker=args.event_ticker,
                status=args.status, min_volume=args.min_volume, limit=args.limit, offset=args.offset,
            )
        elif args.command == "price":
            result = client.kalshi_price(args.market_ticker, at_time=args.at_time)
        elif args.command == "trades":
            result = client.kalshi_trades(
                ticker=args.ticker, start_time=args.start_time, end_time=args.end_time,
                limit=args.limit, offset=args.offset,
            )
        elif args.command == "orderbooks":
            result = client.kalshi_orderbooks(
                ticker=args.ticker, start_time=args.start_time,
                end_time=args.end_time, limit=args.limit,
            )

    elif args.platform == "sports":
        if args.command == "matching":
            result = client.sports_matching(
                polymarket_slugs=args.polymarket_slug, kalshi_tickers=args.kalshi_ticker,
            )
        elif args.command == "by-date":
            result = client.sports_by_date(sport=args.sport, date=args.date)

    if result is not None:
        output = json.dumps(result, indent=2, ensure_ascii=False)
        try:
            print(output)
        except UnicodeEncodeError:
            print(json.dumps(result, indent=2, ensure_ascii=True))
        sys.exit(0 if result.get("success", True) else 1)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)