基于最小二乘法与简单蒙特卡洛随机法的投资组合优化

305 阅读3分钟

简介

多目标优化经常被应用到投资组合分析中,每个资产在投资组合中的权重都将根据目标进行优化。

我们目前的目标(受限的最大化问题)是最大化投资组合的夏普比率,其中X变量将是每个资产的权重。

长期目标是建立一个ARIMA模型,使用模型预测控制方法进行回测,该模型可能与多目标优化过程相结合(最大化sortino比率,最小化波动率等)。

我们将首先从一个简单的单目标优化函数开始,并将这个优化结果与随机的蒙特卡罗方法进行比较。

先决条件

  • numpy 👉Python的基本科学库,内置数学函数和简单的数组处理。
  • matplotlib 👉用于绘制图表
  • pandas👉用于操作数据帧,这是一个Python对象,当我们操作大型数据集时非常方便。
  • scipy👉运行一个最小化过程

依赖

import pandas as pd
from datetime import datetime
from pandas_datareader import data as web
import numpy as np
import matplotlib.pyplot as plt
import math
from scipy.optimize import minimize

数据清理

stockRefDate = '2019-12-10'
stockTodayDate = datetime.today().strftime('%Y-%m-%d')
capital = 1e6
assets = ["UBER","ZM","GOOG","RTX","AMZN"]
#Social Media, Tech, Private transport
weights = {
  "UBER": 0.2,
  "ZM": 0.2,
  "GOOG": 0.2,
  "RTX": 0.2,
  "AMZN":0.2
}
dfTicker = pd.DataFrame()
for ticker in assets:
    dfTicker[ticker] = web.DataReader(ticker,'yahoo',stockRefDate,stockTodayDate)['Adj Close']

dfTickerNormReturn = pd.DataFrame()
for ticker in assets:
    dfTickerNormReturn[ticker] = dfTicker[ticker]/dfTicker[ticker][0]

dfTickerAllocation = pd.DataFrame()
for ticker in assets:
    dfTickerAllocation[ticker] = dfTickerNormReturn[ticker]*weights[ticker]

dfTickerPositionValue = pd.DataFrame()
for ticker in assets:
    dfTickerPositionValue[ticker] = dfTickerAllocation[ticker]*capital
dfTickerPositionValue['Total'] = dfTickerPositionValue.sum(axis = 1, skipna = True) 
dfTickerPositionValue
UBERZMGOOGRTXAMZNTotal
Date
2019-12-10200000.000000200000.000000200000.000000200000.000000200000.0000001.000000e+06
2019-12-11203800.650400196747.718005200053.542954202614.016645201093.6011151.004310e+06
2019-12-12205736.831566193557.385702200834.409473205558.222925202428.6883811.008116e+06
2019-12-13204302.620255196809.667697200471.483021205090.464050202498.8334881.009173e+06
2019-12-16215489.421977205513.392559202455.640734205627.015924203449.8422471.032535e+06
.....................
2020-05-04196629.621200444293.024994197343.568650129266.938118266326.6704141.233860e+06
2020-05-05201290.787444448567.470989200959.343032129596.423904266534.8176341.246949e+06
2020-05-06199498.030144463342.100186200392.666485127224.146359270382.5371951.260839e+06
2020-05-07221799.935332488771.887247204149.751417125225.286597272262.7124501.312210e+06
2020-05-08235138.054020481338.066071206501.265722128871.558528273642.6493491.325492e+06

104行×6列

可视化

dfTickerPositionValue.drop('Total',axis=1).plot()
plt.show()

image.png

回报、风险和夏普比率

Daily Returns = (P+- P)/(P)

dfTickerPositionValue['Returns Daily'] = dfTickerPositionValue['Total'].pct_change(periods=1)
portfolioAvgReturn = dfTickerPositionValue['Returns Daily'].mean()
portfolioStdDev = dfTickerPositionValue['Returns Daily'].std()
portfolioCummulativeReturn = ((dfTickerPositionValue['Total'][-1] / dfTickerPositionValue['Total'][0])-1)*100
riskFreeRate = 0
sharpeRatio = (portfolioAvgReturn-riskFreeRate)/portfolioStdDev
# Assuming 252 trading days in a year:
sharpeRatioAnnualized = math.sqrt(252)*sharpeRatio
print("The portfolio's cummulative return over the specified time period is: ", portfolioCummulativeReturn, "%.")
print("The annualized Sharpe Ratio is: ", sharpeRatioAnnualized, ".")
该投资组合在指定时间段内的累计回报率为:32.54915936887641%
年化夏普比率为。 1.8813353678990838

