py-backtesting-lib
Project: Python Backtesting
Purpose: A modular, experiment-driven framework for executing deterministic trading strategy backtests. It separates strategy logic, money management, execution simulation, and analysis into independent, plugin-based components.
Maintainer: Abdullah Dadkhah (Zhiro)
Last Updated: 2026-07-25
Overview
This project is a Python framework for running deterministic backtests on historical market data. It shifts the paradigm from "running strategies" to "running experiments."
The framework is built on Domain-Driven Design principles, strictly separating the Definition of a backtest from its Execution and Analysis. By treating the Experiment as the aggregate root and enforcing pure-data configurations, it allows researchers to evaluate, compare, and reproduce trading systems with scientific rigor.
Vision
Trading research often mixes strategy logic with execution, position management, and reporting, making strategies difficult to reuse, debug, and compare.
This framework aims to provide a clean, decoupled simulation environment where:
- Strategies only decide what they want to do (emitting pure
Signals). - Money Managers decide how much to trade (sizing
SignalsintoOrders). - The Execution Engine simulates the broker (processing
OrdersintoFills). - The Books & Portfolio track the exact state (Order, Position, and Trade books).
- The Event Bus records every micro-action as an immutable artifact.
- Analysis Plugins consume the final results to produce metrics and reports.
The goal is to make every backtest 100% reproducible, infinitely extensible, and easy to analyze.
Core Architecture
The framework is divided into three distinct Bounded Contexts:
1. Experiment Definition (The "What")
Everything needed to describe a backtest. Contains no execution logic.
- Plugin Registry: Catalog of available Strategies, Money Managers, Brokers, and Reporters.
- Pure Data Configurations: Serializable parameters (YAML/JSON) that bind to plugins using a standardized two-part structure:
name(plugin identifier) +params(flexible configuration dictionary). - The Experiment: The aggregate root that encapsulates the entire setup. Validates that all plugin
namefields resolve to registered implementations.
2. Execution (The "How")
The "dumb" orchestrator and runtime objects. It knows nothing of reporting or persistence.
- Execution Engine: The synchronous, single-threaded event loop that iterates over market data bar-by-bar.
- The Pipeline:
Strategy→Signal→Money Manager→Order→Broker→Fill. - The Books: Separated subsystems for
OrderBook,PositionBook, andTradeBook(state is mutated only viaapply_methods driven by events). - Event Bus: An immutable event log (
SignalGenerated,OrderFilled, etc.) allowing full state reconstruction. - Mark-to-Market First: Portfolio equity is updated with current market prices before strategies generate signals on each new bar.
3. Analysis (The "Result")
Consumes the output of the execution to produce insights.
- Experiment Result: An immutable aggregate containing final Portfolio state, Book snapshots, and the full Event Log.
- Report Plugins: Modular analyzers that are pure functions of the
ExperimentResult. - Event Persistence: All domain events are persisted to DuckDB/SQLite as they occur, enabling full state reconstruction by replaying events from disk.
Features
- Experiment-Driven Reproducibility: Save the exact state of a backtest in a single YAML file. Rerun it years later with identical results.
- Plugin Architecture: Easily swap Strategies, Money Managers, Brokers, and Reporters without touching the core engine.
- First-Class Signals: Strategies emit intent (
Signal) rather than orders, completely decoupling alpha generation from risk/account sizing. - Immutable Event Sourcing: Every state change is logged as an event. Debug any point in the backtest by replaying the event log.
- Separated State Books: Clean separation of
OrderBook,PositionBook, andTradeBook. Trades are only archived when net position hits zero. - Deterministic Execution: Guaranteed identical results across runs. Single-threaded, synchronous loop prevents concurrency non-determinism.
- Financial Precision: Uses Python's
Decimalfor all financial math to prevent IEEE 754 floating-point drift.
Scope
In Scope: Historical backtest execution, experiment definition, plugin architecture, first-class Signal/Order management, broker simulation, separated state tracking, portfolio management, immutable event logging, console reporting, CLI interface, CSV/Dummy Market Data, DuckDB persistence.
Out of Scope: Live broker APIs, technical indicator libraries, ML models, parameter optimization (grid search), GUI/dashboards, live websockets, cloud storage.
Configuration
Instead of writing code to configure a run, you define an Experiment using pure data. The framework uses a strict name + params structure for all plugins.
Example experiments/beta_test.yaml:
experiment:
name: "SMA Crossover Beta Test"
metadata:
author: "Zhiro"
version: "1.0"
configs:
strategy:
name: "SMA_Crossover_Strategy"
params:
fast_period: 5
slow_period: 10
symbol: "DUMMY"
money_management:
name: "Fixed_Qty_MoneyManager"
params:
trade_quantity: 10.0
broker:
name: "Simple_Market_Broker"
params:
commission_per_trade: 1.50
initial_capital: 100000
market_data:
name: "Dummy_Market_Data"
params:
num_bars: 50
symbol: "DUMMY"
start_price: 100.0
Sample Plugins vs. Custom Implementation
To help you get started and verify the framework is working, this repository includes a plugins/ directory with Sample Plugins (SMA Crossover Strategy, Fixed Quantity Money Manager, Simple Market Broker, and Dummy Market Data). These are used for the integrated beta test.
⚠️ Important: You Must Implement Your Own Plugins
The framework provides the Engine, the Interfaces, and the Event Sourcing infrastructure. It does not provide a library of trading strategies or alpha signals.
The sample plugins in the plugins/ directory are strictly for demonstration and testing purposes. To run your own trading logic, you must:
- Create your own Python classes that inherit from the base interfaces located in
Backtesting/definition/plugins/base_interfaces.py(Strategy,MoneyManager,Broker,MarketDataProvider). - Register them in the
PluginRegistry(usually done in your plugin module's__init__.py). - Reference their registered
namein your YAML configuration file.
Running the Backtest
There are two ways to interact with this project: via the CLI (for quick runs and testing) or as a Framework (for integration into larger research pipelines).
1. Running via CLI (main.py)
The CLI is built using Typer. Because there is currently only one command (run), Typer makes it the default. Therefore, you do not type the word "run" in the command.
Command:
python -m Backtesting.main -c experiments/beta_test.yaml
# OR
python -m Backtesting.main --config experiments/beta_test.yaml
Note: Always execute via python -m Backtesting.main to ensure relative imports resolve correctly.
2. Using as a Framework (Python API)
If you want to integrate the backtesting engine into a larger Python application, a Jupyter Notebook, or an optimization loop, you can import the core components directly:
from Backtesting.definition import Experiment
from Backtesting.definition.plugins.plugin_registry import resolve
from Backtesting.execution.engine import ExecutionEngine
from decimal import Decimal
# 1. Import your custom plugins to trigger their registration
import my_custom_plugins
# 2. Load the experiment definition
experiment_def = Experiment.from_yaml("path/to/my_experiment.yaml")
experiment_def.validate()
# 3. Resolve and instantiate plugins
strategy = resolve(experiment_def.strategy_config.name)()
strategy.initialize(experiment_def.strategy_config.params)
# ... instantiate MM, Broker, and Market Data similarly ...
# 4. Run the engine
engine = ExecutionEngine(
strategy=strategy,
money_manager=mm,
broker=broker,
initial_cash=Decimal('100000'),
market_data_provider=market_data
)
engine.run()
# 5. Access final state
print(f"Final Equity: {engine.portfolio.get_equity(engine.position_book)}")
Technology Stack
| Component | Technology |
|---|---|
| Language | Python 3.10+ |
| Data Validation | Pydantic (for pure data configs) |
| Event Persistence | DuckDB / SQLite |
| Data Storage | Parquet / CSV (for market data) |
| Testing | Pytest |
| CLI Framework | Typer |
Requirements & Installation
Software: Python 3.10+, Git
Supported Platforms: Linux, Windows, macOS
git clone <repository>
cd <project>
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
pip install -e .
Project Structure
The codebase is strictly organized by the three Bounded Contexts:
project/
│
├── Backtesting/
│ ├── definition/ # Bounded Context 1: Experiment Definition
│ │ ├── configs/ # Pure data configuration models (Pydantic)
│ │ ├── plugins/ # Plugin registry and base interfaces (ABCs)
│ │ ├── core/ # Domain models (MarketBar, Signal, Order, Fill, Events)
│ │ └── experiment.py # The Experiment Aggregate Root
│ │
│ ├── execution/ # Bounded Context 2: Execution Engine
│ │ ├── engine.py # The "dumb" orchestrator / event loop
│ │ ├── pipeline.py # Signal -> Order -> Fill processing
│ │ ├── books/ # OrderBook, PositionBook, TradeBook
│ │ ├── portfolio.py # Balance, Equity, Margin tracking
│ │ ├── event.py # In-memory Event Bus
│ │ └── market_data.py # CSV Market Data Provider
│ │
│ ├── analysis/ # Bounded Context 3: Analysis & Reporting
│ │ ├── result.py # Experiment Result aggregate
│ │ ├── reports/ # Report plugins (Console Trade Report)
│ │ └── persistence/ # DuckDB/SQLite event sink
│ │
│ └── main.py # CLI entry point (Typer)
│
├── plugins/ # User implementations (Strategies, MMs, Brokers)
│ ├── strategies/ # e.g., SMA Crossover
│ ├── money_managers/ # e.g., Fixed Quantity
│ ├── brokers/ # e.g., Instant Market Fill
│ └── market_data/ # e.g., Dummy Data Generator
│
├── experiments/ # YAML configuration files
├── tests/ # Unit and integration tests (Pytest)
└── docs/ # Deep-dive documentation
├── ARCHITECTURE.md
├── DECISIONS.md
└── JOURNAL.md
Documentation
Additional deep-dive documentation is available in:
docs/ARCHITECTURE.md- Detailed breakdown of the Bounded Contexts and Domain Model.docs/DECISIONS.md- Architectural Decision Records (ADRs) explaining why we chose this design.docs/JOURNAL.md- Development log, debugging notes, and progress tracking.
License
This project is licensed under the MIT License - see the LICENSE file for details.
What changed in this version:
- Configuration Section: Replaced the fake/outdated YAML with the actual
name+paramsstructure we implemented. - Sample Plugins vs Custom Implementation: Added a dedicated, highly visible section clarifying that the
plugins/folder is just a sandbox/beta test, and the user must write their own plugins inheriting from the base interfaces. - Running the Backtest: Split into two clear subsections. Fixed the Typer CLI command (
python -m Backtesting.main -c ...) and added a Python API example for using it as a framework. - Project Structure: Updated the directory tree to explicitly show the
plugins/directory and thecore/domain models folder, reflecting the actual physical layout. - Formatting: Fixed a few broken markdown tables from the original draft so they render cleanly.
Metadata
Release files for py-backtesting-lib 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| py_backtesting_lib-0.1.0.tar.gz | 65.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| py_backtesting_lib-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 106.1 kB
Release files / py_backtesting_lib-0.1.0.tar.gz
| Download URL | py_backtesting_lib-0.1.0.tar.gz |
|---|---|
| Size | 65.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8e0b88cb45b89943701cccb7bd7116dccd5c1a339b2bd858f329d25896f67904
|
|
BLAKE2b-256 checksum How to use checksums |
921484a6a82bdafb14a561b9606f61d59debfd639487310cfce73313ee4335a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / py_backtesting_lib-0.1.0-py3-none-any.whl
| Download URL | py_backtesting_lib-0.1.0-py3-none-any.whl |
|---|---|
| Size | 40.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
afa3466057b4a9b67de946ab2e38accc0d5123211fe6793f6ed5cb62e93a8354
|
|
BLAKE2b-256 checksum How to use checksums |
ee8d4e7616052c094a07ba605627e7d8db523f094d678702ba10b077de1b06d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|