跳至主要內容
FinLab

Python新手教學(Part 7):用暴力法做策略參數最佳化,讓績效再進化

上一篇文章中,帶大家寫了一個簡單的策略, 然而,在現實生活中並沒有這麼管用,20年才賺三倍!?

所以這篇文章將帶介紹如何利用修改參數,來調整策略,進而達到更好的績效 但是人工調整參數很浪費時間,所以我們先使用簡單暴力法,來調整參數試試看。

成果如下:

策略參數最佳化後的回測成果

先回顧上次的策略

由於這是系列文章,要完成到上次的步驟其實有點煩瑣,所以這邊就簡單的前情提要一下,總共有三個步驟:

  1. 下載台股大盤資料
  2. 編寫台股的sharpe ratio
  3. 利用sharpe ratio製作回測

這邊就不厭其煩的先把上次的code拿來,方便大家直接複製貼上

1. 下載台股大盤資料

以下這段程式,已經於「全球指數一次抓」講過了, 假如想瞭解的話,可以去爬個文,這邊就不贅述了。

顯示程式碼
import io
import json
import requests
import numpy as np
import pandas as pd
import datetime
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot')
 
def crawl_price(stock_id):
 
    d = datetime.datetime.now()
    url = "https://query1.finance.yahoo.com/v8/finance/chart/"+stock_id+"?period1=0&period2="+str(int(d.timestamp()))+"&interval=1d&events=history&=hP2rOschxO0"
 
    res = requests.get(url)
    data = json.loads(res.text)
    df = pd.DataFrame(data['chart']['result'][0]['indicators']['quote'][0], index=pd.to_datetime(np.array(data['chart']['result'][0]['timestamp'])*1000*1000*1000))
    return df
 
 
 
twii = crawl_price("^TWII")
twii.head()

下載台股大盤加權指數資料的結果

2. 編寫台股的sharpe ratio

接下來我們就來計算sharpe ratio,這邊同樣於「Python新手教學:風險與報酬」講過了 就請有興趣的大家多多複習囉!

顯示程式碼
mean = twii['close'].pct_change().rolling(252).mean()
std = twii['close'].pct_change().rolling(252).std()
 
sharpe = mean / std
 
twii.close.plot()
sharpe.plot(secondary_y=True)

台股大盤收盤價與夏普指數的對照圖

3. 編寫台股sharpe ratio策略

接下來就是編寫sharpe ratio 的策略了,同樣可以到python新手教學:夏普指數策略 這篇文章中,得到更詳細的解釋

顯示程式碼
import numpy as np
 
# sharpe ratio 平滑
sr = sharpe
srsma = sr.rolling(60).mean()
 
# sharpe ratio 的斜率
srsmadiff = srsma.diff()
 
# 計算買入賣出點
buy = (srsmadiff > 0) & (srsmadiff.shift() < 0)
sell = (srsmadiff < 0) & (srsmadiff.shift() > 0)
 
# 計算持有時間
hold = pd.Series(np.nan, index=buy.index)
hold[buy] = 1
hold[sell] = 0
hold.ffill(inplace=True)
hold.plot()
 
# 持有時候的績效
adj = twii['close'][buy.index]
(adj.pct_change().shift(-1)+1).fillna(1)[hold == 1].cumprod().plot()

夏普指數策略的持有訊號與累積績效曲線

別轉台,終於要開始參數最佳化了

我們將上面的程式碼包成一個函示如下

顯示程式碼
def backtest(a, b, c, d):
    sr = sharpe
    srsma = sr.rolling(a).mean()
 
    srsmadiff = srsma.diff() * 100
    ub = srsmadiff.quantile(b)
    lb = srsmadiff.quantile(c)
    
    buy = ((srsmadiff.shift(d) < lb) & (srsmadiff > ub))
    sell = ((srsmadiff.shift(d) > ub) & (srsmadiff < lb))
 
    hold = pd.Series(np.nan, index=buy.index)
    hold[buy] = 1
    hold[sell] = 0
 
    hold.ffill(inplace=True)
    
    adj = twii['close'][buy.index]
 
    # eq = (adj.pct_change().shift(-1)+1).fillna(1)[hold == 1].cumprod().plot()
    # hold.plot()
    eq = (adj.pct_change().shift(-1)+1).fillna(1)[hold == 1].cumprod()
    if len(eq) > 0:
        return eq.iloc[-1]
    else:
        return 1
 
 
backtest(252,0.4,0.6,4)

可以發現,這個function傳入了四個參數「a,b,c,d」, 而這四個參數是做什麼的呢?是拿來取代原本的數字的, 可以發現原本的常數部分,都被換成了代數,這樣我們到時候在呼叫時,就可以帶入不同的參數 而我們最後的回傳值,原本是一張圖片,但此function中被改成了這20年的報酬率 所以當我們執行「backtest(252,0.4,0.6,4)」的時候, 這20年的報酬就是9.1%,非常爛 所以我們才需要做參數優化

參數枚舉優化

我們使用暴力法,將所有的可能的參數都找一遍:

顯示程式碼
maxeq = 0
 
for a in range(100,200,20):
    for b in np.arange(0.3, 0.9, 0.03):
        for c in np.arange(0.3, 0.6, 0.03):
            for d in range(60, 180, 10):
                
                eq = backtest(a,b,c,d)
                
                if maxeq < eq:
                    maxeq = eq
                    print(eq, a,b,c,d)

暴力法枚舉參數最佳化的執行結果

上面第8行,即是我們執行backtest的結果, 假如我們發現eq,有最高報酬率, 則將新的最高報酬率print出來,並且print它的參數 我們就可以看到數字不斷增加的感覺,滿開心的! 不過上述程式要跑滿久的,請耐心等待,

最後成果滿不錯的,算是一個懶人投資策略,請參考程式碼來獲得最近的報酬率

參數最佳化後策略的累積報酬績效曲線

有興趣的可以到粉絲團按讚,才不會錯過接下來精彩文章喔!

想建立自己的策略?

用自然語言描述你的選股想法,AI 自動驗證、回測、給你答案

免費開始