Skip to main content
Scaffold this example locally and test it with the CLI:
circuit new --name my-polymarket-agent --language python --template polymarket
cd my-polymarket-agent
circuit run    # execute a run cycle
circuit unwind # test the unwind logic

circuit.toml

circuit.toml
name = "Example Polymarket Agent"
tagline = "Trades on Polymarket markets"
category = "prediction"
imageUrl = "https://cdn.circuit.org/agents/default"
walletType = "ethereum"
allowedExecutionModes = ["auto", "manual"]
runtimeIntervalMinutes = 60

[startingAsset]
network = "ethereum:137" # Polygon
address = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb" # pUSD on Polygon
minimumAmount = "5000000" # 5 pUSD (6 decimals)

Example

import json
import requests
from circuit_sdk import AgentContext, SessionPortfolio

BUY_AMOUNT = 5  # USD to spend per cycle

# Polygon pUSD address
PUSD = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb"

CLOB_API = "https://clob.polymarket.com"

def check_order_book_liquidity(token_id: str, side: str, min_usd: float) -> bool:
    """Check that the CLOB order book has enough liquidity for our order."""
    try:
        response = requests.get(
            f"{CLOB_API}/book",
            params={"token_id": token_id},
            timeout=10,
        )
        if not response.ok:
            return False

        book = response.json()
        orders = book.get("asks" if side == "BUY" else "bids", [])

        total_usd = 0
        for order in orders:
            price = float(order["price"])
            size = float(order["size"])
            total_usd += price * size

        return total_usd >= min_usd
    except Exception:
        return False

def find_liquid_sports_market(agent: AgentContext, min_liquidity_usd: float) -> dict | None:
    """Find the most liquid active sports market on Polymarket via the Gamma API."""
    try:
        response = requests.get(
            "https://gamma-api.polymarket.com/events/pagination",
            params={
                "tag_slug": "sports",
                "limit": 10,
                "active": "true",
                "closed": "false",
                "archived": "false",
                "order": "volume24hr",
                "ascending": "false",
            },
            timeout=10,
        )
        events = response.json().get("data", [])

        for event in events:
            for market in event.get("markets", []):
                if not market.get("acceptingOrders"):
                    continue

                clob_token_ids = json.loads(market.get("clobTokenIds", "[]"))
                outcomes = json.loads(market.get("outcomes", "[]"))
                outcome_prices = json.loads(market.get("outcomePrices", "[]"))

                if not clob_token_ids or not outcomes:
                    continue

                price = float(outcome_prices[0]) if outcome_prices else 0

                # Skip markets with extreme prices (thin liquidity on one side)
                if price < 0.10 or price > 0.90:
                    continue

                token_id = clob_token_ids[0]

                # Verify order book has enough ask liquidity for our buy
                if not check_order_book_liquidity(token_id, "BUY", min_liquidity_usd):
                    continue

                return {
                    "tokenId": token_id,
                    "question": market.get("question", "Unknown"),
                    "outcome": outcomes[0],
                    "price": outcome_prices[0] if outcome_prices else "?",
                }

    except Exception as e:
        agent.log(f"Failed to fetch markets: {e}", error=True)

    return None

