1. 程式人生 > >基於風險平價的資產配置策略

基於風險平價的資產配置策略

程式碼如下:
####### step1
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from scipy.optimize import minimize


# 讀取收盤價資料
def read_data(file):
    s="C:/Users\Anita\Desktop"
    file_address=s+file
    df= pd.read_excel(file_address,sheetname="data") 
    # 保證金比例
    marginal_cost=[0.15,0.3,0.02,0.15,1,1]
    # 刪除前兩行說明行
    df.drop(0,inplace=True)
    df.drop(1,inplace=True)
    # 刪除最後一行2018年的3月資料
    df.drop(1497,inplace=True)
    # 把日期列設定為索引
    df.set_index('Date',inplace=True)
    # 缺失值檢驗:沒有缺失值
    df.isnull().any()  
    # 把日資料轉換為月資料
    period_type = 'M'
    __df= df.resample(period_type).last()
    monthly_df=pd.concat([df.iloc[0,0::].T,__df.T],axis=1).T
    return monthly_df

# 計算對數收益率
def calculate_log_return(file):
    data=read_data(file)
    variable_list=['IF00.CFE','IC00.CFE','TF00.CFE','AU00.SHF','159920.OF','511880.SH']
    for j in variable_list:
        data['return_'+str(j)]=0.0
        for i in range(1,len(data)):
            data['return_'+str(j)][i]=np.log(float(data[j][i])/float(data[j][i-1]))
    return data.iloc[1::,0::]


# 將收益率進行標準化處理,形成新的表格std_data
def standardlize_process(file):
    data=calculate_log_return(file)
    scaler=StandardScaler()
    scaler.fit(data)
    column_list=['IF00.CFE','IC00.CFE','TF00.CFE','AU00.SHF','159920.OF','511880.SH','return_IF00.CFE','return_IC00.CFE','return_TF00.CFE','return_AU00.SHF','return_159920.OF','return_511880.SH']
    std_data=pd.DataFrame(scaler.transform(data),columns=column_list,index=data.index)
    std_data=std_data.iloc[:,6::] 
    return std_data

#資產收益率序列相關性檢驗:滾動計算資產的協方差矩陣
def cov_asset(file):
    return standardlize_process(file).rolling(window=12).cov().iloc[66::,0::] 
    # 風險度量期間為一年,故滾動視窗設定為12個月,從2012-12月末到2018-02月末共計 63個月,每個月6條記錄,共63*6=378條記錄

# 協方差矩陣
cov_matrix=np.matrix(cov_asset("\資料.xlsx"))

##### step2

# 計算每類資產對總資產組合的風險貢獻
def calculate_risk_contribution(weight,one_cov_matrix):
    weight=np.matrix(weight) 
    sigma=np.sqrt(weight*one_cov_matrix*np.matrix(weight).T)
    # 邊際風險貢獻 Marginal Risk Contribution (MRC)
    MRC=one_cov_matrix*weight.T/sigma
    # 風險貢獻 Risk Contribution (RC)
    RC=np.multiply(MRC,weight.T)
    return RC

# 定義優化問題的目標函式,即最小化資產之間的風險貢獻差
def risk_budget_objective(x_weight,parameters): 
    # x_weight是帶求解的各個大類資產下面子標的的權重,parameters是下面函式中的args=[one_cov_matrix,RC_set_ratio]引數傳遞
    # x_weight的初始值是函式 calculate_portfolio_weight中的 weight0
      
    # 協方差矩陣,也即函式 calculate_portfolio_weight 中傳遞的 args中的第一個引數 one_cov_matrix
    one_cov_matrix=parameters[0]
    # 風險平價下的目標風險貢獻度向量,也即函式 calculate_portfolio_weight 中傳遞的 args中的第二個引數 RC_set_ratio
    RC_target_ratio=parameters[1] 
    # RC_target為風險平價下的目標風險貢獻,一旦引數傳遞以後,RC_target就是一個常數,不隨迭代而改變
    sigma_portfolio=np.sqrt(x_weight*one_cov_matrix*np.matrix(x_weight).T)
    RC_target=np.asmatrix(np.multiply(sigma_portfolio,RC_target_ratio))
    # RC_real是 每次迭代以後最新的真實風險貢獻,隨迭代而改變
    RC_real=calculate_risk_contribution(x_weight,one_cov_matrix)
    sum_squared_error= sum(np.square(RC_real-RC_target.T))[0,0] 
    return sum_squared_error

# 優化問題的第一個約束條件
def constraint1(x_weight):
    return np.sum(x_weight)-1.0

# 優化問題的第二個約束條件
def constraint2(x_weight):
    return x_weight

# 根據資產預期目標風險貢獻度來計算各資產的權重
def calculate_portfolio_weight(RC_set_ratio,one_cov_matrix):
    weight0=[0.2, 0.2, 0.2, 0.1, 0.1, 0.2] 
    cons=({'type': 'eq', 'fun': constraint1},{'type': 'ineq', 'fun': constraint2})
    res= minimize(risk_budget_objective, weight0, args=[one_cov_matrix,RC_set_ratio], method='SLSQP',constraints=cons, options={'disp': True})
    weight_final= np.asmatrix(res.x)
    return weight_final
    

