GeneralBacktest
GeneralBacktest is a flexible and efficient quantitative strategy backtesting framework designed for multi-asset portfolio strategies. Its core workflow is:
weight -> trades -> NAV
It supports arbitrary rebalancing schedules, realistic trading details, vectorized performance, and rich analytics.
Documentation Languages
- English (this file): README.md
- Chinese: README.zh-CN.md
- Changelog: CHANGELOG.md
Key Features
- Flexible rebalancing: rebalance on any dates, not limited to fixed intervals.
- High performance: vectorized implementation for large universes and long histories.
- Realistic trading simulation:
- Rebalance threshold control (avoid tiny adjustments)
- Separate buy/sell transaction cost
- Slippage modeling
- Rebalance-day PnL split (hold/sell/buy)
- Dynamic total exposure control via
position_ratio_col(v1.1.0). - Cash-based backtest mode via
run_backtest_with_cash(v1.1.0). - Enhanced visualization (log-scale NAV and dual-scale NAV, v1.1.0).
- T+0 (intraday round-trip) backtesting via
TBacktest.run_t0_backtest()(v1.2.0). - 15+ performance metrics and 10+ plotting utilities.
Installation
Basic
pip install GeneralBacktest
With Database Support
If you need run_backtest_ETF() or run_backtest_stock():
pip install GeneralBacktest[database]
All Optional Dependencies
pip install GeneralBacktest[full]
Dependencies
- Required:
numpy,pandas,matplotlib - Optional:
quantchdbfor database-backed ETF/stock dataopenpyxlfor Excel export
Quick Start
Basic Backtest with Local Data
from GeneralBacktest import GeneralBacktest
import pandas as pd
weights_data = pd.DataFrame({
"date": ["2023-01-01", "2023-01-01", "2023-06-01", "2023-06-01"],
"code": ["stock_A", "stock_B", "stock_A", "stock_B"],
"weight": [0.6, 0.4, 0.3, 0.7]
})
price_data = pd.DataFrame({
"date": pd.date_range("2023-01-01", "2023-12-31", freq="D"),
"code": "stock_A",
"open": [...],
"close": [...],
"adj_factor": [...]
})
bt = GeneralBacktest(start_date="2023-01-01", end_date="2023-12-31")
results = bt.run_backtest(
weights_data=weights_data,
price_data=price_data,
buy_price="open",
sell_price="close",
adj_factor_col="adj_factor",
close_price_col="close",
rebalance_threshold=0.005,
transaction_cost=[0.001, 0.001],
slippage=0.0005,
initial_capital=1.0
)
bt.print_metrics()
bt.plot_all()
bt.plot_nav_curve()
bt.plot_monthly_returns()
Advanced Features
Dynamic Total Exposure (position_ratio_col)
Set total invested ratio per rebalance date (e.g., 80% invested, 20% cash):
weights_data = pd.DataFrame({
"date": ["2023-01-01", "2023-01-01", "2023-06-01", "2023-06-01"],
"code": ["stock_A", "stock_B", "stock_A", "stock_B"],
"weight": [0.6, 0.4, 0.3, 0.7],
"position_ratio": [0.8, 0.8, 0.9, 0.9]
})
results = bt.run_backtest(
weights_data=weights_data,
price_data=price_data,
position_ratio_col="position_ratio"
)
Cash Backtest (run_backtest_with_cash)
Use actual capital and lot-size constraints for execution-accurate simulation:
results = bt.run_backtest_with_cash(
weights_data=weights_data,
price_data=price_data,
initial_capital=1_000_000,
buy_price="open",
sell_price="close",
close_price_col="close",
lot_size=100,
trade_critic="weight_desc",
transaction_cost=[0.001, 0.001],
slippage=0.001
)
print(f"Final NAV: {results['nav_series'].iloc[-1]:,.2f}")
print(f"Final Cash: {results['cash_series'].iloc[-1]:,.2f}")
print(f"Cash Ratio: {results['metrics']['Cash Ratio']:.2%}")
Tradable Volume Constraint (v1.2.0)
Pass a volume_data DataFrame to add a strict tradable-volume upper bound
per (date, code). This decouples the framework from any specific intraday
model: the user computes tradable_shares externally (e.g., from minute
data, VWAP participation, order-book depth) and simply hands the result to
the backtester.
# volume_data columns: date, code, tradable_shares
# (any (date, code) not present -> STRICTLY 0, i.e. not tradable that day)
results = bt.run_backtest_with_cash(
weights_data=weights_data,
price_data=price_data,
initial_capital=1_000_000,
buy_price="open",
sell_price="close",
close_price_col="close",
lot_size=100,
volume_data=volume_df,
volume_col="tradable_shares", # or use buy_volume_col/sell_volume_col to split
)
m = results['metrics']
print(f"Avg Fill Ratio: {m['平均订单填充率']:.2%}")
print(f"Volume-Constrained %: {m['量约束订单占比']:.2%}")
print(f"Total Orders: {m['订单总数']}")
Trade records add intended_shares and constraint_hit columns
('none' | 'cash' | 'volume').
ETF Database Backtest (Requires DB Config)
run_backtest_ETF() and run_backtest_stock() need a valid database config. For general usage, run_backtest() is recommended.
T+0 Intraday Round-Trip (TBacktest)
TBacktest supports same-day sell → buy cycles (T+0 strategies). weight is the target position, not the trading amount. The phase column controls intraday execution order:
from GeneralBacktest import TBacktest
tb = TBacktest(start_date='2024-01-01', end_date='2024-12-31')
results = tb.run_t0_backtest(
weights_data=t0_weights, # includes 'phase' column
price_data=price_data,
buy_price='close', # configurable
sell_price='open', # configurable
adj_factor_col='adj_factor',
close_price_col='close',
transaction_cost=[0.001, 0.001]
)
# T+0 dedicated visualization
tb.plot_intraday_trades() # NAV + intraday trade markers
tb.plot_t0_returns_breakdown() # sell vs buy return decomposition
tb.plot_nav_vs_benchmark() # strategy vs benchmark comparison
Weights data format with phase column:
| date | code | weight | phase |
|---|---|---|---|
| 2024-01-02 | stock_A | 1.0 | NaN |
| 2024-01-03 | stock_A | 0.5 | sell |
| 2024-01-03 | stock_A | 1.0 | buy |
A-share compliance is enforced:
buy_phasemust followsell_phasein time order- Net buy on each day: total sell ≤ total buy (no naked shorting)
- Target positions capped at [0, 1]
T+0-specific metrics: sell win rate, buy win rate, sell/buy cumulative return, return contribution ratio, average commission rate.
Core Concepts
Total Exposure Control
When position_ratio_col is provided:
- Normalize per-date asset weights so they sum to 1.
- Multiply normalized weights by
position_ratioof that date. - Final sum of target weights equals
position_ratio; the remaining part is cash.
Example:
- Raw weights: A=0.6, B=0.4
position_ratio= 0.8- Final weights: A=0.48, B=0.32
- Cash = 0.2
Standard vs Cash Backtest
| Item | run_backtest() |
run_backtest_with_cash() |
|---|---|---|
| Capital unit | Relative weight (0-1) | Absolute amount |
| Position tracking | Weights | Share quantity |
| Lot size constraint | No | Yes (lot_size) |
| Cash constraint | No | Yes |
| Adj factor | Required for adjusted return | Not required |
Metrics
The framework includes return, risk, risk-adjusted, tail-risk, relative, and turnover metrics.
Cash-specific metrics in run_backtest_with_cash() include:
Final CashCash RatioAvg Cash Ratio
Visualization
bt.plot_all()
bt.plot_nav_curve() # linear scale
bt.plot_nav_curve(log_scale=True) # log scale (v1.1.0)
bt.plot_nav_curve_dual() # dual linear/log (v1.1.0)
bt.plot_nav_vs_benchmark() # strategy vs benchmark
bt.plot_excess_returns() # excess return analysis
bt.plot_monthly_returns() # monthly heatmap
bt.plot_turnover() # turnover analysis
bt.plot_position_heatmap() # holdings heatmap
bt.plot_return_distribution() # return distribution
# T+0 specific (TBacktest)
tb.plot_intraday_trades() # NAV + trade markers
tb.plot_t0_returns_breakdown() # sell vs buy decomposition
API Overview
Constructor
GeneralBacktest(start_date: str, end_date: str)
Main Methods
run_backtest(...)— standard weight-based backtestrun_backtest_ETF(...)— ETF data from databaserun_backtest_stock(...)— stock data from databaserun_backtest_with_cash(...)— cash-constrained executionTBacktest.run_t0_backtest(...)— T+0 intraday round-trip
Plotting and Reporting
print_metrics()plot_all()plot_nav_curve(log_scale=False)plot_nav_curve_dual(...)plot_comparison()plot_excess_returns()plot_monthly_returns()plot_turnover()plot_positions()plot_return_distribution()
Backward Compatibility
All v1.1.0 and v1.2.0 features are incremental and backward compatible:
position_ratio_colinrun_backtest()defaults toNone.log_scaleinplot_nav_curve()defaults toFalse.run_backtest_with_cash()andplot_nav_curve_dual()are new v1.1.0 methods.TBacktestis a new v1.2.0 class — it does not affect existingGeneralBacktestusage.
Contributing
Issues and pull requests are welcome.
License
MIT License. See LICENSE.
Author
Elen Young - yang13515360252@163.com
Links
- GitHub: https://github.com/ElenYoung/GeneralBacktest
- Issues: https://github.com/ElenYoung/GeneralBacktest/issues
- Releases: https://github.com/ElenYoung/GeneralBacktest/releases
Disclaimer
This framework is for research and educational purposes only and does not constitute investment advice.
Metadata
Release files for GeneralBacktest 1.2.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 | |
|---|---|---|---|
| generalbacktest-1.2.0.tar.gz | 46.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| generalbacktest-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 90.4 kB
Release files / generalbacktest-1.2.0.tar.gz
| Download URL | generalbacktest-1.2.0.tar.gz |
|---|---|
| Size | 46.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c1c106b61398516710ffb88d0bdbc2f6cf37925834472c36d330b9f38b4eb02d
|
|
BLAKE2b-256 checksum How to use checksums |
f4f2efcf4fcec9712955b80939b20420f4df7b6a599ba02c1a42b370f1694898
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Release files / generalbacktest-1.2.0-py3-none-any.whl
| Download URL | generalbacktest-1.2.0-py3-none-any.whl |
|---|---|
| Size | 43.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a99f894028b48f744deb5590dc2a1ca81a9c6e8597809251f6ce112cf1b2d4b7
|
|
BLAKE2b-256 checksum How to use checksums |
e4ba5dfddf156f1712ee5f102b92eae29ba72bc57573676847a77726e5b2d115
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|