Skip to main content
This guide shows the smallest useful bot you can build on top of the PolyQuantLab signal stream. Reads /v1/arb/live every few seconds, filters to the two tiers we audit publicly:
  • tier == "logical" — math-guaranteed (yes_ask + no_ask < $1)
  • tier == "endgame" — last 0-120 s, > 2σ past strike, dual-oracle confirmed
…and submits the corresponding Polymarket order via the py-clob-client (Polymarket’s official SDK).
Backtest the loop against the public audit before betting capital. The /audit page shows the realised PnL of every signal tier — model EV vs reality, unedited.

Prerequisites

pip install httpx py-clob-client python-dotenv
.env:
POLYQUANTLAB_API_KEY=pql_live_xxx       # from /dashboard/api-keys
POLYMARKET_PRIVATE_KEY=0xabc...         # your Polygon wallet private key
POLYMARKET_PROXY_ADDRESS=0xdef...       # your Polymarket proxy address

The loop

bot.py
"""
Minimal auto-trader for PolyQuantLab logical + endgame signals.

Run with:  python bot.py
Stop with: Ctrl+C
"""

import os
import time
import logging
import httpx
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, OrderType

load_dotenv()
logging.basicConfig(level=logging.INFO, format="%(asctime)s  %(message)s")

PQL_API_KEY = os.environ["POLYQUANTLAB_API_KEY"]
PM_KEY      = os.environ["POLYMARKET_PRIVATE_KEY"]
PM_PROXY    = os.environ["POLYMARKET_PROXY_ADDRESS"]

# How much of your bankroll to put on each signal. Start tiny.
POSITION_SIZE_USDC = 5

# Don't trade signals where the order book is so thin that fill price
# beats our expected_pnl_per_share into negative territory.
MIN_NET_EV_PER_SHARE = 0.005   # $0.005/share = 0.5¢ after fees

# Avoid double-trading the same market.
seen_markets: set[str] = set()

pql = httpx.Client(
    base_url="https://api.polyquantlab.com",
    headers={"Authorization": f"Bearer {PQL_API_KEY}"},
    timeout=10.0,
)

pm = ClobClient(
    host="https://clob.polymarket.com",
    key=PM_KEY,
    chain_id=137,                # Polygon mainnet
    signature_type=1,            # email/magic-link proxy wallets
    funder=PM_PROXY,
)
pm.set_api_creds(pm.create_or_derive_api_creds())


def poll_and_trade() -> None:
    r = pql.get("/v1/arb/live", params={"min_edge_pp": 0, "limit": 200})
    r.raise_for_status()
    opps = r.json()["opportunities"]

    actionable = [
        o for o in opps
        if o["tier"] in ("logical", "endgame")
        and o["expected_pnl_per_share"] >= MIN_NET_EV_PER_SHARE
        and o["market_id"] not in seen_markets
    ]

    for o in actionable:
        size_shares = round(POSITION_SIZE_USDC / o["fill_price"], 2)
        side  = "BUY"
        # Use the YES token for BUY_YES / BUY_BOTH, NO token for BUY_NO.
        # Polymarket SDK takes a token_id; resolve from market_id.
        # (Markets list endpoint returns yes_token_id / no_token_id.)
        market = pql.get(f"/v1/markets/{o['market_id']}").json()
        token_id = (
            market["no_token_id"]
            if o["direction"] == "BUY_NO"
            else market["yes_token_id"]
        )

        order = OrderArgs(
            token_id=token_id,
            price=o["fill_price"],
            size=size_shares,
            side=side,
        )
        signed = pm.create_order(order)
        resp = pm.post_order(signed, OrderType.FOK)   # fill-or-kill

        logging.info(
            "submitted %s  tier=%s  size=%.2f @ $%.3f  edge=$%.4f/sh  resp=%s",
            o["market_id"][:8] + "…",
            o["tier"],
            size_shares,
            o["fill_price"],
            o["expected_pnl_per_share"],
            resp.get("status"),
        )
        seen_markets.add(o["market_id"])

        # For BUY_BOTH (logical) also buy the opposite leg.
        if o["direction"] == "BUY_BOTH":
            opp_token = market["no_token_id"] if token_id == market["yes_token_id"] else market["yes_token_id"]
            opp_price = o["no_ask"] if token_id == market["yes_token_id"] else o["yes_ask"]
            opp_size  = round(POSITION_SIZE_USDC / opp_price, 2)
            order2 = OrderArgs(
                token_id=opp_token, price=opp_price, size=opp_size, side=side
            )
            pm.post_order(pm.create_order(order2), OrderType.FOK)
            logging.info(
                "  +opposite leg  size=%.2f @ $%.3f (BUY_BOTH closing)",
                opp_size, opp_price,
            )


def main() -> None:
    logging.info("bot started — POLL every 3 s")
    while True:
        try:
            poll_and_trade()
        except httpx.HTTPError as e:
            logging.warning("PQL fetch failed: %s", e)
        except Exception as e:
            logging.exception("unexpected: %s", e)
        time.sleep(3)


if __name__ == "__main__":
    main()

What this gets you

  • ~3-second loop polling all live signals
  • Skip signals whose net EV is < $0.005/share (fees eat them)
  • Skip markets already traded this session
  • For BUY_BOTH (logical), automatically place the opposite leg too — that’s what locks the math guarantee

What it doesn’t handle

This is the minimum viable loop. Production-grade additions:
GapFix
Stop-loss / unwind on adverse fillsSubscribe /v1/snapshots WS and check positions every snapshot
Polymarket gas reservationsTrack MATIC balance; pause if < 0.5
Bankroll managementCap concurrent open positions; size by Kelly fraction
Idempotency on retryPersist seen_markets to disk / Redis
Risk gatesSanity-check expected_pnl_per_share / fill_price < 0.4 (avoid suspicious 10x edges that are usually wrong oracle data)

Reality check

Even on the math-guaranteed logical tier, you’re racing HFT. Expect 30-60% submission success in the first week as you tune latency. The audit page shows our detector’s record — your execution will be a few % lower than that. The same /v1/arb/live endpoint also returns tier="stable" and tier="stale" rows. We surface them because the dashboard reads the same JSON shape, but don’t trade them — they’re public-audit-confirmed as ~$0 EV after fees. The honest tier label is the most useful field on the whole response.