Example 29 of 32
Polymarket trading bot in Python: data, signal, backtest
Find the live market with /markets, poll its book with /orderbook and its prints with /events, test the rule on how past markets settled, and send the orders through Polymarket's own SDK; the API is the polled data side, with no push feed.
A trading bot is a loop: find the market, read what it can trade at, decide, send an order. This API is the data side of
that loop, and it is read-only. GET /v1/polymarket/markets finds the market trading now in one call and hands out the
two token ids every other call takes; /orderbook and
/events are the book and the prints the bot polls each tick, and
/settlements/bulk says how past markets ended, for the backtest. Orders
go to Polymarket itself, through its own SDK and your wallet. The full parameter list is on
Markets.
Quick answer
A 5-minute BTC window starts on a multiple of 300 seconds and its slug ends in that start, so the market trading now is one search.
# the 5-minute BTC market of 17:05 to 17:10 UTC on 2026-10-25, with its Up and Down token ids
curl -H "X-API-Key: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/markets?query=btc-updown-5m-1792947900&limit=1"Example response
Captured from the live API on 2026-10-25 at 17:08 UTC, 192 seconds into the window; the response is this one row.
token_ids lists Up, then Down, in the order of the market's outcomes, and end_date is the close, 17:10 UTC, in epoch
milliseconds. The next window's market, btc-updown-5m-1792948200, was already listed at the same moment.
[
{
"active": 1,
"closed": 0,
"condition_id": "0xa4dea4ae3cafa0c21d5026247807cdcd7929833e1089fc27917933b62278135f",
"end_date": 1792948200000,
"neg_risk": 1,
"slug": "btc-updown-5m-1792947900",
"tick_size": 0.01,
"token_ids": [
"71017960874711395588755286498180410277662891295563354128010828164009876221919",
"15264935561093061531769473839831866712081495406158977870773121572402304828303"
]
}
]Variations
The book, every tick. depth=5 keeps the touch and four levels behind it on each side. At 17:08:15 UTC Up asked
0.95 for 118.31 shares and bid 0.94 for 556.72, and as_of_ts put the snapshot 7.5 seconds behind the response.
# the Up token's best five asks and bids now, with the venue time of the snapshot
curl -H "X-API-Key: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/orderbook?token_id=71017960874711395588755286498180410277662891295563354128010828164009876221919&depth=5"The prints since the last tick. Start each window where the last one ended and end it a minute behind the clock;
windows are half-open, so no print comes twice. This two-minute window held 618 prints of the two tokens, with
has_more false; the last was a buy of 15 Up shares at 0.75 at 17:07:18 UTC.
# every print of both tokens from 17:05:18 to 17:07:18 UTC
curl -H "X-API-Key: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/events?token_ids=71017960874711395588755286498180410277662891295563354128010828164009876221919,15264935561093061531769473839831866712081495406158977870773121572402304828303&event_type=trade&from_ms=1792947918549&to_ms=1792948038549&limit=1000"Backtest the rule before it trades. The script's rule is the one
Backtest a Polymarket strategy on the real historical book
tests on a recorded day: in the last 60 seconds of a window, buy Up at the ask when it is between 0.55 and 0.90, and hold
to resolution. The payoff side of that test is how every market of the day ended, one call per day. For 2026-10-24 the
first row was btc-updown-5m-1792799700, which Down won, and at limit=5000 one page holds the whole day; that example
prices each entry against the day's recorded quotes in DuckDB.
# how the 5-minute BTC markets that resolved on 2026-10-24 ended, the first three
curl -H "X-API-Key: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/settlements/bulk?slug_prefix=btc-updown-5m-&from_ms=1792800000000&to_ms=1792886400000&limit=3"The loop in one script. It finds the market of the current window, then polls the Up book and both tokens' prints
three times, ten seconds apart, and calls place_order when the rule fires on a snapshot at most five seconds old.
place_order only prints the order: sending it is Polymarket's SDK's job.
pip install requests
export VIKE_API_KEY=...
python pm-trading-bot.py # real run: three polls, ten seconds apart
python pm-trading-bot.py --dry-run # prints the requests it would make, calls nothing"""The data side of a Polymarket trading bot: find the live 5-minute BTC market, poll its book and its prints, apply a rule, hand the order to your own executor.
Run: VIKE_API_KEY=... python pm-trading-bot.py (--dry-run lists the requests and makes none)
"""
import os
import sys
import time
from urllib.parse import urlencode
NAME = "pm-trading-bot"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv
TICKS = 3 # polls per run; a real bot keeps looping and moves to the next window's slug at each close
POLL_S = 10 # two requests a tick: 12 a minute, far inside the 200 a minute a key allows
# The rule the backtest example tests: in the last 60 s of the window, buy Up at the ask when it is 0.55 to 0.90.
LAST_S, LOW, HIGH = 60, 0.55, 0.90
MAX_SHARES = 20 # the most one order buys
MAX_BOOK_AGE_MS = 5000 # never act on an older snapshot, as the backtest refuses a stale quote
SAMPLE_TOKENS = [ # --dry-run has no response to read: the Up and Down tokens of btc-updown-5m-1792947900
"71017960874711395588755286498180410277662891295563354128010828164009876221919",
"15264935561093061531769473839831866712081495406158977870773121572402304828303",
]
def fail(why):
sys.exit(f"{NAME}: {why}")
def call(path, method="GET", **params):
url = BASE + path + ("?" + urlencode(params) if params else "")
if DRY:
print(method, url)
return None
key = os.environ.get("VIKE_API_KEY") or fail("set VIKE_API_KEY")
import requests
r = requests.request(method, url, headers={"X-API-Key": key}, timeout=60)
if r.status_code != 200:
fail(f"{method} {path} answered HTTP {r.status_code}: {r.text[:200]}")
return r.json()
def touch(book, side):
"""(price, size) at level 1 of one side, or None: a side can be empty, and a snapshot can repeat its rows."""
top = [r for r in book if r["side"] == side and r["level"] == 1]
return (top[0]["price"], top[0]["size"]) if top else None
def place_order(token_id, price, shares):
"""Execution is not this API's. Sign and send the order with Polymarket's official Python SDK
(pip install polymarket-client, its SecureClient) from your own wallet. This stub only prints it."""
print(f" would BUY {shares:g} Up at {price} (token {token_id[:12]}...)")
def main():
start = int(time.time()) // 300 * 300 # 5-minute windows start on multiples of 300 s; the slug ends in that start
slug = f"btc-updown-5m-{start}"
close_ms = (start + 300) * 1000
rows = call("/v1/polymarket/markets", query=slug, limit=1)
up, down = SAMPLE_TOKENS
if not DRY:
if not rows:
fail(f"{slug} is not in the market directory")
up, down = rows[0]["token_ids"] # in the order of the outcomes: Up, then Down
tape_from = int(time.time() * 1000) - 120_000
for _ in range(1 if DRY else TICKS):
now = int(time.time() * 1000)
# The tape trails the clock: end each window a minute back and start the next exactly where it ended.
tape_to = now - 60_000
book = call("/v1/polymarket/orderbook", token_id=up, depth=5)
tape = call("/v1/polymarket/events", token_ids=f"{up},{down}", event_type="trade",
from_ms=tape_from, to_ms=tape_to, limit=1000) # a busy window can need `cursor`
if DRY:
return
tape_from = tape_to
if not book:
fail(f"{slug}: no recorded book for the Up token")
ask, bid = touch(book, "ask"), touch(book, "bid")
age_ms = now - book[0]["as_of_ts"]
left_s = (close_ms - now) / 1000
prints = tape["events"]
last = (f"last a {prints[-1]['side']} of {'Up' if prints[-1]['token_id'] == up else 'Down'} "
f"at {prints[-1]['price']:.2f}") if prints else "none"
quote = " / ".join(f"{name} {lvl[0]:.2f} x {lvl[1]:g}" if lvl else f"no {name}" for name, lvl in (("bid", bid), ("ask", ask)))
print(f"{slug}: {left_s:.0f}s left, Up {quote}, book {age_ms / 1000:.1f}s old, {len(prints)} prints ({last})")
if 0 < left_s <= LAST_S and ask and LOW <= ask[0] <= HIGH and age_ms <= MAX_BOOK_AGE_MS:
place_order(up, ask[0], min(ask[1], MAX_SHARES))
time.sleep(POLL_S)
if __name__ == "__main__":
try:
main()
except Exception as e: # a shape the example does not expect fails by name, not by traceback
fail(f"{type(e).__name__}: {e}")Before you rely on it
Polling, not a feed
There is no Polymarket WebSocket in this API: the one WebSocket, /v1/stream, pushes closed
Hyperliquid candles only. The bot asks and the API answers. In 66 polls of 5-minute BTC books on 2026-10-25 the snapshot
was 0.8 to 11.8 seconds old when the answer arrived, and prints reach the tape in batches, which is why each tape window
ends a minute back. A rule fed by this API acts on seconds, not milliseconds. Two requests every ten seconds is 720 an hour, inside
the 20,000 an hour and 200 a minute a key allows.
The API never places an order
It is read-only and never holds a wallet or a venue key of yours. To trade, sign and send each order with Polymarket's
official Python SDK, polymarket-client, whose SecureClient
adds trading, as Place your first order shows, and start with the
smallest size. The script's place_order is where that call goes. For a push feed of the venue's own book, Polymarket
runs a market WebSocket.
Fees are not in the data
Nothing in the book, the tape or the archive carries fees. Polymarket's fee page on 2026-10-25 charged a taker shares × 0.07 × p × (1 − p) on Crypto markets: about 0.013 a share at 0.75 and 0.0175 at 0.50. Subtract it in the backtest and in the rule.
One market every five minutes
The script follows one window; at each close the slug moves on by 300, and the next market is listed before its window
opens. The winner settles on chain a minute or two after the close: btc-updown-5m-1792947900 closed at 17:10:00 UTC and
/settlements/{condition_id} reported Up the winner at 17:11:20. A snapshot can repeat its rows and can lack a side, so
find the touch by side and level, never by position in the array.
Ask your agent
Connected to the Vike MCP server (https://vike.io/mcp) in Claude, ChatGPT or Cursor, ask it in plain words. The
polymarket_orderbook tool answers one tick, at 1 credit a call, and
polymarket_tape the prints, also at 1. The loop and the orders stay in your code.
What are the best bid and ask of the Up token in Polymarket's current 5-minute BTC Up or Down market, and how old is that book?