Skip to main content

GeneralBacktest

PyPI version Python 3.8+ License: MIT

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

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:
    • quantchdb for database-backed ETF/stock data
    • openpyxl for 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_phase must follow sell_phase in 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:

  1. Normalize per-date asset weights so they sum to 1.
  2. Multiply normalized weights by position_ratio of that date.
  3. 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 Cash
  • Cash Ratio
  • Avg 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 backtest
  • run_backtest_ETF(...) — ETF data from database
  • run_backtest_stock(...) — stock data from database
  • run_backtest_with_cash(...) — cash-constrained execution
  • TBacktest.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_col in run_backtest() defaults to None.
  • log_scale in plot_nav_curve() defaults to False.
  • run_backtest_with_cash() and plot_nav_curve_dual() are new v1.1.0 methods.
  • TBacktest is a new v1.2.0 class — it does not affect existing GeneralBacktest usage.

Contributing

Issues and pull requests are welcome.

License

MIT License. See LICENSE.

Author

Elen Young - yang13515360252@163.com

Links

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)

Source distribution for GeneralBacktest 1.2.0
File Size Uploaded
generalbacktest-1.2.0.tar.gz 46.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for GeneralBacktest 1.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

1.4.0

2 release files

1.3.0

2 release files

1.2.1

2 release files

This release

1.2.0 This release

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

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