def calculate_monthly_weight(RC_set_ratio):
    result={}
    year=['2012','2013','2014','2015','2016','2017','2018']
    month=['01','02','03','04','05','06','07','08','09','10','11','12']
    for i in range(63):
        date=str(2012+int((i+11)/12))+'-'+ month[(i-1)%12]
        one_cov_matrix=cov_matrix[i*6:(i*6+6)]
        result[date]=pd.DataFrame(calculate_portfolio_weight(RC_set_ratio,one_cov_matrix)).iloc[0,0::]
        #xx=pd.DataFrame(result,index=date)
    return result

## 計算風險平價下每月的資產配置權重 
  # 假設1:四個資產的風險貢獻度相等
  # 假設2:同類資產下每個標的的風險貢獻相同
df_monthly_weight=pd.DataFrame(calculate_monthly_weight([0.25/3, 0.25/3, 0.25, 0.25, 0.25/3, 0.25])).T
df_monthly_weight.columns=['IF00.CFE','IC00.CFE','TF00.CFE','AU00.SHF','159920.OF','511880.SH']
df_monthly_weight

#### step3:回測Backtest
## 基於過去一年的風險分配權重,計算各資產的月度收益率
def calculate_RC_monthly_return(file):
    std_df=standardlize_process(file)   
    date_list=[]
    for i in range(63):
        date_list.append(str(2012+int((i+11)/12))+'-'+ month[(i-1)%12])
    #date_list
    _std_df=std_df.iloc[11::,0::].T
    _std_df.columns=date_list
    __std__df=_std_df.T
    __std__df['key']=__std__df.index
    df_monthly_weight['key']=df_monthly_weight.index
    # 合併表,將月收益資料和權重表橫向合併
    monthly_return=pd.merge(__std__df, df_monthly_weight,on='key')
    monthly_return.set_index('key',inplace=True)

    column_list=['return_IF00.CFE','return_IC00.CFE','return_TF00.CFE','return_AU00.SHF','return_159920.OF','return_511880.SH','IF00.CFE','IC00.CFE','TF00.CFE','AU00.SHF','159920.OF','511880.SH']
    # 計算加權後的各資產收益率:權重乘以資產收益率
    for i in range(6):
        monthly_return['weight'+'_'+column_list[i]]=monthly_return[column_list[i]]*monthly_return[column_list[i+6]]
    monthly__return=monthly_return.iloc[0::,-6::]
    monthly__return['all_portfolio_return']=monthly__return.apply(lambda x: x.sum(), axis=1)
    # 用債券收益率作為benchmark
    monthly__return['benchmark']=monthly_return['return_TF00.CFE']
    return monthly__return

## 收益回測,模型評價
def backtest_model1(monthly_data):
    # 設定評價指標
    total_return={}
    annual_return={}
    excess_return={}
    annual_volatility={}
    sharpe={}
    information_ratio={}
    win_prob={}
    drawdown={}
    tr={}

    # 單獨計算benchmark的相關指標
    portfolio_list=['weight_return_IF00.CFE','weight_return_IC00.CFE','weight_return_TF00.CFE','weight_return_AU00.SHF','weight_return_159920.OF','weight_return_511880.SH','all_portfolio_return']
    bench_total_return=(monthly_data['benchmark']+1).T.cumprod()[-1]-1 
    bench_annual_return=(float(bench_total_return)+1.0)**(1./(5+1/6))-1 
    
    # 每一種指標的具體構建方法
    for i in portfolio_list: 
        monthly=monthly_data[i] 
        total_return[i]=(monthly+1).T.cumprod()[-1]-1 
        annual_return[i]=(float(total_return[i])+1.0)**(1./(5+1/6))-1 
        annual_volatility[i]=monthly.std() 
        sharpe[i]=(annual_return[i]-bench_annual_return)/annual_volatility[i] 
        drawdown[i]=monthly.min() 
        win_excess=monthly-monthly_data['benchmark'] 
        win_prob[i]=win_excess[win_excess>0].count()/float(len(win_excess))
    
    # 將字典轉換為dataframe
    ar=pd.DataFrame(annual_return,index=monthly.index).drop_duplicates().T
    tr=pd.DataFrame(total_return,index=monthly.index).drop_duplicates().T
    av=pd.DataFrame(annual_volatility,index=monthly.index).drop_duplicates().T
    sp=pd.DataFrame(sharpe,index=monthly.index).drop_duplicates().T
    dd=pd.DataFrame(drawdown,index=monthly.index).drop_duplicates().T
    wp=pd.DataFrame(win_prob,index=monthly.index).drop_duplicates().T
    ar['key']=ar.index  #年化收益
    tr['key']=tr.index  #累積收益
    av['key']=av.index  #年化波動
    sp['key']=sp.index  #夏普比率
    dd['key']=dd.index  #最大回撤
    wp['key']=wp.index  #勝率
    backtest_df=pd.merge(ar,pd.merge(tr,pd.merge(av,pd.merge(sp,pd.merge(dd,wp,on='key'),on='key'),on='key'),on='key'),on='key')
    backtest_df.set_index('key',inplace=True)
    backtest_df.columns=['annual return','total return','annual volatility','sharpe ratio','drawdown','win prob']
    return backtest_df

hh=backtest_model1(calculate_RC_monthly_return("\資料.xlsx"))
hh