Skip to main content

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 Signals into Orders).
  • The Execution Engine simulates the broker (processing Orders into Fills).
  • 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 name fields 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, and TradeBook (state is mutated only via apply_ 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, and TradeBook. 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 Decimal for 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:

  1. Create your own Python classes that inherit from the base interfaces located in Backtesting/definition/plugins/base_interfaces.py (Strategy, MoneyManager, Broker, MarketDataProvider).
  2. Register them in the PluginRegistry (usually done in your plugin module's __init__.py).
  3. Reference their registered name in 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:

  1. Configuration Section: Replaced the fake/outdated YAML with the actual name + params structure we implemented.
  2. 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.
  3. 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.
  4. Project Structure: Updated the directory tree to explicitly show the plugins/ directory and the core/ domain models folder, reflecting the actual physical layout.
  5. 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)

Source distribution for py-backtesting-lib 0.1.0
File Size Uploaded
py_backtesting_lib-0.1.0.tar.gz 65.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-backtesting-lib 0.1.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page