量化交易30天
本系列文章是紀錄一位量化交易新手的學習過程,除了基礎的Python語法不說明,其他金融相關的東西都會一步步地說明,希望讓更多想學習量化交易但是沒有學過相關金融知識的朋友們,透過這系列的文章,能夠對量化交易略知一二,也歡迎量化交易的高手們多多交流。
還記得Day3的最後,成功的用Python抓到Evening Star出現的日期嗎?複習一下K線圖如下:
夜星的K線型態,通常代表股價趨勢轉弱,預期未來股價下跌的機率比較高,所以現在就要來回測看看,到底這個結論是不是真的。
import os
import pandas_datareader as pdr
SPY = pdr.get_data_tiingo('SPY', api_key='YOUR API KEY')
SPY = SPY.reset_index(level=[0,1])
SPY.index = SPY['date']
SPY_adj = SPY.iloc[:,7:11]
SPY_adj.columns = ['Close','High','Low','Open']
關於計算報酬率的方法,舉個範例會比較好理解,假設2016/09/23這天出現訊號(代表這天是Evening star的第3根K棒),我們只能在下1個交易日(2016/09/26)的開盤開始交易,假設9/26買進、9/27賣出的話(隔1天賣),報酬率就會是"9/27的開盤價÷9/26的開盤價",也就是下面程式碼中的ret1;如果9/26買進,過5日後賣,就是下面的ret5。
# 使用開盤價計算報酬率
SPY_Open_adj = SPY_adj.Open
SPY_Open_adj
---------------
date
2015-09-03 00:00:00+00:00 177.578005
2015-09-04 00:00:00+00:00 174.492603
2015-09-08 00:00:00+00:00 177.288466
2015-09-09 00:00:00+00:00 180.346724
2015-09-10 00:00:00+00:00 176.039828
...
2020-08-25 00:00:00+00:00 343.530000
2020-08-26 00:00:00+00:00 344.760000
2020-08-27 00:00:00+00:00 348.510000
2020-08-28 00:00:00+00:00 349.440000
2020-08-31 00:00:00+00:00 350.350000
Name: Open, Length: 1257, dtype: float64
---------------
# 看到訊號後隔天交易,買完隔1日賣/買完隔5日賣 的報酬率
ret1 = SPY_Open_adj.shift(-2) / SPY_Open_adj.shift(-1)
ret1
-------
date
2015-09-03 00:00:00+00:00 1.016023
2015-09-04 00:00:00+00:00 1.017250
2015-09-08 00:00:00+00:00 0.976119
2015-09-09 00:00:00+00:00 1.004215
2015-09-10 00:00:00+00:00 1.008036
...
2020-08-25 00:00:00+00:00 1.010877
2020-08-26 00:00:00+00:00 1.002669
2020-08-27 00:00:00+00:00 1.002604
2020-08-28 00:00:00+00:00 NaN
2020-08-31 00:00:00+00:00 NaN
Name: Open, Length: 1257, dtype: float64
-------
ret5 = SPY_Open_adj.shift(-6) / SPY_Open_adj.shift(-1)
ret5
-------
date
2015-09-03 00:00:00+00:00 1.021260
2015-09-04 00:00:00+00:00 1.003419
2015-09-08 00:00:00+00:00 0.997491
2015-09-09 00:00:00+00:00 1.028063
2015-09-10 00:00:00+00:00 1.006988
...
2020-08-25 00:00:00+00:00 NaN
2020-08-26 00:00:00+00:00 NaN
2020-08-27 00:00:00+00:00 NaN
2020-08-28 00:00:00+00:00 NaN
2020-08-31 00:00:00+00:00 NaN
Name: Open, Length: 1257, dtype: float64
提醒一下,上面ret1與ret5的數字,因為我是採用收盤價相除的方式計算,所以數字大於1代表正報酬,小於1則相反,NaN則是因為尾端資料不足無法計算。
有了報酬率的Series之後,我們只要抓出訊號出現的日期,就可以對照訊號出現後的報酬率是多少了。下面我將Day3抓取K線型態的部分,寫成一個函數:
def Evening_Star_Sig(data):
# 開盤價/收盤價
data_Open = data.Open
data_Close = data.Close
# 當日漲跌
data_DailyChg = data_Close - data_Open
# 取得每日的振幅
data_Abs_DailyChg = abs(data_DailyChg)
# 計算統計數據
mean = data_Abs_DailyChg.mean()
first_quar = data_Abs_DailyChg.quantile(q=0.25)
# 抓取 第1根大振幅陽線、第2根小振幅陽線或陰線、第3根陰線且振幅大於第1根的1/2
evening_condition_1 = [0,0]
for i in range(2, len(data_DailyChg)):
if ( data_DailyChg[i-2] > mean ) & ( abs(data_DailyChg[i-1]) < first_quar ) & ( data_DailyChg[i] < -0.5*mean ):
evening_condition_1.append(1)
else:
evening_condition_1.append(0)
# 第2根的開盤與收盤價 均大於 第1根的收盤與第3根的開盤
evening_condition_2 = [0,0]
for i in range(2, len(data_Open)):
if ( data_Open[i-1] > data_Close[i-2] ) & ( data_Open[i-1] > data_Open[i] ) & ( data_Close[i-1] > data_Close[i-2] ) & ( data_Close[i-1] > data_Open[i] ):
evening_condition_2.append(1)
else:
evening_condition_2.append(0)
# Evening Star Signal
evening_star_signal = []
for i in range(len(evening_condition_1)):
if ( evening_condition_1[i] == 1 ) & ( evening_condition_2[i] == 1 ):
evening_star_signal.append(1)
else:
evening_star_signal.append(0)
# Return a boolean series
import pandas as pd
sig = pd.Series(index = data.index, data = evening_star_signal)
sig = sig.astype('bool')
return sig
這個函數只需要帶入OHLC時間序列的資料,就可以抓出訊號出現的日期,把SPY的資料帶入看看吧:
# 抓取 Evening star訊號
sig = Evening_Star_Sig(SPY_adj)
sig
------
date
2015-09-03 00:00:00+00:00 False
2015-09-04 00:00:00+00:00 False
2015-09-08 00:00:00+00:00 False
2015-09-09 00:00:00+00:00 False
2015-09-10 00:00:00+00:00 False
...
2020-08-25 00:00:00+00:00 False
2020-08-26 00:00:00+00:00 False
2020-08-27 00:00:00+00:00 False
2020-08-28 00:00:00+00:00 False
2020-08-31 00:00:00+00:00 False
Length: 1257, dtype: bool
------
sig是一個time series,如果當天有出現訊號的話,value會是True,否則為False。
如果我們想在訊號出現後,做隔日沖的交易,那麼就可以使用ret1:
從下面資料可以看出,在2015/9/3~2020/8/31中間,出現5次訊號,隔日沖的報酬率則是沒有明顯特徵。
ret1[sig]
------
date
2016-09-23 00:00:00+00:00 0.995489
2018-04-06 00:00:00+00:00 1.011095
2018-06-21 00:00:00+00:00 0.991947
2018-10-18 00:00:00+00:00 0.999531
2019-08-20 00:00:00+00:00 1.002564
Name: Open, dtype: float64
------
使用ret5來計算,可以觀察到,如果隔5天賣的話,可能會遇到下跌較多的情形。
ret5[sig]
------
date
2016-09-23 00:00:00+00:00 1.003721
2018-04-06 00:00:00+00:00 1.021540
2018-06-21 00:00:00+00:00 0.987158
2018-10-18 00:00:00+00:00 0.959550
2019-08-20 00:00:00+00:00 0.978323
Name: Open, dtype: float64
------
如果我們想要計算訊號出現後買賣的平均報酬率,計算方式非常簡單,只要加個mean即可:
ret1[sig].mean()
# 1.0001251917245995
ret5[sig].mean()
# 0.990058399305416
這個平均報酬率,就可以用來判斷,夜星出現後,報酬率最好/最差的買賣天數是多少。因此我就把平均報酬率對應天數的圖表畫出來看看:
# 回測 Evening star出現後,買賣間隔1~100天的平均報酬率
rets = []
for i in range(2,102):
ret = SPY_Open_adj.shift(-i) / SPY_Open_adj.shift(-1)
rets.append(ret[sig].mean())
# 畫出天數對應報酬率的圖
import pandas as pd
import matplotlib.pyplot as plt
ret_df = pd.DataFrame(index=range(1,101),data=rets)
ret_df.columns = ['return']
ret_df = (ret_df-1) * 100
plt.figure(figsize=(12,8))
plt.plot(ret_df)
plt.hlines(y=0, xmin=0, xmax=100, color='red')
plt.title("平均報酬率 v.s. 買賣間隔時間",fontsize=15)
plt.xlabel("買賣間隔天數(天)", fontsize=15)
plt.ylabel("平均報酬率(%)", fontsize=15)
從這張圖可以發現,在2015/9/3~2020/8/31中間,出現的5次訊號來做回測,只有1天到5天內買賣的平均報酬率是負的(紅線以下),剩下的報酬率都是正的,跟一開始預期的結果剛好是相反的,一個看壞的訊號卻得到不錯的報酬率,很酷吧!
推測主要原因是因為SPY是追蹤標普500指數的一檔商品,在回測的這段期間內,標普500指數的趨勢是上漲的,因此只要買賣間隔時間長一點,基本上都可以得到不錯的報酬率,但是在短期的股價上可能有一些下跌的機會,所以它可能比較適合用來當作短線操作的交易訊號。
本篇總結
本篇介紹了K線型態如何做交易,下一篇會開始介紹RSI相對強弱指標,請繼續收看~~
P.S.
如果大家對於量化交易有興趣的話,我自己有上過以下這門課,課程內容從串接股市資料API、儲存至資料庫、將自己的策略轉化成程式碼、自動下單,並且可以把整個流程自動化,每天早上執行一次,一整天就不用看盤了,覺得是蠻實戰的,可以參考看看。
筆者 Sean
奈米戶投資人 / Python愛用者
喜歡用Python玩轉金融數據,從個股基本面、技術面、籌碼面相關資料,一直到總體經濟數據,都是平常接觸到的素材;對於投資,除了研究歷史數據,也喜歡瞭解市場上大家在玩些什麼。
好喜歡這系列,很淺顯易懂的範例
另外想請教大大,若是將這個訊號拿去回測趨勢向下的歷史數據,像是科技泡沫時期或金融海嘯,是否就會得到相反的結果呢?
Hi~~很高興你喜歡這個系列
其實我也沒有拿這個訊號去回測空頭的時間,可以找個時間來玩玩看,感覺會有不同的結論XD