# 被动型FOF产品规划方案（附代码）

## 资产配置模型

### 马科维兹模型

E ( R p ) = ∑ i ω i E ( R i ) E(R_p)=\sum_{i}^{}\omega_iE(R_i)

σ p 2 = ∑ i ω i 2 σ i 2 + ∑ i ∑ i ≠ j ω i ω j σ i σ j ρ i j \sigma_{p}^{2}=\sum_{i}^{}\omega_{i}^{2}\sigma_{i}^{2}+\sum_{i}^{}\sum_{i\neq j}^{}\omega_i\omega_j\sigma_i\sigma_j\rho_{ij}

ω T ∑ ω −   q   ∗ R T ω \omega ^{T}\sum \omega-~q~*R^{T}\omega

#定义马科维茨优化的目标函数
def funs(weight,sigma):
weight = np.array([weight]).T
result = np.dot(np.dot(weight.T,np.mat(sigma)),weight)[0,0]
return(result)


### 风险平价模型

σ ( ω ) = ω ′ Σ ω ′ \sigma(\omega)=\sqrt{\omega^{'}\Sigma\omega^{'} }

σ ( ω i ) = ω i ∗ ∂ σ ( ω ) ∂ ω i = ω i ( Σ ω ) i ω ′ Σ ω ′ = σ ( ω ) N \sigma(\omega_i)=\omega_i\ast \frac{\partial \sigma(\omega)}{\partial \omega_i} \\=\frac{\omega_{i}(\Sigma\omega)_{i}}{\sqrt{\omega^{'}\Sigma\omega^{'}} } =\frac{\sigma(\omega)}{N}

a r g m i n ω ∑ i = 1 N [ ω i − ω ( σ ) 2 ( Σ ω ) i N ] 2 argmin\omega \sum_{i=1}^{N}[\omega_i-\frac{\omega(\sigma)^{2}}{(\Sigma\omega)_iN}]^{2}

#定义风险平价优化的目标函数
def funsRP(weight,sigma):
weight = np.array([weight]).T
X = np.multiply(weight,np.dot(sigma.values,weight))
result = np.square(np.dot(X,np.ones([1,X.shape[0]])) - X.T).sum()
return(result)


### Black Littterman模型

Black Litterman模型对马科维兹模型进行改进，在收益率序列分布上，用贝叶斯统计理论将投资者对大类资产的观点与市场均衡回报相结合。

β = C o v ( r , w T r ) V a r ( w T r ) = C o v ( r , r ) w V a r ( w T r ) = 1 σ 2 Σ w \beta=\frac{Cov(r,w^Tr)}{Var(w^Tr)}=\frac{Cov(r,r)w}{Var(w^Tr)}=\frac{1}{\sigma^2}\Sigma w

E x c e s s   R e t u r n = ( w T r ) β = ( w T r ) β = w T r σ 2 Σ w Excess~Return=(w^Tr)\beta \\=(w^Tr)\beta=\frac{w^Tr}{\sigma^2}\Sigma w

P E ( r ) = q + v v ∼ N ( 0 , Ω ) P E ( r ) ∼ N ( q , Ω ) 假 设   E x c e s s   e t u r n ∣ E ( r ) ∼ N ( E ( r ) , τ Σ ) PE(r)=q+v \\ v\sim N(0,\Omega) \\ PE(r)\sim N(q,\Omega) \\假设~Excess~eturn|E(r)\sim N(E(r),\tau\Sigma)

min ⁡ E ( r )   ( E ( r ) − E x c e s s   R e t u r n ) T ( τ Σ ) − 1 ( E ( r ) − E x c e s s   R e t u r n ) s . t .   P E ( r ) = q \min_{E(r)}\ (E(r)-Excess~Return)^T(\tau\Sigma)^{-1}(E(r)-Excess~Return) \\ s.t.\ PE(r)=q

#定义blacklitterman函数
def blacklitterman(returns, Tau, P, Q):
mu = returns.mean()
sigma = returns.cov()
pi1 = mu
ts = Tau * sigma
Omega = np.dot(np.dot(P, ts), P.T) * np.eye(Q.shape[0])
middle = linalg.inv(np.dot(np.dot(P, ts), P.T) + Omega)
er = np.expand_dims(pi1, axis=0).T + np.dot(np.dot(np.dot(ts, P.T), middle),
(Q - np.expand_dims(np.dot(P, pi1.T), axis=1)))
newList = []
for item in er:
if type(item) == list:
tmp = ''
for i in item:
tmp += float(i) + ' '
newList.append(tmp)
else:
newList.append(item)
New = []
for j in newList:
k = float(j)
New.append(k)
posteriorSigma = sigma + ts - np.dot(ts.dot(P.T).dot(middle).dot(P), ts)
return [New, posteriorSigma]


