Skip to content

API reference

The public API of the outcometick Python package, rendered from the docstrings in this repository. What a strategy can call while it runs — ctx, the event fields, every Order option — is in the SDK reference, which covers both languages.

Backtesting: outcometick.backtest

run

run(
    strategy,
    data,
    *,
    venue=None,
    assets=None,
    days=None,
    datasets=None,
    intervals=None,
    params=None,
    mode="market",
    latency_ms=0,
    fee_bps=None,
    seed=1,
    hooks=None,
    cancel_latency_ms=None,
    cache=True,
    workers=None,
    mark_max_age_ms=None,
    fok=False,
    order_rules=None,
    maker_rebate=None,
    quote_guard=None,
    mirror_depth=None,
    book_throttle=None,
    feed_latency=None,
    queue_model=None,
    record_ms=None,
) -> Result

Backtest strategy (a class) over an unpacked archive at data, or over your own data for any Polymarket binary market: a Dataset, or a directory with markets.json / markets.jsonl and CLOB frame files.

Generic data: every market in it is replayed (no assets / days / intervals); datasets are what the frames hold. A resolved market pays at its resolution time; an unresolved one is valued at the external mid (else last public trade) when trading ends; mark_max_age_ms refuses older marks (a leg with no usable mark is valued at 0, and the report says so).

Execution options (all off by default; same meaning as the manifest keys):

fok           True: orders may use tif="fok" (all within the limit, or none)
order_rules   "assumed" (archive Polymarket: tick 0.001, min 5 shares, sizes
              in 0.01) or "venue" (your own data: each market's own tick/min)
maker_rebate  share of the taker fee credited on resting fills (an estimate)
quote_guard   {"max_age_ms": n, "ask_sum": [lo, hi]}: opening taker orders
              only execute against fresh, consistent quotes
mirror_depth  "shared": on single-book markets one fill consumes both tokens
feed_latency  ms between an event on the venue and the strategy seeing it
              (hooks fire late and ctx.book() lags; orders still match the
              venue's book). on_timer + ctx.schedule() wake the strategy
              at chosen times.
queue_model   resting orders: where unexplained book decreases came from in
              the queue: {"prob": "power", "n": 2} (power/power2/power3,
              n 1..10) or {"prob": "log"} (log/log2). None: the conservative
              default (cancellations behind the order).
record_ms     the account's value every record_ms milliseconds (1000..3600000),
              each market's open legs at the best bid: Result.record,
              report["record"] (market mode only)
book_throttle False: replay the archive's book unthinned (local only); True
              or False also writes coverage.book_throttle

assets    default BTC, ETH, SOL, XRP (as `ot run`); name others to include them.
datasets  default: settlement and book (so orders can fill), plus trades
          when the class has on_trade. "bbo" (the top-of-book bound) comes with "book".
intervals default ["5m"]; Polymarket has 5m and 15m.
fee_bps   None = each market's venue schedule; a number = flat override.
cancel_latency_ms  how long ctx.cancel() takes to reach the venue (resting
          orders); None = the same as latency_ms. Resting (gtc) orders need
          datasets to include "book" and "trades", and Polymarket.
cache     keep decoded days under ~/.cache/outcometick/decoded (or a path),
          so the next run over the same days starts at once. False: never.
workers   None (default): decode on every CPU core, replay in this process;
          always the single-process result. A number: decode and replay
          markets on that many processes (market mode). Parallel replay
          relies on market mode's contract: a strategy keeps its state on
          the instance. State found on its class (nested classes included)
          or in params makes the run replay in one process instead.

run_template

run_template(
    path,
    data,
    *,
    venue=None,
    assets=None,
    days=None,
    seed=1,
    fee_bps=None,
    cache=True,
    workers=None,
) -> Result

Run a strategy directory (outcometick.json + source), like ot run.

sweep

sweep(
    strategy,
    data,
    grid,
    *,
    metric="net_pnl",
    maximize=None,
    holdout=0.0,
    top=3,
    days=None,
    params=None,
    cell_workers=None,
    **run_kwargs,
) -> SweepResult

Run strategy once per combination of grid values and rank the cells.

grid      {param: [values, ...]}, layered over `params` (or the class's own).
metric    a key of report["metrics"]; maximize defaults to True except for
          drawdown-, loss- and error-type metrics.
holdout   fraction of the days (the latest ones) kept out of the ranking;
          the `top` cells are then replayed on them. 0 = rank on everything.
cell_workers  None (default): one cell after another. A number: that many
          cells at once on forked processes; the same result (see the
          module docstring; needs fork, else sequential).
Everything else (venue, assets, datasets, latency_ms, workers, ...) is
passed to run() for every cell.

fingerprint

fingerprint(
    path,
    *,
    assets=None,
    days=None,
    venue="polymarket",
    seed=1,
    fee_bps=None,
    data=None,
    book_throttle=None,
    mark_max_age_ms=None,
) -> dict

A strategy directory's pre-registration fingerprint, without running it.

The same value ot fingerprint prints and the report of the matching run carries (report["fingerprint"]): one sha256 over the code, manifest, params, execution options, scope and seed. Pass the assets and the days list you will run (from = days[0], to = days[-1], as the report's scope); data, if given and your own dataset, makes it a generic-data fingerprint.

load_sample

load_sample(
    venue: str = "polymarket",
    *,
    cache_dir: str | None = None,
    refresh: bool = False,
) -> str

Download (once) and unpack the public sample for a venue; return its root.

venue: "polymarket" or "predict". The archive is ~100-300 MB.

Result

A finished backtest: the report plus every trade, fill and log line.

trades_df

trades_df()

Trades as a pandas DataFrame (needs pandas).

record_df

record_df()

The account on the record grid as a pandas DataFrame (needs pandas).

markets_df

markets_df()

Every market replayed, entered or not, as a pandas DataFrame (needs pandas).

fills_df

fills_df()

Fills as a pandas DataFrame (needs pandas).

plot

plot(ax=None)

Realised PnL curve, one point per closed trade (needs matplotlib).

SweepResult

Every cell's metrics, ranked, with the held-out replay of the top cells.

ranked property

ranked: list

Cells best first on the in-sample metric; a cell without a value sorts last.

table

table()

One row per cell: params, the in-sample metrics, and held-out ones where run (needs pandas).

heatmap

heatmap(x, y, ax=None)

The in-sample metric over two params (needs matplotlib).

Dataset

Generic data for run(): any Polymarket binary market, your own data.

Dataset.from_dir("my-data")                      # markets.json + *.jsonl frames
Dataset.from_frames(markets, frames)             # the same, in memory
Dataset.from_pmdata(l2, trades, market=gamma)    # single-book table shape

Datasets add up (a + b): one run over every market of both.

from_frames classmethod

from_frames(markets, frames)

markets: generic records and/or Gamma market objects. frames: CLOB market-channel frames (dicts, or JSON text lines), raw or as archive rows.

from_pmdata classmethod

from_pmdata(l2, trades=None, *, market=None)

The single-book l2 / trades tables (pandas, polars or lists of dicts).

market: a Gamma market object or generic record for this market (for its labels, fee schedule and resolution). Without it, the record is built from the l2 rows' condition_id / market_slug with labels ["Yes", "No"].

BacktestError

Bases: RuntimeError

The run was refused or could not produce a report. code is the same rejection code a queued run would report (E_MANIFEST, E_RUNTIME, ...).

Writing a strategy: outcometick

Strategy

Base class for submitted strategies.

Hooks have no default implementations, so missing declared hooks are rejected.

Order

An order returned by a hook for simulated execution by the runner.

limit is a price ceiling when opening and a floor when reducing.