Professional event-driven backtesting framework for quantitative trading strategies
Project description
enode-backtester
Lightweight event-driven backtesting framework that wires together data handlers, strategies, sizing logic, a portfolio, execution simulation, and analysis helpers. The library is designed as a foundation you can extend with richer metrics, visualisations, and strategy research.
Repository Layout
backtester/__init__.py– Public entry point exportingrun_backtest, utility helpers, and default sizing/execution models.backtester/data.py– Data-handling abstractions.DataFrameDataHandlerstreams pandas data frames into the event queue asStockEvents.backtester/event.py– Event hierarchy that flows through the engine (market data, signals, orders, fills).backtester/strategy.py– Base strategy contract; provides thesignalhelper for emittingSignalEvents.backtester/sizer.py– Base sizing abstraction plus a simpleFixedSizeSizer.backtester/portfolio.py– Tracks cash, positions, trade log, equity curve, and reacts to incoming events.backtester/execution.py– Simulated execution handler with configurable slippage and commission models.backtester/engine.py– Core event loop that coordinates the handlers and processes the event queue.backtester/analysis.py– Post-run utilities, including an optional PyFolio tear sheet and trade log export.backtester/models.py– Pydantic models shared across modules (e.g.,StockCandle).
Installation
# using uv (recommended)
uv sync
# or using pip / virtualenv
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pyfolio is optional; install it (pip install pyfolio) if you need tear-sheet generation.
Creating a Strategy
- Subclass
backtester.strategy.BaseStrategy. - Implement
on_stock_event(self, event)to react to incoming candles (event.payloadis aStockCandle). - Use
self.signal(symbol, timestamp, action)to enqueueSignalEvents (actionis typically"buy"or"sell").
from collections import deque
from backtester.strategy import BaseStrategy
from backtester.event import StockEvent
class MySMAStrategy(BaseStrategy):
def __init__(self, event_queue, data_handler, window: int = 20):
super().__init__(event_queue, data_handler)
self.window = window
self.prices: dict[str, deque[float]] = {}
def on_stock_event(self, event: StockEvent) -> None:
symbol = event.payload.symbol
price = event.payload.close
history = self.prices.setdefault(symbol, deque(maxlen=self.window))
history.append(price)
if len(history) < history.maxlen:
return
avg_price = sum(history) / len(history)
if price > avg_price:
self.signal(symbol, event.payload.timestamp, "buy")
elif price < avg_price:
self.signal(symbol, event.payload.timestamp, "sell")
Running a Backtest
import pandas as pd
from backtester import FixedSizeSizer, run_backtest, get_trade_log_df
from backtester.data import DataFrameDataHandler
from my_strategies import MySMAStrategy
# 1. Prepare your market data as pandas DataFrames keyed by symbol
data_dict: dict[str, pd.DataFrame] = {
"AAPL": aapl_df,
"GOOGL": googl_df,
}
# 2. Choose a sizing model (optional)
sizer = FixedSizeSizer(default_size=10)
# 3. Execute the backtest
portfolio = run_backtest(
data_dict=data_dict,
strategy_class=MySMAStrategy,
initial_cash=100_000.0,
sizer=sizer,
)
# 4. Inspect results
trade_log = get_trade_log_df(portfolio)
print(trade_log.tail())
print(f"Final equity: {portfolio.equity_curve[-1][1]:,.2f}")
run_backtest instantiates all core components for you:
DataFrameDataHandlerfeeds candles into the event queue.- Your strategy consumes
StockEvents and emitsSignalEvents. PortfoliogeneratesOrderEvents using the providedSizer.SimulatedExecutionHandlerfills orders with configurable commission and slippage.
After the engine finishes processing all data, you receive the final Portfolio object for downstream analysis.
Extending the Framework
- Metrics & Visuals: Add new functions under
backtester.analysisor integrate notebooks that consume the trade log and equity curve. - Strategies: Drop additional strategy classes alongside your own module; they only need to inherit from
BaseStrategy. - Execution/Sizing: Create custom classes that follow the abstract base class signatures in
execution.pyandsizer.py. - Data: Implement new handlers by subclassing
BaseDataHandlerif you need live feeds, streaming data, or alternative storage.
Contributing
Pull requests and issues welcome—especially around additional strategy templates, validation rules, and analytical tooling.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file enode_backtester-0.1.5.tar.gz.
File metadata
- Download URL: enode_backtester-0.1.5.tar.gz
- Upload date:
- Size: 198.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21359cb08f813059f2eed4b9f70803383c99c1d01a9484345bff78280df2e670
|
|
| MD5 |
799c44436f3e034caa950bdfb3c7e20a
|
|
| BLAKE2b-256 |
9db79edbfe64486e780a8e5b37dad8ad3a2275d4a0da8000d3eb0f3e32fc7a2d
|
File details
Details for the file enode_backtester-0.1.5-py3-none-any.whl.
File metadata
- Download URL: enode_backtester-0.1.5-py3-none-any.whl
- Upload date:
- Size: 192.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58f6ad4d24f667aee2264907d7e0a93425b015681110f219b4f204b1a5eb502c
|
|
| MD5 |
cf318cb108074cd4a5952274f9d3bc01
|
|
| BLAKE2b-256 |
16ca50b54c87fdabf476194c62b89647826029bbfbd685650e1edd5da68b379a
|