iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
0
AI & Data

量化交易30天系列 第 7

量化交易30天 Day7 - RSI交易參數最佳化

量化交易30天
本系列文章是紀錄一位量化交易新手的學習過程,除了基礎的Python語法不說明,其他金融相關的東西都會一步步地說明,希望讓更多想學習量化交易但是沒有學過相關金融知識的朋友們,透過這系列的文章,能夠對量化交易略知一二,也歡迎量化交易的高手們多多交流。

量化交易30天 Day7 - RSI交易參數最佳化

上一篇是指定用14天計算RSI,並且用RSI14大於80賣出,小於30買進,最後在2019年得到15.72%的報酬率,不過這些參數其實都是可以調整的,這篇就來試試看調整這些參數,看看能不能得到比較好的報酬率。

由於要做最佳化參數的話,需要把可以調整的參數都放到函數裡面,才能用迴圈去暴力求最佳解,因此下面就先把需要的函數列出來。

  1. RSI函數,這個在之前已經寫好了
# RSI函數
def RSI(Close, period=12):
    # 整理資料
    import pandas as pd
    Chg = Close - Close.shift(1)
    Chg_pos = pd.Series(index=Chg.index, data=Chg[Chg>0])
    Chg_pos = Chg_pos.fillna(0)
    Chg_neg = pd.Series(index=Chg.index, data=-Chg[Chg<0])
    Chg_neg = Chg_neg.fillna(0)
    # 計算12日平均漲跌幅度
    import numpy as np
    up_mean = []
    down_mean = []
    for i in range(period+1, len(Chg_pos)+1):
        up_mean.append(np.mean(Chg_pos.values[i-period:i]))
        down_mean.append(np.mean(Chg_neg.values[i-period:i]))
    # 計算 RSI
    rsi = []
    for i in range(len(up_mean)):
        rsi.append( 100 * up_mean[i] / ( up_mean[i] + down_mean[i] ) )
    rsi_series = pd.Series(index = Close.index[period:], data = rsi)
    return rsi_series
  1. RSI訊號函數:參數包含RSI資料,以及指定的買賣點(例如上上一篇的80/30)。
# RSI策略函數
def RSI_Trading_Sig(RSI, upper = 80, lower = 20):
    import pandas as pd
    # 訊號標籤
    sig = []
    # 庫存標籤,只會是0或1,表示每次交易都是買進或賣出所有部位
    stock = 0
    # 偵測RSI訊號
    for i in range(len(RSI)):
        if RSI[i] > upper and stock == 1:
            stock -= 1
            sig.append(-1)
        elif RSI[i] < lower and stock == 0:
            stock += 1
            sig.append(1)
        else:
            sig.append(0)
    # 將格式轉成 time series
    rsi_sig = pd.Series(index = RSI.index, data = sig)
    return rsi_sig
  1. 回測函數:參數包含買賣訊號、開盤價資料。
def RSI_backtest(RSI_Trading_Sig, Open_Price):
    # 每次買賣的報酬率
    rets = []
    # 是否仍有庫存
    stock = 0
    # 當次交易買入價格
    buy_price = 0
    # 當次交易賣出價格
    sell_price = 0
    # 每次買賣的報酬率
    for i in range(len(RSI_Trading_Sig)-1):
        if RSI_Trading_Sig[i] == 1:
            # 隔日開盤買入
            buy_price = Open_Price[RSI_Trading_Sig.index[i+1]]
            stock += 1
        elif RSI_Trading_Sig[i] == -1:
            # 隔日開盤賣出
            sell_price = Open_Price[RSI_Trading_Sig.index[i+1]]
            stock -= 1
            rets.append((sell_price-buy_price)/buy_price)
            buy_price = 0
            sell_price = 0
    # 如果最後手上有庫存,就用回測區間最後一天的開盤價賣掉
    if stock == 1 and buy_price != 0 and sell_price == 0:
        sell_price = Open_Price[-1]
        rets.append((sell_price-buy_price)/buy_price)
    # 總報酬率
    total_ret = 1
    for ret in rets:
        total_ret *= 1 + ret
    return total_ret

