bt: A flexible backtesting framework for Python

repository·master·Indexed 25 days ago

https://github.com/pmorissette/bt

A Python framework for testing quantitative trading strategies using a tree structure of Node objects and modular algorithm stacks (Algo and AlgoStack). It supports complex strategy construction, detailed statistics, and visualization. Key features include various weighting schemes (Equal, Inverse Volatility, ERC, Mean-Variance), transaction cost modeling via CostModel, and specialized security types for fixed income and hedging.

Tokens
24.1K
Snippets
53
Records
144
Agent score
81%

What's inside bt

  1. Overview of bt features

    master

    bt is a flexible backtesting framework for Python designed to test quantitative trading strategies. Key features include:

    • Tree Structure: Uses a tree of Node objects to facilitate the construction of modular and re-usable complex algorithmic trading strategies. Each Node has its own price index for allocation determination.
    • Algorithm Stacks: Uses Algo and AlgoStack to create modular, re-usable, and testable strategy logic.
    • Charting and Reporting: Provides functions to visualize backtest results.
    • Detailed Statistics: Calculates various backtest statistics and provides Result display methods to compare statistics across multiple backtests.
  2. Overview of the bt backtesting framework

    master
    bt is a flexible Python-based backtesting framework designed for testing quantitative trading strategies. It focuses on the rapid development of complex strategies by allowing users to compose modular, reusable, and easily testable blocks of logic using Algos and a tree structure. The framework is built on top of the ffn financial function library and integrates with the broader Python data analysis ecosystem.
  3. Core features of bt

    master

    bt provides several key features for quantitative strategy development:

    • Tree Structure: Uses a hierarchical structure where each Node has its own prices index, facilitating the composition of complex, modular strategies.
    • Algorithm Stacks: Utilizes Algos and AlgoStacks to create reusable and testable strategy logic blocks.
    • Transaction Cost Modeling: Supports commission functions and instrument-specific, time-varying bid/offer spreads via the Backtest class.
    • Fixed Income Support: Includes support for coupon-paying instruments (bonds), unfunded instruments (swaps), holding costs, and notional weighting.
    • Charting and Reporting: Provides visualization tools for backtest results.
    • Detailed Statistics: Calculates comprehensive statistics and provides methods in the Result class to compare statistics across multiple backtests.
  4. Understand Fixed Income Strategy Characteristics in bt

    master

    When using bt for fixed income strategies, the following behaviors apply:

    • Capital Allocations: Capital allocations are not necessary, and initial capital is not used.
    • Bankruptcy: Disabled (money can always be borrowed at a specific rate).
    • Weights: Based on notional_value rather than market value. For fixed income, notional_value represents the position size. For equities, it represents market value.
    • Strategy Notional Value: Always positive, equal to the sum of the magnitudes of the notional_value of all child assets.
    • Strategy Price: Computed from additive PNL returns per unit of notional_value, using PAR as the reference price.
    • Rebalancing: Adjusts notionals rather than capital allocations based on weights.
  5. Implement a Target Volatility strategy with bt.algos

    master

    You can build a target volatility strategy by combining several bt.algos into a bt.Strategy. A typical workflow involves:

    1. Scheduling: Use bt.algos.RunWeekly(run_on_first_date=True) to trigger the strategy at specific intervals.
    2. Selection: Use bt.algos.SelectThese(['asset1', 'asset2']) to filter the universe.
    3. Weighting: Use bt.algos.WeighInvVol(lookback=..., lag=...) to set weights based on inverse volatility.
    4. Volatility Targeting: Use bt.algos.TargetVol(target_vol, lookback=..., lag=..., covar_method='standard', annualization_factor=252) to scale the portfolio to a specific annualized volatility.
    5. Rebalancing: Use bt.algos.Rebalance() to apply the calculated weights to the portfolio.

    Note: The TargetVol algo uses the weights calculated by the preceding weighting algo (stored in target.temp) to scale the overall portfolio volatility.

    import bt
    import pandas as pd
    
    # 1. Schedule execution
    runWeeklyAlgo = bt.algos.RunWeekly(run_on_first_date=True)
    
    # 2. Select assets
    selectTheseAlgo = bt.algos.SelectThese(['foo', 'bar'])
    
    # 3. Set weights to 1/vol contributions
    weighInvVolAlgo = bt.algos.WeighInvVol(
        lookback=pd.DateOffset(months=12),
        lag=pd.DateOffset(days=1)
    )
    
    # 4. Target annualized 10% volatility
    targetVolAlgo = bt.algos.TargetVol(
        0.1,
        lookback=pd.DateOffset(months=12),
        lag=pd.DateOffset(days=1),
        covar_method='standard',
        annualization_factor=252
    )
    
    # 5. Rebalance to the weights set in target.temp
    rebalAlgo = bt.algos.Rebalance()
    
    # Combine into a Strategy
    strat = bt.Strategy('target_vol_strategy', [
        runWeeklyAlgo,
        selectTheseAlgo,
        weighInvVolAlgo,
        targetVolAlgo,
        rebalAlgo
    ])
  6. Customize the klink theme locally

    master

    To customize the theme's appearance, clone the klink repository into your documentation's _themes folder (create it if it doesn't exist).

    To change styles, edit the .less files directly. You will need lessc to compile the LESS files into CSS. After cloning, update your conf.py to point to the local directory:

    html_theme = 'klink'
    html_theme_path = ['_themes']
    html_theme_options = {
        'github': 'yourname/yourrepo',
        'analytics_id': 'UA-your-number-here',
        'logo': 'logo.png'
    }
  7. Build complex strategies using the Tree Structure

    master

    The bt framework uses a tree structure to allow mixing and matching of securities and strategies. A bt.Strategy can have children that are either bt.Security objects or other bt.Strategy objects. This allows for sophisticated capital allocation, such as a parent strategy that allocates weight between a passive bond (a security) and a momentum strategy (a sub-strategy).

    To build a strategy, you can pass a list of children to the bt.Strategy constructor. Children can be represented by their ticker strings (which are automatically converted to bt.Security instances) or by existing bt.Strategy objects.

    import bt
    
    # 1. Create a sub-strategy (e.g., momentum)
    # The 3rd argument ['spy', 'eem'] limits the universe for this strategy
    mom_s = bt.Strategy('mom_s', [bt.algos.RunMonthly(),
                                  bt.algos.SelectAll(),
                                  bt.algos.SelectMomentum(1),
                                  bt.algos.WeighEqually(),
                                  bt.algos.Rebalance()],
                         ['spy', 'eem'])
    
    # 2. Create a parent strategy
    # One child is the sub-strategy (mom_s), the other is a security ('agg')
    parent = bt.Strategy('parent', [bt.algos.RunMonthly(),
                                    bt.algos.SelectAll(),
                                    bt.algos.WeighEqually(),
                                    bt.algos.Rebalance()],
                          [mom_s, 'agg'])
    
    # 3. Run the backtest
    t = bt.Backtest(parent, data)
    r = bt.run(t)
  8. Combine strategies by merging price data

    master

    If you need to determine the weights of each strategy within a combined portfolio, you can run each strategy independently, merge their resulting price dataframes, and then run a combined strategy on that merged dataset.

    To implement this:

    1. Run individual bt.Backtest objects for each strategy.
    2. Use bt.merge() to combine the .prices attribute from each result into a single DataFrame.
    3. Create a new bt.Strategy and run a bt.Backtest using the merged price DataFrame.
  9. Implement a custom Trend Signal algorithm

    master

    You can create custom trading logic by subclassing bt.Algo. A signal algorithm typically calculates a metric (like total return over a lookback period) and stores it in target.temp for subsequent algorithms to use.

    Key implementation details:

    • Use __init__ to define parameters like lookback and lag (using pd.DateOffset).
    • In __call__(self, target), access price data via target.universe[selected].
    • Store results in target.temp['Signal'] to pass data down the algorithm chain.
    • Return True to continue the algorithm execution or False to stop.
    class Signal(bt.Algo):
        def __init__(self, lookback=pd.DateOffset(months=3), lag=pd.DateOffset(days=0)):
            super(Signal, self).__init__()
            self.lookback = lookback
            self.lag = lag
    
        def __call__(self, target):
            selected = 'foo'
            t0 = target.now - self.lag
            
            if target.universe[selected].index[0] > t0:
                return False
                
            prc = target.universe[selected].loc[t0 - self.lookback:t0]
            trend = prc.iloc[-1]/prc.iloc[0] - 1
            signal = trend > 0.
            
            if signal:
                target.temp['Signal'] = 1.
            else:
                target.temp['Signal'] = 0.
            return True
  10. Configure the klink theme in Sphinx

    master

    To use klink as your Sphinx documentation theme, update your conf.py file with the following settings. You can provide a GitHub repository path, a Google Analytics ID, and a path to a logo image via html_theme_options.

    import klink
    
    html_theme = 'klink'
    html_theme_path = [klink.get_html_theme_path()]
    html_theme_options = {
        'github': 'yourname/yourrepo',
        'analytics_id': 'UA-your-number-here',
        'logo': 'logo.png'
    }