## 策略实现

### 加载包

import pandas as pd
import numpy as np
from scipy import stats
import math
import matplotlib.pyplot as plt
#import ffn
#计算最大回撤时使用的包，也可以使用自行撰写的函数运行，使用该包可以通过pip install进行安装
from tqdm import tqdm
#查看程序运行进度的包，如果没有安装请注释掉该行，之后程序仍然可以正常运行
import statsmodels.api as sm
from scipy import linalg
import scipy.optimize as sco
import datetime
from datetime import date
import seaborn as sns
from scipy.optimize import minimize
from sklearn.covariance import  ledoit_wolf
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r'c:\windows\fonts\simkai.ttf',size = 15)
# 如果有wind数据库，请加载wind数据库接口，接口修复在“我的-自动修复插件-修复Python接口”，修复时需要关闭Python
from WindPy import *
w.start()


### 投资组合评价

#定义策略期间表现的函数，策略需要包括一列列标为nav，含义为投资组合的净值
def performance(strategy):
def MaxDrawdown(return_list):
RET_ACC = []
sum = 1
for i in range(len(return_list)):
sum = sum * (return_list[i] + 1)
RET_ACC.append(sum)
index_j = np.argmax(np.array((np.maximum.accumulate(RET_ACC) - RET_ACC) / 								np.maximum.accumulate(RET_ACC)))
index_i = np.argmax(RET_ACC[:index_j])
MDD = (RET_ACC[index_i] - RET_ACC[index_j]) / RET_ACC[index_i]
return sum, MDD, RET_ACC
"""def MaxDrawdown2(return_list):
value = (1 + return_list).cumprod()
MDD = ffn.calc_max_drawdown(value)
return -MDD"""
# 如果要考虑rf的话启用下面这个sharp函数
def sharp(return_list, std):
returnew = pd.DataFrame(return_list, columns=['R'])
m = pd.concat([returnew.R], axis=1)
sharpratio = np.average(ret_adj) * 12 / std
return sharpratio

def Reward_to_VaR(strategy=strategy, alpha=0.99):  # 99%置信水平，历史收益率法计算VaR
RET = strategy.nav.pct_change(1).fillna(0)
sorted_Returns = np.sort(RET)
index = int(alpha * len(sorted_Returns))
var = abs(sorted_Returns[index])
RtoVaR = np.average(RET) / var
return -RtoVaR

def Reward_to_CVaR(strategy=strategy, alpha=0.99):   # 99%置信水平，历史收益率法计算CVaR
RET = strategy.nav.pct_change(1).fillna(0)
sorted_Returns = np.sort(RET)
index = int(alpha * len(sorted_Returns))
sum_var = sorted_Returns[0]
for i in range(1, index):
sum_var += sorted_Returns[i]
CVaR = abs(sum_var / index)
RtoCVaR = np.average(RET) / CVaR
return -RtoCVaR

ts = strategy.nav.pct_change(1).fillna(0)
RET = (strategy.nav[strategy.shape[0] - 1] /strategy.nav[0])**
(252 / strategy.shape[0]) - 1
T = stats.ttest_1samp(ts, 0)[0]
STD = np.std(ts) * np.sqrt(252)
MDD = MaxDrawdown(ts)[1]
ACC = MaxDrawdown(ts)[0]
SHARP = (RET - 0.03) / STD  # 默认年化无风险收益率为3%
R2VaR = Reward_to_VaR(strategy)
R2CVaR = Reward_to_CVaR(strategy)
print('annual-return', round(RET, 4))
print('t-statistic', round(T, 4))
print('volitility', round(STD, 4))
print('MaxDrawdown', round(MDD, 4))
print('Accumulated return', round(ACC, 4))
print('sharp-ratio', round(SHARP, 4))
print('Reward_to_VaR', round(R2VaR, 4))
print('Reward_to_CVaR', round(R2CVaR, 4))
return RET, T, STD, MDD, ACC, SHARP, R2VaR, R2CVaR


### 输入相关参数