def run(agent: AgentContext) -> None:
    # 1. Redeem this agent's settled positions (filter own portfolio, pass explicit ids)
    redeemable_token_ids = [
        pos.polymarket_metadata.token_id
        for pos in agent.portfolio.positions
        if pos.polymarket_metadata
        and pos.polymarket_metadata.is_redeemable
        and pos.polymarket_metadata.token_id
    ]
    if redeemable_token_ids:
        redemption = agent.platforms.polymarket.redeem_positions({"tokenIds": redeemable_token_ids})
        if redemption.success and redemption.data:
            redeemed = [r for r in redemption.data if r.success]
            if redeemed:
                agent.log(f"Redeemed {len(redeemed)} settled positions")
                for r in redeemed:
                    if r.position:
                        agent.log(f"  {r.position.question} ({r.position.outcome}): ${r.position.value_usd}")

    # 2. Read holdings and log Polymarket portfolio
    for pos in agent.portfolio.positions:
        if pos.polymarket_metadata:
            pm = pos.polymarket_metadata
            agent.log(f"{pm.question} [{pm.outcome}]: ${pm.value_usd} (PnL: ${pm.pnl_usd})")

    # 3. Find pUSD balance (pUSD cash lives in balances, not positions)
    pusd = next(
        (b for b in agent.portfolio.balances if b.token_address.lower() == PUSD),
        None,
    )
    pusd_balance = float(pusd.amount_raw) / 1e6 if pusd else 0
    agent.log(f"pUSD balance: ${pusd_balance:.2f}")

    if pusd_balance < BUY_AMOUNT:
        agent.log("Not enough pUSD to buy")
        return

    # 4. Find the most liquid sports market (with order book liquidity check)
    market = find_liquid_sports_market(agent, min_liquidity_usd=BUY_AMOUNT)
    if not market:
        agent.log("No active sports markets with sufficient liquidity found")
        return

    agent.log(f"Target market: {market['question']} [{market['outcome']}] @ {market['price']}")

    # 5. Buy a position
    buy = agent.platforms.polymarket.market_order({
        "tokenId": market["tokenId"],
        "size": BUY_AMOUNT,
        "side": "BUY",
    })

    if buy.success and buy.data:
        info = buy.data.order_info
        if info.total_price_usd:
            agent.log(f"Bought shares: ${info.total_price_usd} at ${info.price_usd}/share (order {info.order_id})")
        else:
            agent.log(f"Order placed: {info.order_id} ({info.size} shares, {info.side})")
    else:
        agent.log(f"Buy failed: {buy.error}", error=True)

def unwind(agent: AgentContext, portfolio: SessionPortfolio) -> None:
    # Sell all Polymarket positions
    sold = 0
    for pos in portfolio.positions:
        pm = pos.polymarket_metadata
        if not pm:
            continue

        token_id = pm.token_id
        if not token_id:
            continue

        shares = float(pm.formatted_shares)  # Human-readable share count
        if shares <= 0:
            continue

        sell = agent.platforms.polymarket.market_order({
            "tokenId": token_id,
            "size": shares,
            "side": "SELL",
        })

        if sell.success and sell.data:
            agent.log(f"Sold {pm.question} ({pm.outcome}): ${sell.data.order_info.total_price_usd}")
            sold += 1
        else:
            agent.log(f"Failed to sell position: {sell.error}", error=True)

    if sold == 0:
        agent.log("No Polymarket positions to sell")

    # Redeem this agent's settled positions
    redeemable_token_ids = [
        pos.polymarket_metadata.token_id
        for pos in portfolio.positions
        if pos.polymarket_metadata
        and pos.polymarket_metadata.is_redeemable
        and pos.polymarket_metadata.token_id
    ]
    if redeemable_token_ids:
        agent.platforms.polymarket.redeem_positions({"tokenIds": redeemable_token_ids})

Sample Output

Agent run started
Redeemed 1 settled positions
  Will Spain win the 2026 FIFA World Cup? (Yes): $5.12
pUSD balance: $12.34
Target market: Will Argentina win Copa America 2025? [Yes] @ 0.62
Bought shares: $5.00 at $0.62/share (order 8837291045)
Agent run completed
Agent unwind started
Sold 8.06 shares of token 123456... (order 9912345678)
Redeemed 1 settled positions
Agent unwind completed

How It Works

  1. Redeem settled positions: Filters the agent’s own portfolio for redeemable positions and claims their winnings
  2. Log portfolio: Shows current Polymarket positions with PnL
  3. Check balance: Finds pUSD balance from current positions
  4. Find market: Queries the Gamma API for liquid sports markets, filters by price range (0.10-0.90), and verifies order book depth via the CLOB API
  5. Buy shares: Places a market buy order on the best available market
  6. Unwind: Sells all open Polymarket positions and redeems settled ones

Notes

  • Always check order book liquidity before calling marketOrder. The CLOB will error if there aren’t enough asks/bids to fill your order. See Troubleshooting for details.
  • Polymarket uses pUSD on Polygon (ethereum:137) as trading collateral. Set your startingAsset accordingly.
  • size means different things for buys vs sells: USD amount for BUY, share count for SELL.
  • Token IDs are long numeric strings from Polymarket’s API — they identify a specific outcome (e.g., “Yes” or “No”) in a market.
  • Use redeemPositions({ tokenIds }) to claim winnings after markets resolve — filter your portfolio for isRedeemable positions and pass their token IDs. It’s good practice to do this at the start of each run cycle.