DEV Community

Ais Fang
Ais Fang

Posted on

Monitoring Mark-Price Gap on Polymarket Perps with Python and WebSockets

If you trade perps on Polymarket, the single most important number on your position screen is not your entry price and not the last trade. It is the mark price, because that is the number the risk engine uses when it decides how much equity your position has left.

The docs spell out the formula — Equity = Collateral + unrealized PnL (at mark) − fees due − funding due — and the liquidation threshold follows from maintenance margin, which is 0.5 / MaxLeverage (2.5% on a 20x market). What the docs do not hand you is a ready-made monitor that tells you how far your position is from that line right now. This post builds one in Python.

I wrote a longer breakdown of why the mark price diverges from the index on prediction-market perps over at BrainResearchHub (mark vs index price, explained with worked examples): https://brainresearchhub.net/research/perps/mark-vs-index-price

Why the gap matters on prediction markets

On crypto perps the index comes from centralized exchanges and the mark tracks it tightly. On prediction-market perps the underlying "index" is derived from outcome probabilities, and the order book can be thin. When a news headline hits, the book reprices in seconds while the index construction lags behind. That window is exactly where:

  • unrealized PnL gets marked against a price you did not expect,
  • funding payments get computed from a premium you did not anticipate,
  • liquidation thresholds get tested sooner than a naive "I'm 10% away from my liquidation price" mental model suggests.

If you only watch the last traded price, you are reading the wrong gauge. The order book mechanics behind this are covered in depth here: https://brainresearchhub.net/research/perps/polymarket-clob-order-book

The monitor

The script below does four things:

  1. Connects to the market's public WebSocket stream for trades and book updates.
  2. Polls the REST endpoint for the current index and mark values.
  3. Computes the gap in basis points.
  4. Computes your distance to maintenance margin given your side, size, and leverage.
import asyncio, json, time, math
import aiohttp

REST = "https://clob.polymarket.com"
WS   = "wss://ws-subscriptions-clob.polymarket.com/ws/market"

TOKEN_ID   = "..."        # your market's clob token id
SIDE       = "BUY"        # your position side
SIZE       = 100.0        # position size
ENTRY      = 0.62         # your average entry in prob units
MAX_LEV    = 20.0

MMR = 0.5 / MAX_LEV       # maintenance margin ratio (2.5% at 20x)

def gap_bps(mark: float, index: float) -> float:
    if index == 0: return 0.0
    return (mark - index) / index * 10_000

def liq_distance(mark: float) -> float:
    """Rough distance to maintenance margin, in prob units."""
    if SIDE == "BUY":
        equity = (mark - ENTRY) * SIZE          # per-unit mark pnl
        floor  = MMR * mark * SIZE
        return (equity - floor) / SIZE
    equity = (ENTRY - mark) * SIZE
    floor  = MMR * mark * SIZE
    return (equity - floor) / SIZE

async def poll_mark(session):
    async with session.get(f"{REST}/prices-history",
                           params={"market": TOKEN_ID, "interval": "1m"}) as r:
        return await r.json()

async def main():
    async with aiohttp.ClientSession() as session:
        while True:
            try:
                data = await poll_mark(session)
                # prices-history returns a series; last point is our proxy for mark
                series = data.get("history", data if isinstance(data, list) else [])
                if series:
                    last = series[-1].get("p", 0.0)
                    d = liq_distance(last)
                    print(f"{time.strftime('%H:%M:%S')} mark~{last:.4f} "
                          f"liq-distance~{d:+.4f} prob-units  MMR={MMR:.3%}")
            except Exception as e:
                print("poll error:", e)
            await asyncio.sleep(5)

if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

A note on streams: the market WebSocket channel pushes book and price_change events, which is what you want for a book-weighted mark estimate rather than a last-trade proxy. Order types and time-in-force quirks that affect what your bot sees in the book are documented here: https://brainresearchhub.net/research/perps/order-types-tif-explained

Caveats I hit while building this

Nothing here is financial advice. Collateral on Polymarket is pUSD, parameters change, and the only source of truth is the official docs. This monitor is a read-only starting point — wire it into alerts, not into an auto-trader, until you have watched it through a few news cycles.

Top comments (0)