查看全集:💎Quantopia量化分析56讲
关键公式:
其中:
表示资产收益率向量
表示因子暴露度矩阵(因子载荷)
表示因子收益率向量
表示特质收益向量(随机误差项)
这个公式体现了投资组合收益可以分解为:
工具包配置:
import numpy as np
import pandas as pd
import yfinance as yf
import statsmodels.api as sm
import matplotlib.pyplot as plt
from tqdm import tqdm
步骤详解:
sp500 = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')[0]
tickers = sp500.Symbol.tolist()[:50] # 取前50支简化计算
def download_prices(tickers, start, end):
data = yf.download(tickers, start=start, end=end)['Adj Close']
return data.dropna(axis=1)
prices = download_prices(tickers, '2009-01-01', '2011-01-01')
returns = prices.pct_change().dropna() # 日收益率矩阵
数据结构示例:
Date AAPL MSFT AMZN ...
2009-01-02 0.012 -0.005 0.018
2009-01-03 -0.007 0.015 0.002
Fama-French三因子数据获取:
ff_url = "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_daily_CSV.zip"
ff_data = pd.read_csv(ff_url, skiprows=3, index_col=0, parse_dates=True)
ff_data = ff_data.iloc[:, :3] / 100 # 转换为小数形式
因子对齐处理:
common_dates = returns.index.intersection(ff_data.index)
F = ff_data.loc[common_dates] # 因子收益矩阵
R = returns.loc[common_dates] # 对齐后的资产收益
滚动回归示例:
betas = pd.DataFrame(index=R.columns, columns=F.columns)
for stock in tqdm(R.columns):
model = sm.OLS(R[stock], sm.add_constant(F))
results = model.fit()
betas.loc[stock] = results.params.drop('const')
暴露矩阵结构:
Mkt-RF SMB HML
AAPL 1.05 -0.09 -0.34
MSFT 0.92 0.12 0.21
...
方差分解实现:
# 协方差矩阵计算
factor_cov = F.cov()
specific_var = R.var(axis=0)
# 组合权重(等权示例)
weights = np.ones(len(R.columns)) / len(R.columns)
# 风险分解
common_risk = weights @ betas @ factor_cov @ betas.T @ weights
specific_risk = weights @ np.diag(specific_var) @ weights
total_risk = common_risk + specific_risk
风险占比计算:
print(f"系统性风险占比: {common_risk/total_risk:.2%}")
CVXPY优化示例:
import cvxpy as cp
# 定义优化变量
w = cp.Variable(len(weights))
risk_target = 0.05 # 最大因子暴露
# 构建约束
constraints = [
cp.sum(w) == 1,
w >= 0,
cp.norm(betas.T @ w, 'inf') <= risk_target
]
# 定义目标函数(最大化收益)
portfolio_return = R.mean().values @ w
problem = cp.Problem(cp.Maximize(portfolio_return), constraints)
problem.solve()
优化结果分析:
print("最优权重:", w.value.round(3))
print("预期收益:", portfolio_return.value)
使用PyFolio进行归因:
import pyfolio as pf
# 假设已有组合收益序列
returns_portfolio = ...
# 生成归因报告
pf.create_returns_tear_sheet(returns_portfolio, factor_loadings=betas)
方法类型 | 优点 | 局限性 |
均值-方差优化 | 理论完备 | 对输入参数敏感 |
风险平价模型 | 风险均衡配置 | 忽略收益预测 |
因子风险约束模型 | 明确控制风险来源 | 依赖因子模型准确性 |
小练习:
尝试将市场因子暴露限制在0.8以下,比较优化前后组合的收益波动比变化
# 参考答案
risk_target = 0.8
constraints.append(betas['Mkt-RF'] @ w <= risk_target)
problem.solve()
print(f"夏普比率变化: {portfolio_return.value/problem.constraints[0].dual_value}")