有了上面這三個函數之後,就可以使用暴力法來求最佳解:

# 串接API取資料
import os
import pandas_datareader as pdr
SPY = pdr.get_data_tiingo('SPY', api_key='6af47abd76fbc371d5606fca0694502b866c7bcf')
SPY = SPY.reset_index(level=[0,1])
SPY.index = SPY['date']
SPY_adj = SPY.iloc[:,7:12]
SPY_adj.columns = ['Close','High','Low','Open','Volume']

# 篩選2019年開收盤價資料
Close2019 = SPY_adj.Close['2019']
Open2019 = SPY_adj.Open['2019']

# 參數最佳化 No1,所有參數皆可調整
max_total_ret, max_period, max_upper, max_lower = 0, 0, 0, 0
for period in range(6,25):
    for upper in range(70,91):
        for lower in range(10,31):
            ret = RSI_backtest(RSI_Trading_Sig(RSI(Close2019, period), upper, lower), Open2019)
            if ret > max_total_ret:
                max_total_ret, max_period, max_upper, max_lower = ret, period, upper, lower

# 將求出來的結果印出參數及圖看看
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib import gridspec

x = RSI(Close2019, max_period).index
y = RSI(Close2019, max_period).values

fig = plt.figure(figsize=(15,10))
# set height ratios for sublots
gs = gridspec.GridSpec(2, 1, height_ratios=[2, 1]) 

# the fisrt subplot
ax0 = plt.subplot(gs[0])
# line0 = ax0.plot(x, y, color='r')
ax0.plot(RSI(Close2019, max_period))
ax0.axhline(y=max_upper, color='red')
ax0.axhline(y=max_lower, color='green')

#the second subplot
# shared axis X
ax1 = plt.subplot(gs[1], sharex = ax0)
rsi_sig = pd.Series(index = RSI(Close2019, max_period).index, data = list(RSI_Trading_Sig(RSI(Close2019, max_period), max_upper, max_lower).values))
ax1.plot(rsi_sig)

print('總報酬率:' + str(round(100*(max_total_ret-1),2)) + '%')
print('參數:' + 'RSI計算天數: ' + str(period) + ' ,Upper bond: ' + str(max_upper) + ' ,Lower bond: ' + str(max_lower))
plt.show()

總報酬率:24.62%
參數:RSI計算天數: 24 ,Upper bond: 90 ,Lower bond: 24

本篇總結
從這個圖可以看得出來,最佳化的過程中,會盡量讓Upper bond與Lower bond拉到最開,然後交易次數也需要兼顧到。對於這個最佳化的過程,有幾個觀念是可以注意的:

  1. 以2019年的數據得到的這個參數,在其他年度不會是報酬最好的,在未來也不確定能夠持續有最佳的表現,不過它帶來的是一個概念,讓我們知道RSI可以怎麼用。
  2. 這次是只回測2019年的資料,或許同一個標的而言,多回測幾個年份,有可能抓出它大概適合用什麼樣的參數來交易。
  3. 這篇將三個參數同時做最佳化,但其實也可以一次只最佳化一個參數,例如:直接固定Upper bond與Lower bond,指定一組比較可能出現的數字(例如:70/40),去取代這次回測的90/24這種比較極限的數字,然後只調整RSI天數,去實驗看看哪個天數比較有效果。

筆者 Sean
奈米戶投資人 / Python愛用者
喜歡用Python玩轉金融數據,從個股基本面、技術面、籌碼面相關資料,一直到總體經濟數據,都是平常接觸到的素材;對於投資,除了研究歷史數據,也喜歡瞭解市場上大家在玩些什麼。


上一篇
量化交易30天 Day6 - 用RSI指標來做交易
下一篇
量化交易30天 Day8 - 好用的技術指標函數庫TA-Lib
系列文
量化交易30天30

尚未有邦友留言

立即登入留言