效率前緣曲線

本文重點概要

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

編輯環境及模組需求

```# 基礎
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.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]```

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

```mean_returns = returns.mean()
cov_matrix = returns.cov()```

投資組合計算

```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```

```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```

```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()`

效率前緣計算

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

`import scipy.optimize as sco`

1.風險函數

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

2. 風險最小投資組合

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```