Skip to content
All library documents

Threshold-Based Rebalancing for a Multi-Asset Stock and Bond Portfolio

Code Lumibot strategies

Summary

This example describes a daily portfolio rebalancer for five exchange-traded funds spanning US equities, long-term Treasury bonds, and gold. It starts by checking each holding’s weight against a target allocation. A rebalance is triggered on the first iteration, when a target holding is missing, or when a holding’s absolute weight drift exceeds a configurable threshold. The strategy then estimates target share quantities from portfolio value and recent prices, and submits buy or sell orders to close the quantity gaps.

The document gives code and a backtest setup using Yahoo Finance data over a specified date range, with SPY as the benchmark, but reports no performance results. The approach illustrates a simple rule for controlling allocation drift; it does not compare alternative thresholds or evaluate returns. It also omits transaction costs, taxes, cash constraints, and execution details. Its weight check examines existing positions, while the rebalance step relies on current prices and whole-share quantities, so actual allocations may differ from targets.

Key ideas

  • The strategy checks portfolio weights once per trading day against predefined target allocations.
  • A rebalance occurs on the first iteration, when a target asset is missing, or when its weight drift exceeds the threshold.
  • Target quantities are estimated from total portfolio value and each asset’s latest price.
  • The example provides a backtest configuration but no evidence of strategy performance.
  • Transaction costs, taxes, cash limits, and execution effects are not modeled in the example.

Tags

Full text
# drift_rebalancer.py


```py
"""
Drift Rebalancer

Asset class: Multi-Asset (Stocks)
Data source: Yahoo Finance (free)
Description: Maintains a target portfolio allocation and rebalances
when any position drifts beyond a configurable threshold.
"""

from datetime import datetime
from lumibot.strategies import Strategy
from lumibot.backtesting import YahooDataBacktesting


class DriftRebalancer(Strategy):
    parameters = {
        "targets": {
            "SPY": 0.40,   # 40% US large cap
            "QQQ": 0.25,   # 25% tech/growth
            "IWM": 0.15,   # 15% small cap
            "TLT": 0.15,   # 15% long-term bonds
            "GLD": 0.05,   # 5% gold
        },
        "drift_threshold": 0.03,  # 3% drift triggers rebalance
    }

    def initialize(self):
        self.sleeptime = "1D"

    def on_trading_iteration(self):
        targets = self.parameters["targets"]
        threshold = self.parameters["drift_threshold"]
        portfolio = self.portfolio_value

        # Calculate current weights
        needs_rebalance = self.first_iteration
        for symbol, target_weight in targets.items():
            position = self.get_position(symbol)
            if position:
                price = self.get_last_price(symbol)
                current_weight = (position.quantity * price) / portfolio
                drift = abs(current_weight - target_weight)
                if drift > threshold:
                    needs_rebalance = True
                    self.log_message(f"{symbol}: current {current_weight:.1%}, target {target_weight:.1%}, drift {drift:.1%}")
            else:
                needs_rebalance = True

        if not needs_rebalance:
            return

        # Rebalance
        self.log_message("Rebalancing portfolio")
        for symbol, target_weight in targets.items():
            price = self.get_last_price(symbol)
            if not price:
                continue

            target_qty = int((portfolio * target_weight) // price)
            position = self.get_position(symbol)
            current_qty = position.quantity if position else 0
            diff = target_qty - current_qty

            if diff > 0:
                self.submit_order(self.create_order(symbol, diff, "buy"))
            elif diff < 0:
                self.submit_order(self.create_order(symbol, abs(diff), "sell"))


if __name__ == "__main__":
    DriftRebalancer.backtest(
        YahooDataBacktesting,
        datetime(2020, 1, 1),
        datetime(2024, 1, 1),
        benchmark_asset="SPY",
    )

```

Shown in full with attribution under the source's licence. Licence: MIT

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.