效率前緣曲線

Photo Credits: Unsplash

本文重點概要

  • 文章難度:★★★☆☆
  • 效率前緣計算與視覺化
  • 閱讀建議:這篇文章大概介紹理論核心,並不會詳細討論,如果對理論有興趣的讀者可以查詢相關文獻,而在數學計算上會相對複雜,使用較多函數式編寫,以增加計算效能並減少記憶體需求,在視覺化的部分使用 plotly 模組,互動式的圖表讓我們對結果能有更深的體會。

前言

大部分的人在決定自己的投資組合該怎麼分配權重時,常常沒有一個依據,而諾貝爾經濟學得獎者 Harry Markowitz 提出一個理論,依據股票的波動度和彼此的相關性,根據不同的權重設定,找到一條「總風險相同時,相對上可獲得最高之預期報酬率」,如此一來就可以根據自己的風險承受度去選擇投資組合的權重分配了!

編輯環境及模組需求

本文使用 Mac OS 並以 Jupyter Notebook 作為編輯器

# 基礎
import numpy as np
import pandas as pd
# 繪圖
import matplotlib.pyplot as plt
import matplotlib
import plotly.express as px
import plotly.graph_objects as go
# API
import tejapi
tejapi.ApiConfig.api_key = 'Your Key'
tejapi.ApiConfig.ignoretz = True

資料庫使用

資料撈取

Step 1. 撈我們以台積電(2330)、 長榮(2603)、統一超商(2912) 作為投資組合的範例,日期選定 2020 年度,欄位選擇報酬率 roi。

data = tejapi.get('TRAIL/TAPRCD',
                  coid=['2330', '2603', '2912'],
                  mdate={'gte': '2020-01-01', 'lte': '2020-12-
                          31'},
                  opts={"sort": "mdate.desc", 'columns': [
                        'coid', 'mdate', 'roi']},
                  paginate=True)

Step 2. 重設索引值、資料轉置、欄位以股票代號命名

data = data.set_index('mdate')
returns = data.pivot(columns='coid')
returns.columns = [columns[1] for columns in returns.columns]
returns

Step 3. 計算平均報酬、共變異數矩陣

mean_returns = returns.mean()
cov_matrix = returns.cov()
mean and covariance

投資組合計算

接下來我們要隨機產生投資組合權重,利用大量模擬進而找出效率前緣,我們要針對每一個投組紀錄他的報酬率標準差權重,我們定義兩組函數。

def portfolio_performance(weights, mean_returns, cov_matrix):
    returns = np.sum(mean_returns*weights )
    std = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) 
    return std, returns

我們要模擬的投資組合數量

num_portfolios = 5000

將函數在放入隨機投組計算函數裡,計算每一個投資組合的數據

def random_portfolios(num_portfolios, mean_returns, cov_matrix):
    results = np.zeros((3,num_portfolios))
    weights_record = []
    for i in range(num_portfolios):
        weights = np.random.random(len(coid))
        weights /= np.sum(weights)
        weights_record.append(weights)
        portfolio_std_dev, portfolio_return = 
        portfolio_performance(weights, mean_returns, cov_matrix)
        results[0,i] = portfolio_std_dev
        results[1,i] = portfolio_return
        results[2,i] = (portfolio_return) / portfolio_std_dev
    return results, weights_record

開始模擬,並將結果存入 resultsweights_record

results = np.zeros((3,num_portfolios))
weights_record = []
for i in range(num_portfolios):
    weights = np.random.random(len(coid))
    weights /= np.sum(weights)
    weights_record.append(weights)
    portfolio_std_dev, portfolio_return = 
    portfolio_performance(weights, mean_returns, cov_matrix)
    results[0,i] = portfolio_std_dev
    results[1,i] = portfolio_return
    results[2,i] = (portfolio_return) / portfolio_std_dev
Random Portfolio

視覺化,鼠標跳出的文字框,第一個數字代表風險,第二個數字代表報酬,第二行數組代表投資的權重。

