重新平衡仓位
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 |
在回测的第一个月,投资组合优化将整个投资组合分配给了 AAPL、AMZN 和 TSLA。