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.
markets_df ¶
markets_df()
Every market replayed, entered or not, as a pandas DataFrame (needs pandas).
SweepResult ¶
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.