Python新手教學(7):策略優化

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

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

成果如下:

先回顧上次的策略

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

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

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

1. 下載台股大盤資料

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import io
import requests
import pandas as pd
import datetime
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot')

def crawl_price(stock_id):
now = int(datetime.datetime.now().timestamp())+86400
url = "https://query1.finance.yahoo.com/v7/finance/download/" + stock_id + "?period1=0&period2=" + str(now) + "&interval=1d&events=history&crumb=hP2rOschxO0"

response = requests.post(url)

f = io.StringIO(response.text)
df = pd.read_csv(f, index_col='Date', parse_dates=['Date'] )

return df


twii = crawl_price("^TWII")
twii.head()

2. 編寫台股的sharpe ratio

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

1
2
3
4
5
6
7
mean = twii['Adj Close'].pct_change().rolling(252).mean()
std = twii['Adj Close'].pct_change().rolling(252).std()

sharpe = mean / std

twii.Close.plot()
sharpe.plot(secondary_y=True)

3. 編寫台股sharpe ratio策略

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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['Adj Close'][buy.index]
(adj.pct_change().shift(-1)+1).fillna(1)[hold == 1].cumprod().plot()

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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['Adj 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%,非常爛
所以我們才需要做參數優化

參數枚舉優化

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

1
2
3
4
5
6
7
8
9
10
11
12
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它的參數
我們就可以看到數字不斷增加的感覺,滿開心的!
不過上述程式要跑滿久的,請耐心等待,

最後成果滿不錯的,算是一個懶人投資策略:

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

文章不錯,影音課程更讚:


或我們按個 鼓勵一下吧!