最大化夏普比率

dfTickerLogReturn = np.log(dfTicker/dfTicker.shift(1))

dfTickerLogReturn.cov() 将是一个5*5的矩阵。

设置

numberOfPortfolios = 1000
weightsStore = np.zeros([numberOfPortfolios,len(dfTicker.columns)])
returnsStore = np.zeros(numberOfPortfolios)
volatilityStore = np.zeros(numberOfPortfolios)
sharpeRatioStore = np.zeros(numberOfPortfolios)
for i in range(numberOfPortfolios):
  weights = np.array(np.random.random(len(dfTicker.columns))) 
  weights = weights/np.sum(weights)  
  weightsStore[i,:] = weights
  returnsStore[i] = np.sum(dfTickerLogReturn.mean()*weights*252)
  expectedPortfolioVarianceAnnualized = np.dot(weights.T,np.dot(dfTickerLogReturn.cov()*252,weights))
  volatilityStore[i] = np.sqrt(expectedPortfolioVarianceAnnualized)
  sharpeRatioStore[i] = returnsStore[i]/volatilityStore[i]

蒙特卡洛方法

plt.scatter(volatilityStore,returnsStore,c=sharpeRatioStore,cmap='inferno')
plt.scatter(volatilityStore[sharpeRatioStore.argmax()], returnsStore[sharpeRatioStore.argmax()],c='red',s=50,edgecolors='blue')
plt.colorbar(label='Sharpe Ratio')
plt.xlabel('volatility')
plt.ylabel('returns')
print('The best randomized portfolio (as seen from red dot in graph) is portfolio #',sharpeRatioStore.argmax(),'with weightage of',weightsStore[sharpeRatioStore.argmax(),:],".")
print('The maximum randomized sharpe ratio is', sharpeRatioStore.max())
最佳随机组合(如图中红点所示)是612号组合,其权重为[3.20559130e-04 3.88696608e-01 1.39882760e-01 2.87853769e-02]4.42314695e-01] .
最大的随机夏普比率为2.9615807483115177

优化设置

constraints = ({'type': 'eq', 'fun': constraintsSum})
bounds = ((0,1),(0,1),(0,1),(0,1),(0,1))
initialWeights = [0.2,0.2,0.2,0.2,0.2]
     fun: -3.235724725115919
     jac: array([ 3.56596380e-01,  6.21378422e-05,  8.04379791e-01,  2.33128673e+00,
       -5.43892384e-05])
 message: 'Optimization terminated successfully.'
    nfev: 59
     nit: 8
    njev: 8
  status: 0
 success: True
       x: array([0.00000000e+00, 4.67178546e-01, 0.00000000e+00, 6.06843233e-16,
       5.32821454e-01])

优化方法

def optimizerResults(constraints,bounds,initialWeights):

  def model(weights):
    weights=np.array(weights)
    modelReturns = np.sum(dfTickerLogReturn.mean()*weights*252)
    modelVariance = np.dot(weights.T,np.dot(dfTickerLogReturn.cov()*252,weights))
    modelVolatility = np.sqrt(modelVariance)
    modelSharpeRatio = modelReturns/modelVolatility  
    return modelReturns, modelVolatility, modelSharpeRatio

  def minSharpeRatio(weights):
    minSR = model(weights)[2]*-1
    return minSR

  def constraintsSum(weights):
    return np.sum(weights)-1

  optimalResults = minimize(minSharpeRatio,initialWeights,method='SLSQP', bounds=bounds,constraints=constraints)
  optimalWeights = optimalResults.x
  optimalReturns = model(optimalWeights)[0]
  optimalVolatiliy = model(optimalWeights)[1]
  optimalSharpeRatio = model(optimalWeights)[2]

  return optimalWeights, optimalReturns, optimalVolatiliy, optimalSharpeRatio

顺序最小二乘法优化与简单蒙特卡洛随机化方法的比较

print('The optimal sharpe ratio is',optimizerResults(constraints,bounds,initialWeights)[3],'which is HIGHER as compared to the monte carlo method sharpe ratio of',sharpeRatioStore.max(),'.')
最佳的夏普比率为3.235724725115919,与蒙特卡洛法的夏普比率2.9615807483115177相比,这个比率更高。