Repo Layout
strategies/contains reusable strategy classes and configs.strategies/private/contains historical strategy modules that were once local-only. They remain in that namespace for import compatibility.backtests/contains flat public runner entrypoints.backtests/private/contains historical Telonex research runners that were once local-only. They are tracked as an archive, not as the recommended baseline smoke-test set.prediction_market_extensions/backtesting/contains shared runner plumbing, data-source adapters, timing, reporting, artifacts, and optimizer helpers.
The menu only considers flat backtests/*.py, backtests/*.ipynb,
backtests/private/*.py, and backtests/private/*.ipynb files. Python files
still need a run() function or EXPERIMENT object to appear. Subdirectories
under backtests/ should be support code only.
Current public Python runners:
backtests/polymarket_book_ema_crossover.pybacktests/polymarket_book_ema_optimizer.pybacktests/polymarket_book_joint_portfolio_runner.pybacktests/polymarket_beffer45_trade_replay_telonex.pybacktests/polymarket_btc_5m_late_favorite_taker_hold.pybacktests/polymarket_btc_5m_pair_arbitrage.pybacktests/polymarket_pmxt_book_100_replay_runner.pybacktests/polymarket_telonex_book_100_replay_runner.pybacktests/polymarket_telonex_book_joint_portfolio_runner.py
Current public notebook runners:
backtests/generic_optimizer_research.ipynbbacktests/generic_tpe_research.ipynbbacktests/polymarket_beffer45_trade_replay_telonex.ipynbbacktests/pmxt_book_joint_portfolio_runner.ipynbbacktests/telonex_book_joint_portfolio_runner.ipynb
These are research demos, not profitability claims. They are pinned to concrete markets, windows, sources, strategy configs, and execution assumptions so direct script runs stay reproducible.
Archived Private Research
The private/ namespace is now a historical research archive. The files remain
under backtests/private/ and strategies/private/ so old import paths and
saved model profiles keep working, but they are no longer hidden from git.
Treat them as study material and reproducibility aids, not as maintained trading
recommendations or guaranteed public smoke tests.
Tracked private strategies:
strategies/private/btc_snapshot_model.py: the BTC 5m snapshot model strategy used by the archived Nautilus replay validator and live sandbox runners.strategies/private/passive_pair_accumulation.py: a maker-first complementary-token strategy. It watches paired YES/NO books, posts post-only buy limits on both legs when the fee-adjusted combined passive cost is below settlement value, holds matched shares to resolution, and market-sells any unmatched surplus after a bounded completion window.
Tracked private cache helpers:
backtests/private/telonex_btc_5m_polymarket_download.pybacktests/private/telonex_binance_btc_download.py
The Polymarket helper downloads BTC 5m book_snapshot_full API-day files into
the Telonex cache layout. The Binance helper downloads spot trades,
book_snapshot_25, and optional quotes parquet days into the spot cache
layout used by the snapshot research runner. Both are direct CLI helpers with
main() entrypoints, and both require TELONEX_API_KEY in the environment or
.env.
Tracked BTC 5m snapshot model runners:
backtests/private/telonex_btc_5m_snapshot_model_research.py
backtests/private/telonex_btc_5m_snapshot_model_walkforward.py
backtests/private/telonex_btc_5m_snapshot_model_runner_validate.py
The research runner builds BTC 5m snapshot rows, fits the logistic model used by
the published live/models/btc_snapshot_model_*.json profiles, evaluates fixed
policy grids, and writes research artifacts under
output/telonex_churn/model_research/. The walk-forward runner consumes a
dataset CSV emitted by that research runner, refits the logistic model across
rolling train/validation/holdout folds, and writes fold-level CSV and JSON
summaries. The runner validator replays a selected model through Nautilus/Telonex
L2 chunks with the archived strategy, then writes chunk CSVs, fills/skips CSVs,
a summary JSON, and an HTML report.
Tracked BTC 5m strategy-family validators:
backtests/private/telonex_btc_5m_passive_pair_accumulation_search.pybacktests/private/telonex_btc_5m_passive_pair_forward_validate.pybacktests/private/telonex_btc_5m_passive_pair_chunked_forward_validate.pybacktests/private/telonex_btc_5m_late_favorite_chunked_forward_validate.pybacktests/private/telonex_btc_5m_microprice_imbalance_chunked_forward_validate.pybacktests/private/telonex_btc_5m_pair_arbitrage_chunked_forward_validate.py
The passive-pair search uses the archived
BookPassivePairAccumulationStrategy. The other validators reuse public
strategy classes such as late-favorite taker-hold, microprice imbalance, and
binary pair arbitrage with archived champion parameters and later BTC 5m
forward windows. Several of these modules share evaluation helpers from the
passive-pair search runner; that coupling is historical and intentionally kept
visible.
Tracked broader Telonex research runners:
backtests/private/telonex_general_market_value_rebound_search.pybacktests/private/telonex_resolved_sports_research.py
The general-market runner compares deep-value, panic-fade, and VWAP-reversion candidates across a configurable subset of the Telonex 100-market replay sample and defaults to 80 selected slugs split between train and holdout. The resolved-sports runner discovers recently resolved Polymarket sports markets and compares late-favorite or final-period-momentum candidates over their closing windows.
These archived runners usually require Telonex API access, local Telonex cache coverage, and enough memory for multi-market L2 replay. The expected local cache layouts for the BTC snapshot workflow are:
~/.cache/nautilus_trader/telonex/api-days/*/polymarket/book_snapshot_full/{slug}/outcome={Up,Down}/{date}.parquet
~/.cache/nautilus_trader/telonex-binance/raw/binance/{symbol}/trades/{date}.parquet
~/.cache/nautilus_trader/telonex-binance/raw/binance/{symbol}/book_snapshot_25/{date}.parquet
~/.cache/nautilus_trader/telonex-binance/raw/binance/{symbol}/quotes/{date}.parquet
The quote cache is only required for profiles trained with quote features.
Cross-asset profiles additionally require matching ethusdt, solusdt, or
xrpusdt spot caches.
All tracked private runners define run() and can appear in the backtest menu.
They are intentionally omitted from scripts/run_all_backtests.py, which only
targets first-class public runners under backtests/*.py.
A Telonex subscriber can use these files to approximate the original workflow:
hydrate the Polymarket and Binance caches for the dates and spot symbols in the
target model summary, run the research script with matching TELONEX_CHURN_*
environment settings, then run the walk-forward or runner-validation scripts
against the emitted dataset/model. Exact bit-for-bit reproduction still depends
on Telonex retaining the same historical files and on matching the original
environment choices recorded in the model JSON summaries and runner constants.
Runner Contract
Public Python runners are flat script entrypoints. Each runner exposes run()
and builds its concrete experiment inline inside that function. This keeps the
script import side effect small while preserving explicit inputs: platform,
vendor, source priority, replay windows, strategy config, execution model, and
report config are still visible in one file.
The menu discovers runner metadata by AST-scanning literal name= and
description= fields in build_replay_experiment(...) or
ParameterSearchExperiment(...). Do not hide those behind variables.
The canonical book-runner shape is:
from decimal import Decimal
if __package__ in {None, ""}:
from _script_helpers import ensure_repo_root
else:
from ._script_helpers import ensure_repo_root
ensure_repo_root(__file__)
from prediction_market_extensions.backtesting._execution_config import (
ExecutionModelConfig,
StaticLatencyConfig,
)
from prediction_market_extensions.backtesting._experiments import (
build_replay_experiment,
run_experiment,
)
from prediction_market_extensions.backtesting._prediction_market_backtest import (
MarketReportConfig,
)
from prediction_market_extensions.backtesting._prediction_market_runner import (
MarketDataConfig,
)
from prediction_market_extensions.backtesting._replay_specs import BookReplay
from prediction_market_extensions.backtesting._timing_harness import timing_harness
from prediction_market_extensions.backtesting.data_sources import Book, PMXT, Polymarket
@timing_harness
def run() -> None:
run_experiment(
build_replay_experiment(
name="polymarket_book_ema_crossover",
description="EMA crossover momentum on one Polymarket market using L2 book data",
data=MarketDataConfig(
platform=Polymarket,
data_type=Book,
vendor=PMXT,
sources=(
"local:/Volumes/storage/pmxt_data",
"archive:r2v2.pmxt.dev",
"archive:r2.pmxt.dev",
),
),
replays=(
BookReplay(
market_slug="market-slug",
token_index=0,
start_time="2026-03-19T07:35:57.277659Z",
end_time="2026-03-24T07:35:57.277659Z",
),
),
strategy_configs=[
{
"strategy_path": "strategies:BookEMACrossoverStrategy",
"config_path": "strategies:BookEMACrossoverConfig",
"config": {
"trade_size": Decimal("100"),
"fast_period": 64,
"slow_period": 256,
"entry_buffer": 0.0005,
"take_profit": 0.010,
"stop_loss": 0.010,
},
},
],
initial_cash=100.0,
probability_window=256,
min_book_events=500,
min_price_range=0.005,
execution=ExecutionModelConfig(
queue_position=True,
latency_model=StaticLatencyConfig(
base_latency_ms=75.0,
insert_latency_ms=10.0,
update_latency_ms=5.0,
cancel_latency_ms=5.0,
),
),
report=MarketReportConfig(
count_key="book_events",
count_label="Book Events",
pnl_label="PnL (pUSD)",
),
empty_message="No replays met the book requirements.",
)
)
Important contract details:
- Use
BookReplay, notQuoteReplayorTradeReplay. - Use
data_type=Book, whose string value is"book". - Gate replay coverage with
min_book_events. - PMXT and Telonex adapters emit Nautilus
OrderBookDeltasfor L2 MBP book state. TradeTickrecords are execution-only and are loaded by the replay adapter, not by runner strategy configs.- Public runners should preserve normal Nautilus output and timing output by default.
HTML And Report Modes
Per-market HTML report generation has been removed. Public runners now use the summary-report path when HTML output is needed.
The active HTML/report surface is inline MarketReportConfig with:
summary_report=Truesummary_report_path="output/<name>.html"summary_plot_panels=(...)return_summary_series=Trueonbuild_replay_experiment(...)
The summary report can still contain per-market rows and comparison panels. The removed behavior is separate one-file-per-market detail HTML generation.
Typical basket config:
build_replay_experiment(
...,
report=MarketReportConfig(
count_key="book_events",
count_label="Book Events",
pnl_label="PnL (pUSD)",
summary_report=True,
summary_report_path="output/polymarket_book_joint_portfolio_runner_joint_portfolio.html",
summary_plot_panels=(
"total_equity",
"equity",
"market_pnl",
"periodic_pnl",
"yes_price",
"allocation",
"total_drawdown",
"drawdown",
"total_rolling_sharpe",
"rolling_sharpe",
"total_cash_equity",
"cash_equity",
"monthly_returns",
"total_brier_advantage",
"brier_advantage",
),
),
)
Known panel ids live in Plotting.
Optimization Runners
Optimizer runners keep the same inline explicit-source style but build a
ParameterSearchExperiment. The inline ParameterSearchConfig pins:
- the base replay market and token
- train and holdout windows
- strategy payload with
__SEARCH__:<name>placeholders parameter_gridfor discrete random-grid valuesparameter_spacefor TPE ranges when Optuna is used- scoring, sampler, coverage, and holdout settings
The scoring objective is documented in Research.
Designing Good Runner Files
A runner should answer these questions directly in code:
- Which platform is being replayed?
- Which data type is being used?
- Which vendor supplies that data?
- Which source priority should be used?
- Which market or basket is being replayed?
- Which strategy config is bound into the run?
- Which capital, latency, and queue-position assumptions apply?
- Which report, if any, should be emitted?
Keep shared mechanics out of runner files. The inline inputs should still be obvious:
MarketDataConfigselects platform, data type, vendor, and source priority.BookReplayselects the instrument basket and windows.strategy_configsbinds strategy classes and parameters.ExecutionModelConfigholds queue-position and latency assumptions.MarketReportConfigcontrols terminal and HTML reporting.build_replay_experiment(...)owns cash, probability window,min_book_events, price-range filters, and log level.
Multi-Market Strategy Configs
The executor supports one strategy instance per replay or one batch-level strategy config that references the full basket.
Useful sentinels:
__SIM_INSTRUMENT_ID__binds to the current replay instrument.__ALL_SIM_INSTRUMENT_IDS__binds to every loaded replay instrument.__SIM_METADATA__:<key>binds metadata fromBookReplay.metadata.
This lets a runner keep the replay basket and strategy payload explicit inside
the build_replay_experiment(...) call without reintroducing module-level
configuration constants.
Running Backtests
Interactive menu:
make backtest
Equivalent direct menu command:
uv run python main.py
Direct runner execution is usually better once you know what you want:
uv run python backtests/polymarket_book_ema_crossover.py
uv run python backtests/polymarket_book_ema_optimizer.py
uv run python backtests/polymarket_book_joint_portfolio_runner.py
uv run python backtests/polymarket_telonex_book_joint_portfolio_runner.py
To run every public Python backtest entrypoint:
uv run python scripts/run_all_backtests.py
The menu discovers flat Python and notebook runners. It uses Textual, supports
filtering with /, and shows the current runner file contents so you can
inspect the exact experiment before launching it.
Notebook Runners
Flat notebook files under backtests/ and backtests/private/ are valid menu
entrypoints.
Notebook contract:
- The notebook must live at a flat discoverable path.
- Optional metadata under
metadata.prediction_market_backtestcan setnameanddescription. - If metadata is absent, the menu falls back to the filename and first markdown heading.
- Execution happens from the repo root through
nbclient.
Public notebooks:
backtests/generic_optimizer_research.ipynbbacktests/generic_tpe_research.ipynbbacktests/polymarket_beffer45_trade_replay_telonex.ipynbbacktests/pmxt_book_joint_portfolio_runner.ipynbbacktests/telonex_book_joint_portfolio_runner.ipynb
Editing Runner Inputs
The public runner layer does not depend on shell env vars for experiment
definition. Edit the runner file, or copy it into backtests/private/, when
you want a different market, source priority, strategy, cash value, latency
model, or report path.
Primary edit surface:
- the
MarketDataConfig(...)passed asdata - the
BookReplay(...)entries passed asreplays - the
strategy_configs=(...)tuple - the
ExecutionModelConfig(...)passed asexecution - the
MarketReportConfig(...)passed asreport - the optimizer or experiment windows passed to the runner factory
Low-level env vars still exist for custom workflows:
POLYMARKET_GAMMA_BASE_URLPOLYMARKET_TRADE_API_BASE_URLPOLYMARKET_CLOB_BASE_URLPMXT_RAW_ROOTPMXT_REMOTE_BASE_URLPMXT_CACHE_DIRPMXT_DISABLE_CACHETELONEX_LOCAL_DIRTELONEX_API_BASE_URLTELONEX_API_KEYTELONEX_CHANNELTELONEX_CACHE_ROOTBACKTEST_ENABLE_TIMING=0
Data Vendor Notes
Native Vendors
The public runner surface is focused on Polymarket book replay through PMXT and Telonex. Native source env vars remain available for lower-level extension work, but public direct-runner examples should not reintroduce standalone trade-tick replay.
PMXT
- PMXT is the raw-hourly Polymarket L2 vendor path.
- Public runners usually list
local:/Volumes/storage/pmxt_datafirst, thenarchive:r2v2.pmxt.dev, thenarchive:r2.pmxt.dev. - PMXT source parsing is strict: only
local:andarchive:entries are supported inMarketDataConfig.sources. - The local filtered cache is enabled by default at
~/.cache/nautilus_trader/pmxt. - Timing output is enabled by default unless
BACKTEST_ENABLE_TIMING=0is set.
Telonex
- Telonex is the full-snapshot Polymarket L2 vendor path.
- Public Telonex runners use
data_type=Book,vendor=Telonex, andbook_snapshot_full. - Telonex source parsing accepts
local:andapi:only. - Public Telonex runners list
api:${TELONEX_API_KEY}first, thenlocal:/Volumes/storage/telonex_dataas the standard local mirror fallback. api:readsTELONEX_API_KEYfrom the environment or fromapi:<key>in runner source config. Do not commit private keys.- API-day payloads are cached by default at
~/.cache/nautilus_trader/telonex.
For vendor-specific behavior and timings, use: