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"
walletType = "ethereum"
allowedExecutionModes = ["auto", "manual"]
imageUrl = "https://cdn.circuit.org/agents/default"
runtimeIntervalMinutes = 60
version = "0.0.1"

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

Example

import json
import requests
from agent_sdk import Agent, AgentContext
from agent_sdk.agent_context import CurrentPosition

BUY_AMOUNT = 5  # USD to spend per cycle

# Polygon USDC address
USDC = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"

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(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:
        print(f"Failed to fetch markets: {e}")

    return None

def run(agent: AgentContext) -> None:
    # 1. Redeem any settled positions
    redemption = agent.platforms.polymarket.redeem_positions()
    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.valueUsd}")

    # 2. Check current positions and log Polymarket portfolio
    result = agent.get_current_positions()
    if not result.success or not result.data:
        agent.log(result.error or "Failed to get positions", error=True)
        return

    for pos in result.data.positions:
        if pos.polymarketMetadata:
            pm = pos.polymarketMetadata
            agent.log(f"{pm.question} [{pm.outcome}]: ${pm.valueUsd} (PnL: ${pm.pnlUsd})")

    # 3. Find USDC balance
    usdc = next(
        (p for p in result.data.positions if p.assetAddress.lower() == USDC),
        None,
    )
    usdc_balance = float(usdc.currentQty) / 1e6 if usdc else 0
    agent.log(f"USDC balance: ${usdc_balance:.2f}")

    if usdc_balance < BUY_AMOUNT:
        agent.log("Not enough USDC to buy")
        return

    # 4. Find the most liquid sports market (with order book liquidity check)
    market = find_liquid_sports_market(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.orderInfo
        if info.totalPriceUsd:
            agent.log(f"Bought shares: ${info.totalPriceUsd} at ${info.priceUsd}/share (order {info.orderId})")
        else:
            agent.log(f"Order placed: {info.orderId} ({info.size} shares, {info.side})")
    else:
        agent.log(f"Buy failed: {buy.error}", error=True)

def unwind(agent: AgentContext, positions: list[CurrentPosition]) -> None:
    # Sell all Polymarket positions
    result = agent.get_current_positions()
    if not result.success or not result.data:
        agent.log("Failed to get positions for unwind", error=True)
        return

    sold = 0
    for pos in result.data.positions:
        if not pos.polymarketMetadata:
            continue

        token_id = pos.tokenId
        if not token_id:
            continue

        shares = float(pos.currentQty) / 1e6  # Convert from raw units
        if shares <= 0:
            continue

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

        if sell.success and sell.data:
            pm = pos.polymarketMetadata
            agent.log(f"Sold {pm.question} ({pm.outcome}): ${sell.data.orderInfo.totalPriceUsd}")
            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 any settled positions
    agent.platforms.polymarket.redeem_positions()

agent = Agent(run_function=run, unwind_function=unwind)

handler = agent.get_handler()

if __name__ == "__main__":
    agent.run()

Sample Output

Agent run started
Redeemed 1 settled positions
  Will Spain win the 2026 FIFA World Cup? (Yes): $5.12
USDC 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: Claims winnings from any resolved markets
  2. Log portfolio: Shows current Polymarket positions with PnL
  3. Check balance: Finds USDC 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 USDC on Polygon (ethereum:137). 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() to claim winnings after markets resolve. It’s good practice to call it at the start of each run cycle.