技術筆記-玩一玩 scipy 的最佳化方法

更新於 發佈於 閱讀時間約 17 分鐘

前言

「最佳化」是很酷的觀念,因為現實世界中許多問題,並沒有嚴謹一致的公式解,但可以利用計算機高速運算能力,透過巧妙的演算法,迭代式反覆逼近最佳解,應用領域非常廣。若能多瞭解一點原理,一定可以提昇解決問題的能力。今天從網路上發現一堂手把手的教學課程,就來演練一下整個過程。期望徹底了解之後,後面可以有些延伸應用。

課程連結在此:https://youtu.be/9GA2WlYFeBU?si=0oEjPXGlrk1VrBDm

感謝專業講師 Ryan O'Connell, CFA, FRM 的分享。


課程演練

問題描述

對於一籃子的資產種類,如何調整各種類所佔的比例 (投資權重),使報酬-風險比 (sharpe ratio) 最大。


資料準備

  • 從 yahoo finance 取得資料,只取一個欄位「調整後收盤價」,存在一個 dataFrame
import yfinance as yf
import pandas as pd
from datetime import datetime, timedelta
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
import seaborn as sns

# 定義一籃子股票 (台積電,聯發科,聯詠,瑞昱,王品,中美晶,台達電)
tickers = ['2330.TW', '2454.TW', '3034.TW', '2379.TW', '2727.TW', '5483.TWO', '2308.TW']

# 設定七年的期間
end_date = datetime.today()
start_date = end_date - timedelta(days = 7*365)

adj_close_df = pd.DataFrame()
for ticker in tickers:
data = yf.download(ticker, start = start_date, end = end_date)
adj_close_df[ticker] = data['Adj Close']
adj_close_df
raw-image


初步分析

從收盤價,計算每日報酬率,記得去掉空資料,以免後續計算錯誤。

log_returns = np.log(adj_close_df / adj_close_df.shift(1))
log_returns = log_returns.dropna()
log_returns
raw-image

有了每日報酬,已經可以得到各資產的歷史表現,以年化指標方式表達。

log_returns.mean()*252
----------------------
2330.TW 0.243656
2454.TW 0.253070
3034.TW 0.291873
2379.TW 0.252808
2727.TW 0.123987
5483.TWO 0.142502
2308.TW 0.182675
dtype: float64

log_returns.std() * np.sqrt(252)
--------------------------------
2330.TW 0.275972
2454.TW 0.378044
3034.TW 0.374934
2379.TW 0.367530
2727.TW 0.354785
5483.TWO 0.420420
2308.TW 0.298477
dtype: float64

接下來計算各標的間的關聯性,以「共變異矩陣」方式表達。

cov_matrix = log_returns.cov()*252
cov_matrix
raw-image


函式準備

# 計算 portfolio 的年化報酬    
def expected_return (weights, log_returns):
return np.sum(log_returns.mean()*weights) * 252

# 計算 portfolio 的標準差,也就是 volatility,或稱波動性
# 重要!!
def standard_deviation (weights, cov_matrix):
variance = weights.T @ cov_matrix @ weights
return np.sqrt(variance)

# 計算 portfolio 的 shapre ratio
def sharpe_ratio (weights, log_returns, cov_matrix, risk_free_rate):
return (expected_return(weights, log_returns) - risk_free_rate) /
standard_deviation(weights, cov_matrix)

# 最佳化的目標函數 (最大化 sharpe,就是最小化 負sharpe)
def neg_sharpe_ratio(weights, log_returns, cov_matrix, risk_free_rate):
return - sharpe_ratio(weights, log_returns, cov_matrix, risk_free_rate)

參數準備

# 執行最佳化時,所需要的重要參數
# 權重加總,必須 = 1
constraints = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}
constraints
-----------
{'type': 'eq', 'fun': <function __main__.<lambda>(weights)>}


# 所有權重,都限制在 00.5 之間,也就是不准單一標的,佔比超過五成
bounds = [(0, 0.5) for _ in range(len(tickers))]
bounds
------
[(0, 0.5), (0, 0.5), (0, 0.5), (0, 0.5), (0, 0.5), (0, 0.5), (0, 0.5)]

risk_free_rate = 0 # 假設忽略無風險利率

# 權重初始值
initial_weights = np.array([1/len(tickers)] * len(tickers))
initial_weights
---------------
array([0.14285714, 0.14285714, 0.14285714, 0.14285714, 0.14285714,
0.14285714, 0.14285714])

執行最佳化

