Risk Parity
- リングオンリーのRisk Parity(Equal Risk Contribution)ポートフォリオのウェイトを算出
- 過去リターンを用いて推定リスクを算出
# 合計ウェイト=1
def weight_sum_constraint(x):
return x.sum() - 1.0
# ロングオンリー
def weight_longonly(x):
return x
# リスク寄与
def rc(weight, covmat):
weight = np.array(weight)
variance = weight.T @ covmat @ weight
sigma = variance ** 0.5
mrc = 1 / sigma * (covmat @ weight)
rc_ = weight * mrc
rc_out = rc_ / rc_.sum()
return rc_out
# 目的関数(各構成要素のリスク寄与が同じ)
def riskparity_objective(x):
variance = x.T @ covmat @ x
sigma = variance ** 0.5
mrc = 1 / sigma * (covmat @ x)
rc = x * mrc
a = np.reshape(rc, (len(rc), 1))
risk_diffs = a - a.T
sum_risk_diffs_squared = np.sum(np.square(np.ravel(risk_diffs)))
return sum_risk_diffs_squared
def riskparity(covmat):
x0 = np.diag(covmat)
constraints = ({'type': 'eq', 'fun': weight_sum_constraint},
{'type': 'ineq', 'fun': weight_longonly})
options = {'ftol': 1e-20, 'maxiter': 800}
result = minimize(fun=riskparity_objective,
x0=x0,
method='SLSQP',
constraints=constraints,
options=options)
# print(result.success)
return result.x
ウェイト計算例
- インプットデータ
- df_ret:リターンデータ(dataframe)
- ポートフォリオ構築に求めるリターン日数:252日
- 時点:2018/12/31
lookback = 252
d = '2018/12/31'
result_weight = {}
ret0 = df_ret[:d][-1 * lookback:]
# 共分散行列
covmat = ret0.cov()
# ウェイトを算出
result_weight[d] = riskparity(covmat)
# dictionary→dataframeにし、列名を整理
DF_weight_port = pd.DataFrame.from_dict(result_weight).transpose()
DF_weight_port.columns = df_ret_set.columns
- インプットが共分散行列なので、ヒストリカルリターンからのdf.cov()以外に、Shrinkageなど、工夫を凝らした共分散行列を用いる場合でも、上記プログラムに変更の必要はないはず
- 60/40やその他ポートフォリオ構築方法との比較の実証分析を今後アップ予定。。。