Python新手教學(Part 7):策略再進化

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

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

成果如下:

result 2

先回顧上次的策略

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

  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 2 1

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 2 3

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()
5 2 1

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

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

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)
3 3 1

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

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

4 2 1

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

FinLab - 韓承佑

嗨大家好,我是韓承佑,FinLab創辦人,畢業於巴黎薩克雷大學資工博士,目前擔任臺灣量化交易協會 學術顧問、台北商業大學 創新育成中心 創業技術顧問與上市科技公司 量化交易顧問。當初,我喜歡寫程式、無意間因為軟體比賽接觸Fintech,從此開始了財經跟程式的學習之路。我們成立 FinLab 量化投資部落格,用自己研發的軟體,對台灣股市做大量快速的實驗。希望可以在量化投資的路上,當大家的「武器製造商」!