# 最關鍵的一步,scipy 套件提供 minimize(),未提供 maximize() 
optimized_results_sharpe = minimize(neg_sharp_ratio, initial_weights,
args=(log_returns, cov_matrix, risk_free_rate), method='SLSQP',
constraints = constraints, bounds = bounds, options = {'disp': True})
--------------------------------------------------------------------------
Optimization terminated successfully (Exit mode 0)
Current function value: -0.9342487059203365
Iterations: 5
Function evaluations: 40
Gradient evaluations: 5

為了顯示結果,先準備一個函式,讓結果顯示在一張圖表裡:

def show_result(result):
optimal_weights = result.x

optimal_portfolio_return = expected_return(optimal_weights, log_returns)
optimal_portfolio_volatility =
standard_deviation(optimal_weights, cov_matrix)
optimal_sharpe_ratio =
sharpe_ratio(optimal_weights, log_returns, cov_matrix, risk_free_rate)

sns.set_theme(style='whitegrid')
sns.set_color_codes('pastel')
f, ax = plt.subplots(figsize=(7, 5))
title1 = f'Return {optimal_portfolio_return: .4f}'
title2 = f'with Volatility {optimal_portfolio_volatility: .4f}'
title3 = f'and sharpe {optimal_sharpe_ratio: .2f}'
ax.set_title(f'{title1} {title2} {title3}')
sns.barplot(x='weight', y='ticker',
data=pd.DataFrame({'ticker': tickers, 'weight': optimal_weights}),
label='weight', color='b')


raw-image

以上已經完整掌握所有定義,設定和計算流程。


檢討與變形

最小化波動

以上過程最重要的基本邏輯就是最大化 sharpe,就是報酬 / 波動,但用以過的歷史資料的平均報酬,當作面對未來績效的期望值估計量,明顯存在著大量例外。但本來預測未來就是困難的事,目前也沒有更好的作法,就算專業機構也只能將就使用。但個人研究無需包袱纏身,就大膽來做一個變形,就是「忽略報酬率」!只求「最小化波動」。計算 portfolio 標準差的函式已經存在,就把它直接當成目標函數,執行最佳化作業:

optimized_results_volatility = minimize(standard_deviation, initial_weights, 
args=(cov_matrix), method='SLSQP', constraints = constraints,
bounds = bounds, options = {'disp': True})
--------------------------------------------
Optimization terminated successfully (Exit mode 0)
Current function value: 0.2238746037968142
Iterations: 8
Function evaluations: 64
Gradient evaluations: 8

得到結果如下:

raw-image

雖然 sharpe 降至 0.91,但波動從 24% 降至 22%,不錯,這就是我所要的。歷史波動對未來的預測力,我相信是很有參考價值的,比用單純算術平均的報酬去預測未來有用多了。當波動降到很低,我們就可以審慎的逐步擴大槓桿倍數了。槓桿不是洪水猛獸,要提升絕對績效,這是一定要的!


納入空頭部位

為了更近一步降低波動,「做空」就是必要之惡了,因為再怎樣「長期向上」的市場,也免不了無法預測的「中級回調」!屆時如果部位只有 long-only,免不了大幅縮水,痛苦程度不見得扛得住。簡言之還是降低波動的概念。所以需要改一下 constraint and bounds:

# 允許權重為負值,代表做空,而絕對數字依然一樣,代表整體曝險市值的比例。
bounds = [(-0.6, 0.6) for _ in range(len(tickers))]
constraints = {'type': 'eq', 'fun': lambda weights: np.sum(abs(weights)) - 1}

optimized_results_volatility_long_short =
minimize(standard_deviation, initial_weights, args=(cov_matrix),
method='SLSQP', constraints = constraints, bounds = bounds,
options = {'disp': True})

執行結果與上一次相同,似乎此演算法對 initial weight 的影響很大!怎麼跑都還是維持在正直,看來選股難,選空方標的更難。試著加上「大盤指數」看看,重新抓取資料。此時因為未考慮報酬率,所以除權息影響也就甚微了。同時特別指定此標的的 initial weight 為負值,也就是用做空指數來平衡多頭風險:

# 重新指定 ticker 集合,重新執行抓取資料程序
tickers = ['^TWII', '2330.TW', '2454.TW', '3034.TW', '2379.TW', '2727.TW',
'5483.TWO', '2308.TW']

# 指定初值時,特別把第一個元素變號
initial_weights = np.array([1/len(tickers)] * len(tickers))
initial_weights[0] = - initial_weights[0]

重新執行最佳化,得到很棒棒的「超低風險」(0.03) 的投資組合:

raw-image

