IMC Prosperity Backtester written in Rust for Blazingly Fast Backtest runs.
Rust Backtester
This repo is a self-contained Rust backtester for IMC Prosperity 4.
It only supports local backtesting. There is no API surface and no hosted workflow in this repo.
Everything needed for the default backtest flow now lives inside this directory. The bundled default trader is:
traders/latest_trader.py
traders/allproductstrader.py
Setup
Clone the repo:
git clone https://github.com/GeyzsoN/prosperityrustbacktester.git
cd prosperityrustbacktester
macOS
Install the toolchain once:
xcode-select --install
curl https://sh.rustup.rs -sSf | sh
source "$HOME/.cargo/env"
python3 --version
For normal end-user usage, install the published CLI from crates.io:
cargo install rust_backtester --locked
Then run it directly:
rust_backtester --help
rust_backtester
If rust_backtester is not found after install, make sure ~/.cargo/bin is on your PATH.
If rustbacktester cannot be executed after install, make sure that ~/scripts/cargolocal.sh is executable: <pre><code class="lang-">ls -la scripts/
sed -i 's/\r$//' scripts/cargo_local.sh
chmod +x scripts/cargo_local.sh</code></pre>
For local development from this repo, either install the local CLI:
<pre><code class="lang-bash">make install</code></pre>
or run the backtester directly from source:
<pre><code class="lang-bash">make backtest</code></pre>
For the fastest local-source runs, prefer:
<pre><code class="lang-bash">cargo run --release -- --help</code></pre>
The macOS make targets intentionally build through a wrapper instead of your full shell environment. By default they write Rust build artifacts to:
<pre><code class="lang-bash">~/Library/Caches/rust_backtester/target</code></pre>
If you want a different target dir, override it explicitly:
<pre><code class="lang-bash">CARGOTARGETDIR=/path/to/target make build-release</code></pre>
Windows
Use WSL2. Open an Ubuntu shell inside WSL2 and run the same commands there. Native Windows shells are not the target environment for this repo.
There is no separate manual build step required for normal use:
- make backtest
and the othermakerun targets usecargo run, which builds automatically on first use - cargo install rustbacktester --locked
installs the published release binary so you can runrustbacktesterdirectly afterward - make install
installs the CLI once so you can runrust_backtesterdirectly afterward - make doctor
prints local diagnostics for macOS build hangs and execution-policy issues
Included Data
The repo is organized by round:
- datasets/tutorial/pricesround0day-2.csv
- datasets/tutorial/tradesround0day-2.csv
- datasets/tutorial/pricesround0day-1.csv
- datasets/tutorial/tradesround0day-1.csv
- datasets/tutorial/submission.log
- datasets/round1/pricesround1day-2.csv
- datasets/round1/tradesround1day-2.csv
- datasets/round1/pricesround1day-1.csv
- datasets/round1/tradesround1day-1.csv
- datasets/round1/pricesround1day0.csv
- datasets/round1/tradesround1day0.csv
- datasets/round2/pricesround2day-1.csv
- datasets/round2/tradesround2day-1.csv
- datasets/round2/pricesround2day0.csv
- datasets/round2/tradesround2day0.csv
- datasets/round2/pricesround2day1.csv
- datasets/round2/tradesround2day1.csv
- datasets/round3/pricesround3day0.csv
- datasets/round3/tradesround3day0.csv
- datasets/round3/pricesround3day1.csv
- datasets/round3/tradesround3day1.csv
- datasets/round3/pricesround3day2.csv
- datasets/round3/tradesround3day2.csv
- datasets/round4/pricesround4day1.csv
- datasets/round4/tradesround4day1.csv
- datasets/round4/pricesround4day2.csv
- datasets/round4/tradesround4day2.csv
- datasets/round4/pricesround4day3.csv
- datasets/round4/tradesround4day3.csv
- datasets/round5/pricesround5day2.csv
- datasets/round5/tradesround5day2.csv
- datasets/round5/pricesround5day3.csv
- datasets/round5/tradesround5day3.csv
- datasets/round5/pricesround5day4.csv
- datasets/round5/tradesround5day4.csv
- datasets/round6/
- datasets/round7/
- datasets/round8/
, the raw round 1 day data in datasets/round1/, the raw round 2 day data in datasets/round2/, the raw round 3 day data in datasets/round3/, the raw round 4 day data in datasets/round4/, the raw round 5 day data in datasets/round5/, plus a sample tutorial submission.log produced with the bundled basic trader. The remaining round folders are there so future round files can be placed in the correct folder instead of being mixed together.
If you place a portal submission.log file into a round folder, the backtester will use it. submission.log is also generated for persisted runs.
CLI
The CLI is intentionally simple:
<pre><code class="lang-bash">rust_backtester</code></pre>
With no arguments it will:
- auto-pick the newest Python file that looks like a trader from this repo's local
scripts/, traders/submissions/, or traders/
default the dataset to the latest populated round folder under datasets/
run in fast mode
print one compact row per day
That means the simplest local commands are:
<pre><code class="lang-bash">make backtest</code></pre>
or:
<pre><code class="lang-bash">rust_backtester</code></pre>
For the published crates.io binary, the normal flow is:
<pre><code class="lang-bash">cargo install rust_backtester --locked rust_backtester</code></pre>
To update later:
<pre><code class="lang-bash">cargo install rust_backtester --locked</code></pre>
Round-specific shortcuts:
<pre><code class="lang-bash">make tutorial</code></pre>
Submission and future-round shortcuts are also available once you place a
submission.log, submission.json, or round data into datasets/round1/, datasets/round2/, and so on.
Useful optional variables for any
make backtest target:
<pre><code class="lang-bash">make tutorial DAY=-1 make submission ROUND=round1 make round3 TRADER=traders/latest_trader.py make round3 TRADER=traders/allproductstrader.py make round2 PERSIST=1 make tutorial FLAT=1 make tutorial CARRY=1</code></pre>
Supported input formats:
- normalized dataset JSON files
- IMC day data as matching
prices.csv and trades.csv, with optional paired observations_*.csv
portal submission logs such as submission.log
Day selection behavior:
DAY=-1 or DAY=-2 runs only that day file inside the round
DAY=all or omitting DAY runs the whole round bundle, including any submission file when present
make submission ROUND=round1 runs the submission dataset for datasets/round1/ when a submission file is present
Explicit examples:
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset tutorial</code></pre>
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset datasets/tutorial</code></pre>
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset /path/to/submission.log</code></pre>
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset datasets/round1</code></pre>
Behavior:
- fast mode is the default
- the CLI prints one result row per day
--dataset accepts either a path or a short alias
when --dataset points to a directory, every supported dataset in that directory is run
prices.csv files are paired automatically with matching trades.csv files from the same folder
matching observations_*.csv files are loaded automatically when present and exposed through state.observations.conversionObservations
latest and tutorial run the full bundled tutorial round bundle: day -2, day -1, and the sample tutorial submission log
use --day to run only the matching day dataset within the round bundle; this excludes submission files
metrics.json is always written under runs/
default fast runs also write submission.log under runs/
use --artifact-mode to choose which extra artifacts are written
use --carry or CARRY=1 to carry positions, own trades, market trades, and trader state across non-submission day datasets in the same round
use --flat or FLAT=1 to place multi-run outputs in a single directory with dataset/day-prefixed filenames
use --persist or PERSIST=1 to write the full replay artifact set under runs/
persisted multi-day or multi-file runs also write one combined bundle at runs/, including combined.log and manifest.json
for multi-run visualizer uploads, use each child RUN_DIR/submission.log; the top-level bundle does not emit a stitched replay file
product output defaults to every product so no product PnL is hidden in larger rounds
Published binary vs local development:
rustbacktester runs the installed binary found on your PATH, typically ~/.cargo/bin/rustbacktester
cargo run -- ... runs the local source tree in debug mode
cargo run --release -- ... runs the local source tree in release mode
if end users report slow runs, make sure they are running the installed rust_backtester binary and not repeatedly using cargo run
Bundled dataset aliases:
latest
tutorial, tut, tutorial-round, tut-round
round1, r1
round2, r2
round3, r3
round4, r4
round5, r5
round6, r6
round7, r7
round8, r8
tutorial-1, tut-1, tut-d-1
tutorial-2, tut-2, tut-d-2
Product display modes:
--products full default: print a separate product table with every product
--products summary: print a compact product table with the top product PnL contributors and an OTHER(+N) rollup when needed
--products off: show only the per-day total
Artifact modes:
--artifact-mode none: write only metrics.json
--artifact-mode submission default when --persist is not set: write metrics.json and submission.log
--artifact-mode diagnostic: write metrics.json and bundle.json with the PnL series included for diagnostics
--artifact-mode full: write the full persisted artifact set: metrics.json, bundle.json, submission.log, activity.csv, pnlbyproduct.csv, combined.log, and trades.csv
--persist implies --artifact-mode full unless you explicitly override --artifact-mode
Flat layout behavior:
--flat only changes multi-run layouts; single-run outputs stay unchanged
multi-run outputs are written into runs/ with prefixed filenames such as tutorial-day-2-submission.log and tutorial-day-2-metrics.json
when --flat is combined with --persist, the same directory also includes combined.log and manifest.json
Carry mode behavior:
--carry only applies to non-submission day datasets; submission datasets remain separate runs
with --carry, consecutive day datasets in the same round are merged into one connected replay ordered by (day, timestamp)
positions, prior own trades, prior market trades, and trader data are carried across those merged day boundaries
carry mode also normalizes timestamps into one continuous timeline, so the first tick of the next day starts immediately after the previous day ends
a carried run reports DAY=all in the summary because it spans multiple days
Artifact mode examples:
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset tutorial \ --artifact-mode none</code></pre>
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset tutorial \ --artifact-mode diagnostic</code></pre>
<pre><code class="lang-bash">rust_backtester \ --trader /path/to/trader.py \ --dataset tutorial \ --artifact-mode full</code></pre>
Example output shape:
<pre><code class="lang-text">trader: latest_trader.py [auto] dataset: tutorial [default] mode: fast artifacts: log-only SET DAY TICKS OWNTRADES FINALPNL RUN_DIR D-2 -2 10000 39 118.10 runs/backtest-123-day-2-day-2 D-1 -1 10000 42 123.45 runs/backtest-123-day-1-day-1 SUB -1 2000 18 51.20 runs/backtest-123-submission-day-1
PRODUCT D-2 D-1 SUB TOM 70.00 77.20 29.10 EMR 48.10 46.25 22.10</code></pre>
Bundled Targets
<pre><code class="lang-bash">make doctor make build make build-release make test make install make install-pip make install-uv make install-uv-editable make backtest make tutorial</code></pre>
macOS Troubleshooting
If a local Rust build hangs on macOS, the most likely symptom is that
cargo build, make build-release, or make backtest stalls during build-script-build while syspolicyd uses a lot of CPU.
First retry path:
<pre><code class="lang-bash">make doctor make build-release make backtest</code></pre>
Those
make targets already use the repo wrapper and a stable target dir outside the repo.
If it still hangs and you do not want to reboot, the non-restart remediation is:
<pre><code class="lang-bash">sudo killall syspolicyd make build-release</code></pre>
If local macOS execution policy is still unhealthy, use the isolated fallback:
<pre><code class="lang-bash">make docker-build make docker-smoke</code></pre>
This is a local macOS executable-launch issue, not a backtester logic issue.
Additional
round1 to round8 and round1-submission to round8-submission targets are included in the Makefile. They become usable once those dataset folders contain JSON files.
Isolated Verification
There is a Docker-based smoke path for isolated verification:
<pre><code class="lang-bash">make docker-smoke</code></pre>
The Docker image builds the project in a clean container and runs the zero-argument backtest flow during image build.
Repository Layout
src/ Rust backtester implementation
traders/latest_trader.py bundled default trader
traders/allproductstrader.py diagnostic trader for verifying every product surface
datasets/tutorial/ bundled raw IMC tutorial day CSVs and sample submission log
datasets/round1/ bundled raw IMC round 1 CSVs
datasets/round2/ bundled raw IMC round 2 CSVs
datasets/round3/ bundled raw IMC round 3 CSVs
datasets/round4/ bundled raw IMC round 4 CSVs
datasets/round5/ bundled raw IMC round 5 CSVs
datasets/round6/ ... datasets/round8/ placeholders for future round data
runs/ persisted outputs when --persist is used
runs/ combined bundle for persisted multi-day runs, including combined.log and manifest.json
Licensing
Dual-licensed under:
- Apache-2.0:
LICENSE-APACHE
MIT: LICENSE-MIT`