Before a Polymarket bot: build a paper data-and-payoff check

AI Trading Ranked

Last Updated: 2026-10-26

Start with a program that cannot submit orders. The runnable example below accepts a synthetic snapshot, rejects missing or stale inputs and calculates both settlement scenarios. It does not forecast the event, connect to an exchange or establish a profitable strategy. It needs only Python's standard library; no account, wallet, key or installation is required.

The earlier guide's near-expiry buy loop treated a 95–99-cent price as evidence that a contract would win. That is not resolution evidence. A 95% success rate at a 97-cent purchase price has expected gross loss of 2 cents per share before costs, even if that probability estimate were reliable. Winning outcome tokens and losing outcome tokens have different payouts; YES is not inherently the winning token.

Keep identity, evidence and execution separate

The official market-data overview distinguishes events, markets and outcome IDs. The resolution documentation says rules define the result; the headline alone does not. These sources were checked on October26. They do not validate the old article's SDK snippets, fee assumptions or alleged funded results.

Preserve the exact question, outcome ID, rule version, resolution source, timezone and observation time with each snapshot. A similar title or a high quote is insufficient. Store the raw response separately from normalized fields so a parser error can be investigated. Check the current API version and pagination contract before building a read-only collector; this article's fixture format is deliberately not represented as an API response.

Run a small, inspectable example

Save this code as paper_check.py and run python paper_check.py. It reads no credentials, makes no network requests and writes no files. The thirty-second freshness threshold is a test parameter, not a universal market-data standard. The rule reference is only a required field: this code does not authenticate or interpret the referenced rules.

from decimal import Decimal, InvalidOperation

def inspect(snapshot, now, max_age=30):
    # Teaching schema only: not a provider API response or trading signal.
    if not isinstance(snapshot, dict):
        return {"status": "invalid"}
    required = ("market", "outcome", "rules_ref", "observed_at")
    if any(not snapshot.get(k) for k in required):
        return {"status": "missing_identity_or_rules"}
    if snapshot["outcome"] not in ("YES", "NO"):
        return {"status": "invalid_outcome"}
    stamp = snapshot["observed_at"]
    if type(stamp) is not int or type(now) is not int:
        return {"status": "invalid_time"}
    if not 0 <= now - stamp <= max_age:
        return {"status": "stale_or_future"}
    try:
        ask = Decimal(str(snapshot["ask"]))
        shares = Decimal(str(snapshot["shares"]))
        costs = Decimal(str(snapshot["costs"]))
        values = (ask, shares, costs)
        if not all(x.is_finite() for x in values):
            return {"status": "invalid_number"}
        if not (Decimal("0.000001") <= ask < 1
                and Decimal("0.000001") <= shares <= 1000000
                and 0 <= costs <= 1000000):
            return {"status": "invalid_number"}
    except (KeyError, InvalidOperation, ValueError):
        return {"status": "invalid_number"}
    paid = ask * shares + costs
    return {
        "status": "paper_scenarios_only",
        "win_net": str(shares - paid),
        "lose_net": str(-paid),
        "break_even_probability": str(paid / shares),
        "probability_evidence": "not_established",
    }

sample = dict(market="synthetic-election", outcome="YES",
              rules_ref="fixture-rules-v1", observed_at=1000,
              ask="0.97", shares="100", costs="1")
if __name__ == "__main__":
    good = inspect(sample, 1010)
    assert good["win_net"] == "2.00"
    assert good["lose_net"] == "-98.00"
    assert good["break_even_probability"] == "0.98"
    assert inspect(sample, 1040)["status"] == "stale_or_future"
    assert inspect({**sample, "ask": "NaN"}, 1010)["status"] == "invalid_number"
    assert inspect({**sample, "rules_ref": ""}, 1010)["status"] == "missing_identity_or_rules"
    print(good)

The numeric bounds in the example limit extreme inputs for this demonstration; they are not exchange tick sizes, lot limits or accepted trading parameters.

The output says a hypothetical 100-share purchase at0.97 plus1 in total costs pays98: win profit2, loss98, and break-even probability0.98. It deliberately supplies no estimated probability or buy instruction. Costs are an input, not a hard-coded venue fee. If costs push break-even above1, the scenario cannot have positive expected value under the assumed binary payout.

Expand the test before expanding the system

Scroll sideways to see every column. Keyboard: focus the table region and use the arrow keys.

Failure fixtureExpected behavior
Missing rules or wrong outcome mappingNo usable research decision until identity is reconciled
Stale or future timestampPreserve the rejected observation; do not reuse the last signal
Empty book or insufficient displayed sizeReport unavailable or partial depth; no invented full fill
Replayed feed eventDeduplicate by a documented event identity; conflicting payloads need review
Connection gapMark the interval incomplete and rebuild from a verified snapshot
Process restart after haltKeep the halt until deliberate reconciliation; restart is not permission to resume

The example implements only the first two rows and numeric checks. Persistent storage, feed sequencing, deduplication, depth simulation and recovery are further engineering work, not secretly completed features. Test them with saved fixtures before attaching a data feed. A single process and local journal can make a research prototype easier to inspect than several services; neither architecture supplies an edge.

An execution adapter is a separate project

A real executor needs documented order identities, partial-fill accounting, balances, cancellations, permissions and unknown-outcome recovery. A timeout is not proof an order failed. Repeating an unconfirmed request can create a second order. Stopping new entries does not close inventory; cancelling an order does not erase an already completed fill. Check the documented order lifecycle when specifying those states, rather than adapting an unpinned SDK example by guesswork.

Nothing here authorizes funded testing or says a month of observation makes a system ready. Preserve denied or unavailable states, verify regional eligibility independently, and do not bypass access restrictions. The research-dossier guide covers sources and calibration; the strategy-mechanics guide explains inventory and incomplete-hedge risks.

Disclosure: AITR may receive commissions from marked links elsewhere. This guide has no funded-test record or return forecast. Trading can lose all committed capital; this is an offline engineering exercise, not financial advice.