此結果表示,大約配置 50% more 的指數空頭部位,可充分抵銷投資組合的風險。雖然報酬率也被犧牲了,但「歷史績效永遠不等於未來績效」,而我相信「歷史風險大約等於未來風險」。 雖然全球經濟長期向上的事實還是值得相信的,但中期回檔還是很痛苦的,例子不遠,2022 就經歷過了,所以我偏向經常性的 Long-short 配置,在 portfolio 穩定的前提下,加大槓桿與內部波動共舞。


大致完成一回合的探索過程,透過變化問題定義,限制條件,最佳化函數等等,相信會找到新的應用,趣味也將持續。


Newman 2024/11/6

導覽頁:紐曼的技術筆記-索引







留言
avatar-img
留言分享你的想法!
avatar-img
newman的沙龍
25會員
119內容數
漫步是一種境界。
newman的沙龍的其他內容
2025/04/01
Reinforcement Learning (強化學習) 的理論非常有趣,可能是因為其中許多方法,與人類的學習歷程極為相似,如試錯,獎懲,改進策略,持續優化等等。現在準備來爬這座山了,我把學習階段大致分成三個小山峰,依序為 Q-Learning --> DQN --> Actor-Critic,
Thumbnail
2025/04/01
Reinforcement Learning (強化學習) 的理論非常有趣,可能是因為其中許多方法,與人類的學習歷程極為相似,如試錯,獎懲,改進策略,持續優化等等。現在準備來爬這座山了,我把學習階段大致分成三個小山峰,依序為 Q-Learning --> DQN --> Actor-Critic,
Thumbnail
2025/03/08
稍微看一下 Telegram 官方文件,哇!好強喔,功能說明的第一項赫然出現「可以取代整個網站」!口氣真的很大。雖然我的需求應該很低,但能夠確認前面是一座豐富的寶藏,還是很令人興奮的,待基本功能掌握之後,可以再評估和決定要不要往下挖。 發送訊息 要達成這第一個目標,首先必須建立一個 bot。
Thumbnail
2025/03/08
稍微看一下 Telegram 官方文件,哇!好強喔,功能說明的第一項赫然出現「可以取代整個網站」!口氣真的很大。雖然我的需求應該很低,但能夠確認前面是一座豐富的寶藏,還是很令人興奮的,待基本功能掌握之後,可以再評估和決定要不要往下挖。 發送訊息 要達成這第一個目標,首先必須建立一個 bot。
Thumbnail
2025/03/01
Line Notify 即將停止服務,隨著時間越來越緊迫,隱約聽到許多人在哀嚎。印象中有許多廠商,把 Line Notify 用得淋漓盡致,甚至可以一個客戶建一個群組,把許多客製化服務都用程式管理的井井有條,得到很好的滿意度。但這種好康,無限免費的即時訊息,沒有了,時間就在 2025/3/31!公告
Thumbnail
2025/03/01
Line Notify 即將停止服務,隨著時間越來越緊迫,隱約聽到許多人在哀嚎。印象中有許多廠商,把 Line Notify 用得淋漓盡致,甚至可以一個客戶建一個群組,把許多客製化服務都用程式管理的井井有條,得到很好的滿意度。但這種好康,無限免費的即時訊息,沒有了,時間就在 2025/3/31!公告
Thumbnail
看更多
你可能也想看
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
本期這篇文章。將迎來我們建構股票池工具的收官之作。依照慣例,追劇的每一季結尾,代表著總要對每一季階段性的故事做一個交代。然後也必須要適時的加入一些念想。
Thumbnail
本期這篇文章。將迎來我們建構股票池工具的收官之作。依照慣例,追劇的每一季結尾,代表著總要對每一季階段性的故事做一個交代。然後也必須要適時的加入一些念想。
Thumbnail
首先、本來想在這一邊做一個收官,用來結束我們創建股票池工具的階段工作。如此也可以讓我們展開往後的策略設計,以及回測的工作。
Thumbnail
首先、本來想在這一邊做一個收官,用來結束我們創建股票池工具的階段工作。如此也可以讓我們展開往後的策略設計,以及回測的工作。
Thumbnail
 大家好。很高興在這個新的部落格上面。有機會能再跟大家一起討論交易的兩三事。未來我們將在這個新的地方上面;與大家一步一步地,建立起屬於我們自己個性化的量化交易平臺;敬請期待。
Thumbnail
 大家好。很高興在這個新的部落格上面。有機會能再跟大家一起討論交易的兩三事。未來我們將在這個新的地方上面;與大家一步一步地,建立起屬於我們自己個性化的量化交易平臺;敬請期待。