def protfolios_allocation(mean_returns, cov_matrix, 
                         num_portfolios):
    results, weights = random_portfolios(
        num_portfolios, mean_returns, cov_matrix)
        
    fig = go.Figure(data=go.Scatter(x=results[0, :], 
                                    y=results[1, :],
                                    mode='markers',
                                    text = weights_record,
                                   ))
    
    fig.update_layout(title='投資組合表現分佈',
                      xaxis_title="投資組合總風險",
                      yaxis_title="預期平均報酬率",)
    fig.update_xaxes(showspikes=True,spikecolor="grey",
                     spikethickness=1, spikedash='solid')
    fig.update_yaxes(showspikes=True,spikecolor="grey",
                     spikethickness=1, spikedash='solid')
    fig.show()
Portfolios Allocation

效率前緣計算

我們要找兩個條件:

  1. 找出所有投資組合中最小風險的投資組合
  2. 找到相同報酬率下最小風險的投資組合

這類的條件相當於尋找極值,我們可以利用 scipy 模組下的 optimize 來計算最小值

import scipy.optimize as sco

這裡定義四個計算的函數

1.風險函數

代入權重計算出報酬和標準差

def portfolio_volatility(weights, mean_returns, cov_matrix):
 return portfolio_performance(weights,mean_returns, cov_matrix)[0]

2. 風險最小投資組合

我們使用 scipy 模組下的 optimize 計算在權重 0 ~ 1 限制下,取風險函數下的極值,演算法選擇 SLSQP( Sequential Least Squares Programming) 非線性規劃

fun:優化的目標函數

args:目標函數可設定的參數

method:選用的優化算法

bounds:每一個x的取值範圍

constraints:優化的約束條件,輸入為字典組成的元組,字典主要由 ‘type‘ 和 ‘fun‘ 組成,type可選 ‘eq‘ 和 ‘ineq‘,分別是等式約束和不等式約束,fun是對應的約束條件,可為lambda函數。

def min_variance(mean_returns, cov_matrix):
    num_assets = len(mean_returns)
    args = (mean_returns, cov_matrix)
    constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    bound = (0,1)
    bounds = tuple(bound for asset in range(num_assets))
    result = sco.minimize(portfolio_volatility, num_assets*
             [1/num_assets,], args=args,
             method='SLSQP', bounds=bounds, 
             constraints=constraints)
    return result

3. 同報酬率下風險最小投組

主要為約束條件上的差異,報酬要限制在固定數字,求風險極小值

def efficient_return(mean_returns, cov_matrix, target):
    num_assets = len(mean_returns)
    args = (mean_returns, cov_matrix)
    def portfolio_return(weights):
        return portfolio_performance(weights, mean_returns, 
                                     cov_matrix)[1]
    constraints = ({'type': 'eq', 'fun': lambda x:  
                     portfolio_return(x) - target},
                   {'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    bounds = tuple((0,1) for asset in range(num_assets))
    result = sco.minimize(portfolio_volatility, num_assets*
                          [1/num_assets,], args=args,
                          method='SLSQP', bounds=bounds, 
                          constraints=constraints)
    return result

4. 組合效率前緣的樣本

def efficient_profolios(mean_returns, cov_matrix, returns_range):
    efficients = []
    for ret in returns_range:
        efficients.append(efficient_return(mean_returns, 
                          cov_matrix, ret))
    return efficients

視覺化,方法同投組視覺化,再另外加上最小風險投組和效率前緣

MVP & Efficient Frontier

結論

效率前緣整體架構其實不難理解,利用大量的模擬計算,求得效率的權重投資,可以設定自己想要的預期報酬,選擇風險最小的投資組合,Plotly 互動式圖表讓我們可將鼠標移至圖點上,就會顯示當下投資組合的權重分配,當然萬年不變的道理:「高報酬高風險」,想要更高的投資報酬率,需要承擔更大的風險波動,如果對演算法有興趣的讀者,可以詳閱官方文件,而此權重的結果,是根據選擇資料庫的時間,當時股票的波動度,所以獲得的效率前緣是會隨著時間不斷變化,故一段時間需要調整權重分配。

完整程式碼

延伸閱讀

相關連結

返回總覽頁
Procesing