【手把手系列】參數最佳化

一個策略有許多的參數可以調整,改變參數可以讓報酬率跟著改變,透過不斷的嘗試去計算出最大化利益的參數值就是所謂的參數最佳化,舉例來說均線的參數有長短兩個周期也就是兩個參數,布林通道則是均線週期、上下標準差倍數總共三個參數可以最佳化,本篇選用布林通道作為範例。

 

最佳化方法

參數最佳化的方法有很多種,下面簡單介紹四種:

1. 手動調整法需要大量的經驗作為輔助

2. 網格式搜索法又稱窮舉法是對於一段範圍的參數進行不斷的嘗試,跑完所有參數組合後列出所有的回測結果

3. 隨機搜索法通常比窮舉法快很多,搜尋次數相對少很多但同時也犧牲一點精確度

4. 遺傳演算法相當於生態的優勝劣汰,此方法不受限於人們所制定的範圍內,適用於探索最優解答並且已經有一個相對最優的解答

 

布林通道策略

布林通道是由均線與兩個標準差倍數計算出來的,上和下標準差數各算一個參數加上均線週期一共三個參數,三個參數都會影響買賣的時機點,範例中的買賣時機點為碰到下軌時買進、碰到上軌時賣出,只有第一筆交易買入或賣出一口,其餘情況都反向買入或賣出兩口。

本篇運用窮舉法調整均線週期來找出最大投資報酬率,週期範圍從5到29,兩個標準差數都維持2不進行調整,總共25組參數組合一一計算回測績效,該方法雖然比較耗時一點,但是對於小範圍的參數值可以有效找出最大化的組合。

回測的歷史數據範圍是台指期2022/6/25到2022/7/25的一分K資料,最終在均線週期為5時會有最大的投資報酬率,再匯出成csv檔案,在試算表中可以按照週期、勝率、賺賠比或投資報酬率排序。

 

最佳化步驟

這篇的Sample是VolTraderSample中的course5裡面的bbandstrategy_optimization.py檔案,下面開始講解步驟:

1. 首先我們匯入套件,再訂閱歷史資料,訂閱歷史資料的部分可以參考【手把手系列】布林通道策略 + 回測教學

# 載入 tcoreapi
from tcoreapi_mq import *
# 載入我們提供的行情 function
from quote_functions import *
import threading
import numpy as np
import talib
import pandas as pd
from datetime import timedelta
import time

 

2. 建立四個空列表,分別儲存每組參數算出來的投資報酬率、勝率、賺賠比與參數值,另外設定初始資金十萬元

# 投資報酬率
ROI_list = []
# 勝率
winrate = []
# 賺賠比
winloss_list = []
# 參數值
params = []
# 初始資金十萬元
money = 100000

【免費體驗】立即申請 VolTrader Python API 免費試用!

學生專屬,【限量 20 名】免費體驗價值 18,000/年的專業行情與歷史數據服務!

立即申请

3. 定義函式Max_ROI,用來計算每組參數代入後的投資報酬率

將沒有加濾網與止損的布林通道策略放進Max_ROI中,參數值mean、up_sigma、down_sigma分別代表均線週期、上標準差數量和下標準差數量,每次呼叫該函式相當於完成一次回測並顯示投資報酬率、勝率及賺賠比

def Max_ROI(mean, up_sigma, down_sigma):
    ...
    # 計算布林通道上中下三條線
    upperband, middleband, lowerband = talib.BBANDS(history_data["Close"], 
                timeperiod=mean, nbdevup=up_sigma, nbdevdn=down_sigma, matype=0)
    # 均線種類:matype 0:SMA 1:EMA 2:WMA ...

 

4. 交易策略

計算出收盤價跟上下軌的差距後,如果收盤價從原本高於下軌轉為低於下軌,收盤價減去下軌的數值也會由正轉負,在沒有作多的情況下會有買入訊號點,下分鐘開盤時買入。

當收盤價由原本低於上軌變成高於上軌,收盤價減去上軌的數值由負轉正,且沒有做空的情況下產生賣出訊號,下分鐘開盤時進行賣出。

需要注意的是上分鐘的索引是i-1,這分鐘的索引則是i

    # 計算當前收盤價格減去上軌跟下軌的值
    close_upper = history_data["Close"] - upperband
    close_lower = history_data["Close"] - lowerband
    for i in range(1, len(history_data["Close"]) - 1):
        # 如果上分鐘收盤價高於下軌且這分鐘收盤價又低於下軌,
        # 且在沒有做多的情況下,在下分鐘開盤時買進
        if close_lower[i] < 0 < close_lower[i - 1] and pos <= 0:
            ...
        # 如果上分鐘收盤價低於上軌且這分鐘收盤價又高於上軌,
        # 且在沒有做空的情況下,在下分鐘開盤時賣出
        elif close_upper[i] > 0 > close_upper[i - 1] and pos >= 0:
            ...

 

5. 儲存投資報酬率、勝率、賺賠比

ROI_value是每次交易的投資報酬率,每次買入或賣出都會記錄當前的投資報酬率。