Thumbnail
上一篇文章 說明了不要讓單一標的大跌對整體部位造成毀滅性傷害,必要且有效的方法就是將資金分散到多個標的,也就是投資組合。因為看錯標的不見得會受傷,受傷的原因是看錯又重壓,而加槓重壓則造成完全毀滅。標的的選擇和比重的設定這是不同的功課,必須逐步拆解,基礎打穩後再加入擇時因子分析以伸縮部位,無法一蹴可及
Thumbnail
上一篇文章 說明了不要讓單一標的大跌對整體部位造成毀滅性傷害,必要且有效的方法就是將資金分散到多個標的,也就是投資組合。因為看錯標的不見得會受傷,受傷的原因是看錯又重壓,而加槓重壓則造成完全毀滅。標的的選擇和比重的設定這是不同的功課,必須逐步拆解,基礎打穩後再加入擇時因子分析以伸縮部位,無法一蹴可及
Thumbnail
題目摘要 給定一個陣列 prices,其中 prices[i] 代表第 i 天的股票價格。你希望透過在某一天購買一股股票,並在未來的某一天賣出它,以最大化你的利潤。如果無法獲得任何利潤,則返回 0。
Thumbnail
題目摘要 給定一個陣列 prices,其中 prices[i] 代表第 i 天的股票價格。你希望透過在某一天購買一股股票,並在未來的某一天賣出它,以最大化你的利潤。如果無法獲得任何利潤,則返回 0。
Thumbnail
一、前言 上一堂提到,如何透過加入「對策略有幫助的因子(濾網)」,讓策略報酬增加,而這堂課則是當你執行一段時間後,知識量又增長了,於是就可以更增加策略的報酬,首先以下就是我改進的成果,提供大家參考,大家可以針對參數在修正,找出最適合你的策略。 二、最近學到的因子 (1)主力做多成本線:最近,我
Thumbnail
一、前言 上一堂提到,如何透過加入「對策略有幫助的因子(濾網)」,讓策略報酬增加,而這堂課則是當你執行一段時間後,知識量又增長了,於是就可以更增加策略的報酬,首先以下就是我改進的成果,提供大家參考,大家可以針對參數在修正,找出最適合你的策略。 二、最近學到的因子 (1)主力做多成本線:最近,我
Thumbnail
如何觀察一家個股的好壞,最直觀的方式就是查詢其財務報表,像是資產負債表或是現金流量表等等,但這些報表中存在著密密麻麻的眾多指標,真的是會讓人看了頭昏眼花,而且也真的是極少數人才會每個指標都關注,大多的人一定都是觀察幾個自認為比較重要的指標而已吧,因為我就是如此,因此今天我們就來學習如何一鍵輕鬆查詢報
Thumbnail
如何觀察一家個股的好壞,最直觀的方式就是查詢其財務報表,像是資產負債表或是現金流量表等等,但這些報表中存在著密密麻麻的眾多指標,真的是會讓人看了頭昏眼花,而且也真的是極少數人才會每個指標都關注,大多的人一定都是觀察幾個自認為比較重要的指標而已吧,因為我就是如此,因此今天我們就來學習如何一鍵輕鬆查詢報
Thumbnail
今天是端午節,先祝各位佳節愉快!疫情嚴峻的情況下,有出門旅遊的朋友們也要遵守防疫措施,保護自己也保護他人。沒出門遊玩的朋友們,趁著難得悠閒的假期,來複習一下課程重點內容吧! 在這幫各位學員們做重點整理,當有疑惑、或是不了解時,隨時回來檢視是哪個環節出了問題! 位置 慣性 價量 Bonus
Thumbnail
今天是端午節,先祝各位佳節愉快!疫情嚴峻的情況下,有出門旅遊的朋友們也要遵守防疫措施,保護自己也保護他人。沒出門遊玩的朋友們,趁著難得悠閒的假期,來複習一下課程重點內容吧! 在這幫各位學員們做重點整理,當有疑惑、或是不了解時,隨時回來檢視是哪個環節出了問題! 位置 慣性 價量 Bonus
Thumbnail
有粉絲問濕王怎麼選股,這問題其實很複雜,也牽涉很多層面,選了很多股,但買的就那幾隻(第一次篩選),試單後失敗也很多(第二次篩選),有時候加碼後又失敗(第三次篩選),最後真正做到的飆股,一年有個2-3支就很了不起。
Thumbnail
有粉絲問濕王怎麼選股,這問題其實很複雜,也牽涉很多層面,選了很多股,但買的就那幾隻(第一次篩選),試單後失敗也很多(第二次篩選),有時候加碼後又失敗(第三次篩選),最後真正做到的飆股,一年有個2-3支就很了不起。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News