start = input("请输入开始日期，格式为xxxx/xx/xx: ")
end = input("请输入结束日期，格式为xxxx/xx/xx: ")
startdate = datetime.strptime(start,'%Y/%m/%d')
enddate = datetime.strptime(end,'%Y/%m/%d')

codes=input("请输入要研究的指数代码，并以逗号隔开：")
#000985.CSI,H11001.CSI,CCFI.WI,AU9999.SGE,SPX.GI,HSI.HI
datas = w.wsd(codes, "close", startdate, enddate, "")
datas = pd.DataFrame(np.array(datas.Data).T,columns = datas.Codes,index = datas.Times)

rolling = input("请输入滚动回测区间长度（天）：")
rollingtimeinput = float(rolling)
#本产品方案的回测区间为输入数据第一天至历史上的当前时间，
#滚动回测的数据输入仅用于blacklitterman策略的开始生效日
#以及输出结果的开始日期与输入数据第一天之间的间隔长度

perio = input("请输入调仓频率（月/次）：")
periodinput = float(perio)


### 策略的主函数

def ProductPlan(datas, expected_return, period=1, rollingtime=126, method='MAC', tau=0.01,wmin=0,wmax=1):
#datas为指数或者标的资产的日度收盘价数据，格式为dataframe，行标为日期（格式为日期），列表为资产的代码或者名称
#expected_return格式和形式同datas
#peiod为策略频率，单位为月，缺省值为1
#rollingtime为策略滚动回测时间，单位为天，缺省值为126
#本产品方案的回测区间为输入数据第一天至历史上的当前时间，
#滚动回测的数据输入仅用于blacklitterman策略的开始生效日
#以及输出结果的开始日期与输入数据第一天之间的间隔长度
#method为策略的方式，选项为 MAC, RP, BL,缺省值为MAC
#tau为blacklitterman模型的输入参数，为主观观点与历史收益率的比率，缺省值为0.01
#wmin为资产配置比例的最小值，缺省值为0
#wmax为资产配置比例的最大值，缺省值为1
ret = datas.pct_change(1).fillna(0) #计算标的资产的日度收益率数据，缺失值填充为0
data_norm = datas / datas.iloc[0,] * 1000 #将指数收盘价的第一天统一调整为1000
result = data_norm.copy() #构架过程输入的dataframe
result['m'] = result.index
result['m'] = result.m.apply(lambda x: x.month) #计算每一天属于一年12个月中的哪一个月，方便月度调仓的计算
weights = pd.DataFrame(columns=datas.columns, index=datas.index).fillna(0)  #构建空的dataframe用于存放权重
N = pd.DataFrame(columns=datas.columns, index=datas.index).fillna(0)  #构建空的dataframe用于存放资产的数量
datas_index = np.array(datas.index) #日期序列
noa = datas.shape[1] #number of asset 资产的数量
position = 0 #用于数据迭代的一个初始值
#优化问题的约束条件，可以设置不等式约束或者方程约束，eq表示等式，ineq表示不等式（小于等于）
cons = ({'type': 'eq', 'fun': lambda x:  1 - sum(x)})#所有权重相加为1
#优化问题的边界
bnds = tuple((wmin, wmax) for i in range(datas.shape[1]))#所有权重的上下界

if method == 'MAC':
for i in tqdm(range(result.shape[0])):
if i == 0:
pass
elif result.m[i] != result.m[i - 1] and result.m[i] % int(period) == 0:
sigma = ret.iloc[position:i].cov()
position = i
weight = [0 for i in range(datas.shape[1])]
res =  minimize(funs,weight, method='SLSQP',args = (sigma,),
bounds=bnds,constraints=cons,tol=1e-8)
weights.iloc[i,:] =  res.x
#将优化出来的权重输入到weights这个空的dataframe中
price = datas.loc[datas.index[i],:]
V = (weights.iloc[i,:]*price).sum()
n = V*weights.iloc[i,:].values/price.values
N.loc[result.index[i],:] = n
else:

N.iloc[i,:] = N.iloc[i-1,:]
w = N.iloc[i,:]*datas.loc[datas.index[i],:]
weights.iloc[i,:] = w/w.sum()
elif method =='RP':
for i in tqdm(range(result.shape[0])):
if i == 0:
pass
elif (result.m[i] != result.m[i - 1])and result.m[i] % int(period) == 0:
sigma = ret.iloc[position:i].cov()
position = i
weight = [0 for i in range(datas.shape[1])]
res =  minimize(funsRP,weight, method='SLSQP',args = (sigma,),
bounds=bnds,constraints=cons,tol=1e-20)