如果回測完在歷史資料範圍內沒有交易的話勝率以及賺賠比會因為空值而無法計算,因此這裡判斷ROI_value是否為空值,如果不是空值代表有交易紀錄才計算並顯示投資報酬率、勝率與賺賠比。

另外將每組參數代入Max_ROI函式後計算出來的投資報酬率、勝率、賺賠比儲存進列表中,紀錄時使用np.round記錄到小數點後第四位,沒有交易紀錄則將0儲存進列表中,到此完成Max_ROI函式的定義

    if ROI_value:
        print("最終投資報酬率:", np.round(sum(ROI_value), 4))
        print("勝率:", np.round(wincounts / len(sellprice + sellshortprice), 4))
        print("賺賠比:", np.round(wintotal * losscounts / (wincounts * losstotal), 4))
        ROI_list.append(np.round(sum(ROI_value), 4))
        winrate.append(np.round(wincounts / len(sellprice + sellshortprice), 4))
        winloss_list.append(np.round(wintotal * losscounts / (wincounts * losstotal), 4))
    else:
        ROI_list.append(0)
        winrate.append(0)
        winloss_list.append(0)

 

6. 計算所有參數組合的投資報酬率

使用for迴圈分別改變三個參數值,範圍可以自定義,範例中只嘗試更改均線週期,兩個標準差數都維持2,均線週期範圍從5到29總共25組,需要注意range的範圍不包括最後一個數值,如果想調整上下標準差數只要將range的範圍進行改動就可以了。

使用print顯示當前的參數值,在print中輸入\n可以空一行方便區別,在print中輸入%d可以指定變數為整數,在字串後面加上括號變數就可以替換成%d的位置。

接著代入Max_ROI,每呼叫一次Max_ROI函式就會自動儲存回測完的投資報酬率、勝率、賺賠比與總資產,並且顯示出來。

將當前的參數值儲存進params列表中,紀錄每一組投資報酬率對應的參數組合

for mean in range(5, 30):
    for up_sigma in range(2, 3):
        for down_sigma in range(2, 3):
            print("\n參數為均線週期:%d,上標準差:%d,下標準差:%d" % (mean, up_sigma, down_sigma))
            Max_ROI(mean, up_sigma, down_sigma)
            params.append((mean, up_sigma, down_sigma))

 

7. 找出最大化投資報酬率的參數值

在計算完所有的參數組合後,使用max找出最大的投資報酬率,再用python內建的index函式找到列表中該值的索引位置,將索引位置儲存起來。

呼叫投資報酬率的對應參數組合,輸出時按照均線週期、上標準差、下標準差的順序分別儲存。

最優的參數組合為周期5投資報酬率高達1.6345,也就是初始資金10萬元賺了163,450元最終持有263,450元

# 紀錄最大化投資報酬率的參數值
index = ROI_list.index(max(ROI_list))
max_mean, max_up_sigma, max_down_sigma = params[index]
print('\n' * 2)
print("最優化參數均線回測結果為:")
print("最優參數為均線週期:%d, 上標準差:%d, 下標準差:%d" % (max_mean, max_up_sigma, max_down_sigma))
print("最終持有資金:", money*(1+ROI_list[index]))
print("最終投資報酬率:",ROI_list[index])
print("勝率:",winrate[index])
print("賺賠比:",winloss_list[index])

 

8. 匯出回測結果

把各個參數的數值、計算出來的勝率、賺賠比、投資報酬率一起放入opt列表中。

使用pd.DataFrame轉換為表格,指定欄位名稱後轉置表格,不然預設的數值會是從左到右而不是由上往下顯示出來。

將均線週期、上下標準差數的格式轉換成int,預設的格式為float會有小數點。

然後用to_csv轉換為指定名稱的csv檔案,csv檔案中第一列是參數編號,Pyhton中預設的索引值從0開始,在試算表中可以自行排序均線週期、勝率、賺賠比或投資報酬率。

opt = [[mean[0] for mean in params],
       [up_sigma[1] for up_sigma in params],
       [down_sigma[2] for down_sigma in params],
       winrate, winloss_list, ROI_list]
df = pd.DataFrame(opt, index=["均線週期", "上標準差數", "下標準差數", "勝率", "賺賠比", "投資報酬率"]).T
df["均線週期"] = df["均線週期"].astype(int)
df["上標準差數"] = df["上標準差數"].astype(int)
df["下標準差數"] = df["下標準差數"].astype(int)
df.to_csv("優化結果.csv", index_label="欄位索引")

 

最佳化結果

可以看到參數編號為1的組合均線週期為5、上下標準差數各為2的時候投資報酬率高達1.6345,但要注意的是參數最佳化可能會造成過擬合的情況,也就是說這組參數對歷史數據的判斷十分準確,但是對於未來沒有發生過的情況可能不準確,因此還需要再進行其他數據的驗證

 

》延伸阅读 - 【手把手系列】布林通道策略 + 回測教學

》延伸阅读 -【手把手系列】達錢 Python 【行情&歷史數據】串接設定教學