Contributing
Are you interested in contributing to Backtide? Do you want to report a bug? Do you have a question? Before you do, please read the following guidelines.
Submission context
Question or problem?
For quick questions, there's no need to open an issue. Check first if the question isn't already answered in the FAQ section. If not, reach us through the discussions page.
Report a bug?
If you found a bug in the source code, you can help by submitting an issue to the issue tracker in the GitHub repository. Even better, you can submit a Pull Request with a fix. However, before doing so, please read the submission guidelines.
Missing a feature?
You can request a new feature by submitting an issue to the GitHub Repository. If you would like to implement a new feature, please submit an issue with a proposal for your work first. Please consider what kind of change it is:
-
For a major feature, first open an issue and outline your proposal so that it can be discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project.
-
Small features and bugs can be crafted and directly submitted as a Pull Request. However, there is no guarantee that your feature will make it into
master, as it's always a matter of opinion whether if benefits the overall functionality of the project.
Project layout
The latest stable release of Backtide is on the master branch, whereas the
latest version in development is on the development branch. Make sure to
familiarize yourself with the project layout before making any major contributions.
Folder structure
backtide/ # Repository root
├── pyproject.toml # Python package metadata, dependencies & tool config
├── tox.ini # Test / CI task runner configuration
├── uv.lock # Locked dependency versions (managed by uv)
├── justfile # Convenience task recipes for `just`
├── mkdocs.yml # Documentation site configuration
├── backtide.config.toml # Default runtime configuration file
├── .pre-commit-config.yaml # Pre-commit hook definitions
│
├── src/ # Python package + Rust crate live under src/
│ ├── backtide/ # Python package (public API)
│ │ ├── __init__.py
│ │ ├── backtest.py # Backtest model re-exports
│ │ ├── cli.py # Click CLI entry point (backtide launch / download)
│ │ ├── config.py # Configuration re-exports
│ │ ├── data.py # Data re-exports
│ │ ├── storage.py # Storage re-exports
│ │ ├── core.*.pyd / .so # Compiled Rust extension (built by maturin)
│ │ ├── core/ # Type stubs (.pyi) for the compiled extension
│ │ ├── analysis/ # Plotting & statistics functions
│ │ ├── indicators/ # Technical indicators (Python wrappers + base class)
│ │ ├── strategies/ # Trading strategies (Python wrappers + base class)
│ │ ├── ui/ # Streamlit interactive UI
│ │ │ ├── app.py # Main Streamlit application (navigation)
│ │ │ ├── analysis.py # Analysis page
│ │ │ ├── download.py # Download page
│ │ │ ├── experiment.py # Experiment page
│ │ │ ├── home.py # Home dashboard page
│ │ │ ├── indicators.py # Indicators page
│ │ │ ├── results.py # Results page
│ │ │ ├── storage.py # Storage page
│ │ │ ├── strategies.py # Strategies page
│ │ │ └── utils.py # UI helpers
│ │ └── utils/ # Python utility modules
│ │ ├── constants.py
│ │ ├── enum.py
│ │ └── utils.py
│ │
│ └── backtide_core/ # Rust crate (compiled into backtide.core via PyO3)
│ ├── Cargo.toml # Crate metadata & dependencies
│ ├── Cargo.lock # Locked Rust dependency versions
│ ├── rustfmt.toml # Rust formatter configuration
│ ├── src/
│ │ ├── lib.rs # Crate root & PyO3 module registration
│ │ ├── engine.rs # Core backtest engine
│ │ ├── analysis.rs # Statistics computation
│ │ ├── constants.rs # Shared constants
│ │ ├── errors.rs # Error types
│ │ ├── backtest/ # Backtest models, indicators & strategies
│ │ ├── config/ # Configuration models & parsing
│ │ ├── data/ # Data layer: models & providers (Yahoo, Binance, Kraken, Coinbase)
│ │ ├── storage/ # Storage layer: DuckDB backend & Storage trait
│ │ └── utils/ # Utility functions & HTTP helpers
│ └── benches/ # Criterion.rs benchmarks
│ ├── storage_bench.rs # DuckDB storage throughput / latency benchmarks
│ ├── data_bench.rs # Live API download latency benchmarks
│ └── backtest_bench.rs # Core strategy runtime benchmarks
│
├── tests/ # Python unit tests (pytest)
│ ├── __init__.py
│ ├── conftest.py # Shared fixtures
│ ├── test_analysis.py
│ ├── test_backtest.py
│ ├── test_cli.py
│ ├── test_config.py
│ ├── test_data.py
│ ├── test_sizing.py
│ ├── test_storage.py
│ ├── test_ui.py
│ └── test_utils.py
│
├── scripts/ # Developer scripts
│ ├── generate_stubs.py # Regenerate .pyi stubs from the compiled extension
│ └── run_cargo.py # Helper wrapper around cargo invocations
│
├── docs_sources/ # MkDocs documentation sources
│ ├── index.md
│ ├── about.md
│ ├── getting_started.md
│ ├── contributing.md
│ ├── dependencies.md
│ ├── faq.md
│ ├── license.md
│ ├── user_guide/ # User-guide pages
│ ├── api/ # Auto-generated API reference pages
│ ├── img/ # Images, icons, logos
│ ├── overrides/ # MkDocs Material theme overrides
│ ├── scripts/ # Build-time hooks (autodocs, autorun)
│ └── stylesheets/ # Custom CSS / JS
│
└── images/ # Branding assets & provider logos
Key technologies
| Layer | Technology |
|---|---|
| Core engine | Rust compiled to a Python extension via PyO3 & maturin |
| Storage | DuckDB (embedded OLAP database) |
| Python API | Re-export wrappers around the compiled backtide.core module |
| CLI | Click |
| UI | Streamlit |
| Docs | MkDocs Material |
| Testing | pytest (Python) · cargo test (Rust) |
| Linting | Ruff (Python) · cargo clippy / cargo fmt (Rust) |
| Benchmarking | Criterion.rs |
| Task runner | tox with the tox-uv plugin · just for local recipes |
| Package mgmt | uv |
Development setup
1. Clone the repository
git clone https://github.com/tvdboom/backtide.git
cd backtide
2. Create a virtual environment and install
uv venv
uv sync --all-groups
3. Build the Rust extension (development mode)
uv pip install -e .
This triggers maturin (configured as the PEP 517 build backend in
pyproject.toml) to compile the backtide_core crate and place the resulting
.pyd / .so extension next to the Python package, so that import backtide.core
works without a full wheel build.
If you prefer to invoke maturin directly:
maturin develop --manifest-path src/backtide_core/Cargo.toml
4. Install pre-commit hooks
pre-commit install
5. (Optional) install just for local task recipes
A justfile at the repository root provides convenience recipes such as
just build, just test, just lint, just docs and just launch.
uv tool install rust-just
just --list
Running tests
Python tests
Python tests live in the tests/ directory and are executed with pytest:
pytest tests/
For parallel execution (requires pytest-xdist):
pytest -n auto tests/
Rust tests
Rust unit tests embedded in the backtide_core crate:
cargo test --manifest-path src/backtide_core/Cargo.toml --no-default-features
Tox
Tox is used as the unified task runner for the project. It is configured
in tox.ini and uses the tox-uv plugin so environments are created with
uv instead of plain venv.
Available environments
| Environment | What it does |
|---|---|
py311 ... py314 |
Build the wheel (including Rust compilation) and run pytest on that Python version. |
py314-min |
Test against the oldest compatible versions of runtime dependencies. |
cargo-test |
Run cargo test on the Rust crate. |
pre-commit |
Run all pre-commit hooks (ruff, ruff-format, cargo fmt, cargo clippy, …). |
bench |
Run Criterion.rs benchmarks (see Benchmarks). |
docs |
Build the MkDocs documentation in strict mode. |
Benchmarks
Performance of the Rust core is tracked with Criterion.rs benchmarks defined
in src/backtide_core/benches/. Criterion generates HTML reports in
src/backtide_core/target/criterion/report/index.html. Three benchmark suites
exist:
Storage benchmarks
Measures DuckDB bulk-insert throughput and query latency using synthetic bar
data. Each iteration creates an isolated temporary database via tempfile so
runs never interfere with one another.
Data benchmarks
Measures end-to-end download latency for the data providers. These benchmarks hit real network endpoints, so results are inherently noisier and depend on network conditions.
Backtest benchmarks
Measures runtime for representative built-in strategies over a fixed historical dataset to track the performance of the Rust engine as strategy logic evolves.
Running benchmarks
# All benchmarks
cargo bench --manifest-path src/backtide_core/Cargo.toml
# Storage only
cargo bench --manifest-path src/backtide_core/Cargo.toml --bench storage_bench
# Data/download only
cargo bench --manifest-path src/backtide_core/Cargo.toml --bench data_bench
# Backtest only
cargo bench --manifest-path src/backtide_core/Cargo.toml --bench backtest_bench
Or via tox:
tox -e bench
Pre-commit & linting
The project uses pre-commit to enforce code quality
on every commit. The hooks are defined in .pre-commit-config.yaml. To run all
hooks manually:
pre-commit run --all-files
Or through tox:
tox -e pre-commit
Python style
- Ruff is the single linter and formatter.
- Maximum line length is 99 characters. Keep docstrings below 80 characters where practical.
- Docstrings follow the NumPy convention.
Rust style
cargo fmtwith settings inbacktide_core/rustfmt.toml.cargo clippywith-D warnings(all warnings are errors).
Building the documentation
The docs are built with MkDocs Material
and live in docs_sources/. Build-time hooks in docs_sources/scripts/ handle
auto-generated API reference pages.
# Live preview with hot-reload
mkdocs serve
# Production build (strict mode)
mkdocs build --strict
Or via tox:
tox -e docs
Submission guidelines
Submitting an issue
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists, and the discussion might inform you of workarounds readily available.
We want to fix all the issues as soon as possible, but before fixing a bug, we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using the custom issue template.
Submitting a pull request
Before you submit a pull request, please work through this checklist to make sure that you have done the necessary so we can efficiently review and accept your changes.
- Update the documentation so all of your changes are reflected there.
- Adhere to the coding style enforced by Ruff (Python) and rustfmt / Clippy
(Rust). Run
pre-commit run --all-filesto verify. - Use a maximum of 99 characters per line. Try to keep docstrings below 80 characters.
- Update the project unit tests to test your code changes as thoroughly
as possible — both
tests/(Python) andcargo test(Rust). - Make sure that your code is properly commented with docstrings and comments explaining your rationale behind non-obvious coding practices.
- Run the full tox suite:
toxand make sure all environments pass.
If your contribution requires a new Python library dependency:
- Double-check that the new dependency is easy to install via pip.
- The library should support Python 3.11, 3.12, 3.13 and 3.14.
- Make sure the code works with the latest version of the library.
- Update the dependencies in the documentation.
- Add the library with the minimum required version to
pyproject.toml.
If your contribution requires a new Rust crate dependency:
- Add it to
src/backtide_core/Cargo.tomlwith an explicit version. - Make sure
cargo clippyandcargo teststill pass.
After submitting your pull request, GitHub will automatically run the tests on your changes and make sure that the updated code builds successfully. The checks run on all supported Python versions, on Ubuntu, macOS and Windows. We also use services that automatically check code style and test coverage.