vike
GuidesPolymarket

Find a market and check its tape

Search Polymarket markets by slug, take the first hit's condition and token ids, and ask whether that outcome token has a recorded tape.

Almost every Polymarket route needs an id that only /markets hands out. This recipe searches by slug, then carries the ids through the two routes that use them: /markets/{condition_id} takes the condition_id, and /market_stats takes a token_id.

Run
export VIKE_API_KEY=...
python pm-find-market.py             # real run: needs the key
python pm-find-market.py --dry-run   # prints the requests it would make, calls nothing

It prints the three latest-ending markets whose slug contains bitcoin, the first one's tick size and token count, and that token's event and trade counts with the span of the recorded tape.

pm-find-market.py
"""Search Polymarket markets by slug, then check whether the first hit's outcome token has a recorded tape.

Run:  VIKE_API_KEY=... python pm-find-market.py    (--dry-run lists the requests and makes none)
"""
import os
import sys
from datetime import datetime, timezone
from urllib.parse import urlencode

NAME = "pm-find-market"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv


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 when(ms):
    return datetime.fromtimestamp(ms / 1000, timezone.utc).strftime("%Y-%m-%d %H:%M UTC")


def main():
    # A bare array, latest end_date first. Every other Polymarket route wants an id only this one hands out.
    hits = call("/v1/polymarket/markets", query="bitcoin", limit=3)
    cid = "0x7b9072e6a9cdcf022c4f098564e8ee612d544e179e4a33dcc2790fbaa778daa8"  # --dry-run has no response to read
    if not DRY:
        if not hits:
            fail("no market slug contains 'bitcoin'")
        for m in hits:
            print(f"{m['slug']}  closed={m['closed']}  ends {when(m['end_date'])}")
        cid = hits[0]["condition_id"]

    # A one-element array, not an object; an unknown id is [] with a 200, so test the length.
    detail = call(f"/v1/polymarket/markets/{cid}")
    token = "95532524407959903145510583684979049735028502452973932716346555811355282494630"
    if not DRY:
        if not detail:
            fail(f"{cid} is not in the directory")
        token = detail[0]["token_ids"][0]
        print(f"condition_id {cid}  tick_size {detail[0]['tick_size']}  tokens {len(detail[0]['token_ids'])}")

    # One row always comes back; total_events == 0 is how "never recorded" reads.
    stats = call("/v1/polymarket/market_stats", token_id=token)
    if DRY:
        return
    s = stats[0]
    if s["total_events"] == 0:
        print(f"token {token[:12]}...: no recorded tape")
    else:
        print(f"token {token[:12]}...: {s['total_events']} events, {s['trades']} trades, tape {when(s['first_ms'])} to {when(s['last_ms'])}")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:  # a shape the recipe does not expect fails by name, not by traceback
        fail(f"{type(e).__name__}: {e}")

Read it this way

  • Search is a case-insensitive substring match on the slug. Results come back latest end_date first, with a cap of 100 rows and no cursor: to reach further, narrow the slug rather than page.
  • Detail is a one-element array. An unknown condition_id is [] with a 200, not a 404, so the script tests the length. A token_id in that path also returns [].
  • market_stats always returns one row. A token that was never recorded reads as total_events: 0, which the script prints as "no recorded tape".

last_ms is where the recording ends, not now

The span the script prints is the range /events can serve for that token. A request aimed at the current hour for a token whose tape ended weeks ago returns nothing, while a request inside the span returns plenty. Read the span before asking for the tape.

On this page