重新平衡仓位

PyBroker 可让你通过调整资产配置以匹配目标配置来模拟投资组合再平衡。本文档还将演示如何使用 投资组合优化 进行再平衡。

[1]:
import pybroker
from pybroker import ExecContext, Strategy, YFinance

pybroker.enable_data_source_cache("rebalancing")
[1]:
<pybroker.cache._L1Cache at 0x7f2c8c0cb530>

等额仓位配置

假设我们希望在每个月初对一个仅做多的投资组合进行再平衡,以便为每只股票维持相等的配置。

首先,我们编写一个辅助函数,用于检测当前 K 线是否为新月份的开始:

[2]:
def start_of_month(ctxs: dict[str, ExecContext]) -> bool:
    dt = tuple(ctxs.values())[0].dt
    if dt.month != pybroker.param("current_month"):
        pybroker.param("current_month", dt.month)
        return True
    return False

接下来,我们编写一个 rebalance 函数,在每个月初为每个资产设置相等的目标配置:

[3]:
def rebalance(ctxs: dict[str, ExecContext]):
    if start_of_month(ctxs):
        target = 1 / len(ctxs)
        for ctx in ctxs.values():
            ctx.set_target_shares(target, dir="long")

在完成 rebalance 函数后,我们可以使用包含五只股票的投资组合对策略进行回测。为了在每个数据条上同时处理所有股票,我们使用 Strategy.set_after_exec 方法:

[4]:
strategy = Strategy(YFinance(), start_date="1/1/2018", end_date="1/1/2023")
strategy.add_execution(None, ["TSLA", "NFLX", "AAPL", "NVDA", "AMZN"])
strategy.set_after_exec(rebalance)
result = strategy.backtest()
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00

Loading bar data...
[*********************100%***********************]  5 of 5 completed
Loaded bar data: 0:00:00

Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time:  0:00:00

Finished backtest: 0:00:01

set_after_exec 函数会在通过 add_execution 添加的所有执行完成后运行。由于我们向 add_execution 传入了 None,因此在 after_exec 之前不会运行任何执行逻辑。

[5]:
result.orders
[5]:
type symbol date created order_type intent shares limit_price market_price fill_price fees
id
1 buy AAPL 2018-01-03 2018-01-02 market buy_to_open 464 NaN 43.31 43.31 0.0
2 buy AMZN 2018-01-03 2018-01-02 market buy_to_open 336 NaN 59.84 59.84 0.0
3 buy NFLX 2018-01-03 2018-01-02 market buy_to_open 994 NaN 20.39 20.39 0.0
4 buy NVDA 2018-01-03 2018-01-02 market buy_to_open 4013 NaN 5.22 5.22 0.0
5 buy TSLA 2018-01-03 2018-01-02 market buy_to_open 869 NaN 21.36 21.36 0.0
... ... ... ... ... ... ... ... ... ... ... ...
293 sell NFLX 2022-12-02 2022-12-01 market sell_to_close 153 NaN 31.60 31.60 0.0
294 sell NVDA 2022-12-02 2022-12-01 market sell_to_close 974 NaN 16.69 16.69 0.0
295 buy AAPL 2022-12-02 2022-12-01 market buy_to_open 27 NaN 146.82 146.82 0.0
296 buy AMZN 2022-12-02 2022-12-01 market buy_to_open 41 NaN 94.57 94.57 0.0
297 buy TSLA 2022-12-02 2022-12-01 market buy_to_open 70 NaN 193.68 193.68 0.0

297 rows × 11 columns

投资组合优化

投资组合优化 可指导再平衡以实现特定目标,例如以最小化风险的方式配置股票。

Riskfolio-Lib 是一个用于投资组合优化的流行 Python 库。你可以使用 pip install riskfolio-lib 进行安装。

以下示例演示了如何通过最小化过去一年收益的 条件风险价值(CVaR) 来构建一个最小风险投资组合:

[6]:
import pandas as pd
import riskfolio as rp

pybroker.param("lookback", 252)  # Use past year of returns.


def calculate_returns(ctxs: dict[str, ExecContext], lookback: int):
    prices = {}
    for symbol, ctx in ctxs.items():
        prices[symbol] = ctx.adj_close[-lookback:]
    df = pd.DataFrame(prices)
    return df.pct_change().dropna()


def optimization(ctxs: dict[str, ExecContext]):
    lookback = pybroker.param("lookback")
    if start_of_month(ctxs):
        Y = calculate_returns(ctxs, lookback)
        port = rp.Portfolio(returns=Y)
        port.assets_stats(method_mu="hist", method_cov="hist")
        w = port.optimization(
            model="Classic",
            rm="CVaR",
            obj="MinRisk",
            rf=0,  # Risk free rate.
            l=0,  # Risk aversion factor.
            hist=True,  # Use historical scenarios.
        )
        for symbol, ctx in ctxs.items():
            target = w.T[symbol].values[0]
            ctx.set_target_shares(target, dir="long")

有关更多信息和示例,请参阅 Riskfolio-Lib 官方文档。接下来,我们对策略进行回测:

[7]:
strategy.set_after_exec(optimization)
result = strategy.backtest(warmup=pybroker.param("lookback"))
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00

Loaded cached bar data.

Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:01 Time:  0:00:01

Finished backtest: 0:00:01
[8]:
result.orders.head()
[8]:
type symbol date created order_type intent shares limit_price market_price fill_price fees
id
1 buy AAPL 2019-01-04 2019-01-03 market buy_to_open 1420 NaN 36.54 36.54 0.0
2 buy AMZN 2019-01-04 2019-01-03 market buy_to_open 347 NaN 77.81 77.81 0.0
3 buy TSLA 2019-01-04 2019-01-03 market buy_to_open 1020 NaN 20.69 20.69 0.0
4 sell AAPL 2019-02-04 2019-02-01 market sell_to_close 103 NaN 42.37 42.37 0.0
5 buy AMZN 2019-02-04 2019-02-01 market buy_to_open 1 NaN 81.58 81.58 0.0

在回测的第一个月,投资组合优化将整个投资组合分配给了 AAPLAMZNTSLA