weights.iloc[i,:] =  res.x
price = datas.loc[datas.index[i],:]
V = (weights.iloc[i,:]*price).sum()
n = V*weights.iloc[i,:].values/price.values
N.loc[result.index[i],:] = n
else:
N.iloc[i,:] = N.iloc[i-1,:]
w = N.iloc[i,:]*datas.loc[datas.index[i],:]
weights.iloc[i,:] = w/w.sum()
elif method =='BL':
# 构造观点矩阵
pick1 = np.array([1 for i in range(noa)]) # 所有资产的预期收益
pick21 = [0 for i in range(noa-2)]
pick22 = [0.5,-0.5]
pick2 = np.array(pick22+pick21)  # 股票和债券的溢价
P = np.array([pick1, pick2])
for i in tqdm(range(result.shape[0])):
# 输入每一期的目标收益率
#targetanlrate = goalrate.iat[i, 0]
if i == 0:
weights.iloc[i, :] = 1 / datas.shape[1]
price = datas.loc[datas.index[i], :]
n = weights.iloc[i, :].values / price.values
N.loc[result.index[i], :] = n
del price, n
elif (result.m[i] != result.m[i - 1]) and (i > int(rollingtime))and (result.m[i] % int(period)) == 0:
Rett = ret[i - int(rollingtime):i]
expected_return['sum'] = expected_return.sum(axis=1)
- expected_return1.iloc[:,1]
Q = expected_return.iloc[i:i + 1,
(expected_return.shape[1] -2):
expected_return.shape[1]].T.values

Returns = blacklitterman(Rett, tau, P, Q)[0]
sigma = blacklitterman(Rett, tau, P, Q)[1]
weight = [0 for i in range(datas.shape[1])]
res =  minimize(funs,weight, method='SLSQP',args = (sigma,),
bounds=bnds,constraints=cons,tol=1e-8)

weights.iloc[i,:] =  res.x
price = datas.loc[datas.index[i],:]
V = (weights.iloc[i,:]*price).sum()
n = V*weights.iloc[i,:].values/price.values
N.loc[result.index[i],:] = n
else:

N.iloc[i,:] = N.iloc[i-1,:]
w = N.iloc[i,:]*datas.loc[datas.index[i],:]
weights.iloc[i,:] = w/w.sum()

else:
pass

result['mv'] = 0
result['nav'] = 1
for i in tqdm(range(result.shape[0])):
result.loc[result.index[i],'mv'] = (datas.iloc[i,:]*N.iloc[i,:]).sum()
if all(N.iloc[i,:]==0):
pass
elif all(N.iloc[i,:] == N.iloc[i-1,:] ):
result.loc[result.index[i-1],'mv']
result.loc[result.index[i],'nav'] = result.nav[i-1]*result.mv[i]
else:
(datas.iloc[i-1,:]*N.iloc[i,:]).sum()
result.loc[result.index[i],'nav'] =

result = result.iloc[int(rollingtime):,:]
weights = weights.iloc[int(rollingtime):,:]
result['nav'] = result.nav/result.nav[0]*1000
return weights,result
#最后输出两个dataframe，weight为权重，result为计算过程，开始日期为原datas往后推rollingtime个交易日


## 策略运行结果

itemsMarkovitzRisk parityBlack litterman
annual-return0.05360.05760.0514
t-statistic2.42374.38532.4006
volatility0.07170.04110.0695
Max Draw-down0.15040.07380.1203
Accumulated return1.6921.75781.6579
sharp-ratio0.32860.67010.3083
Reward_to_VaR-0.0199-0.033-0.0195
Reward_to_CVaR-2.7436-1.6979-2.8700

Black Litterman模型运行的结果如下图所示：

Reference: Black Litterman 数学推导

• 点赞
• 评论
• 分享
x

海报分享

扫一扫，分享海报

• 收藏 6
• 打赏

打赏

天天学习的零柒贰幺

你的鼓励将是我创作的最大动力

C币 余额
2C币 4C币 6C币 10C币 20C币 50C币
• 举报
• 一键三连

点赞Mark关注该博主, 随时了解TA的最新博文

05-09 3535

04-27 5413
08-21 2114
06-29 2625
03-09 8850
03-09 1万+
01-10 127
12-20 2540
08-31 6724
07-21 4922
07-19 8696
09-20 3681
03-07 2503