程式交易大冒險(一)-免費的python交易策略回測框架-以0050的KD指標為例

用免費的python回測框架驗證自己的策略吧!

長期追蹤勳仔的朋友們,應該知道勳仔今年初許下的目標,就是要研究程式交易的部分。

而程式交易上線之前,有一個很重要的部分,就是要把策略進行回測

目前台灣這邊市場的主流是使用multichart 或是XQ之類的軟體,用其各自的策略撰寫語言,來進行回測。這類型軟體,雖然功能也都非常強大,不過要使用完整功能,都需要付費購買完整版或是訂閱加值模組。

而另一個麻煩的點,就是還要去學習其對應的策略或指標的撰寫語言,雖然語法對應傳統程式語言可能已經比較簡單了,不過還是需要花時間去了解一些特定的寫法。

而身為精打細算代表的勳仔,第一個是不想花大錢買套裝軟體或是想節省每個月的持續訂閱費,二來是勳仔也有點懶的去學那些套裝軟體的策略語言,於是乎一開始我就打算用目前最流行的python程式語言,來做策略開發跟回測。

而相對應的,勳仔也選擇在有提供python API的券商跟期貨商開戶,方便後續把策略串接到實際盤中下單,達到自動交易的功能。

用python來建自動交易系統的好處,是網路上有非常大量的資源可以使用,像是時下最流行的AI,python 有非常多前人開發的套件可以使用,所以可以迅速嘗試非常多種類的策略,不過對於希望專注策略開發的投資朋友,直接選擇功能完整的套裝軟體multichart跟XQ也是沒有問題的,如果真的能找到好的策略,則支付在軟體的成本很容易就可以直接忽略。

勳仔這篇主要著重在介紹python 的策略回測框架套件backtesting.py,關於python 環境的建置(ex:anaconda),還有基本的語法,不會在這篇做介紹,有需要的朋友可以投靠google 大神,網路上有很多免費教學的資源可以利用。

簡單易懂的Python 回測框架-Backtesting.py

Backtesting的這套免費策略回測框架,回測框本身跟API使用方式都潛顯易懂,等下後面會有一個簡單的程式碼範例可以給大家參考,而且回測後的結果也可以生成html網頁檔案,讓我們可以視覺化的去研究策略的成效,對於跟勳仔一樣還在程式交易新手村的投資朋友們,已經非常夠用了。

(Backtesting.py 回測結果顯示網頁)

我們這邊就用有投資網紅推薦的,利用0050的KD指標的黃金交叉跟死亡交叉當作進出場依據的策略當作範例,來實際回測過去八年(一般認為景氣循環是四年,八年跨過兩個景氣循換)的0050日k數據,看看是否真的是個能賺錢的策略。

KD指標

KD指標又稱隨機指標,是一個剛學習技術分析的投資朋友一定都聽過的指標。

而KD值的計算還會用到另一個指標,叫做RSV指標。RSV指標叫做未成熟隨機值,這個指標的意義是用來表示今天的股價對應近期一段時間內,究竟是弱勢還是強勢。

RSV的算法是找出最近N天股價的最高點跟最低點當作分母,而當日的股價減掉最近N天的最低價當作分子,兩者相除再乘上100之後會得到一個介於0-100的值

從上述RSV指標的定義,可以看出這個指標看的是今天股價相對於近期的股價,是處於偏弱勢區間還是偏強勢區間

而K值的計算,是計算昨日的k值跟今日的RSV值加權平均,昨日的k值佔2,今日的RSV值佔1。

而D值的計算則是昨日的D值,跟今日的K值,一樣照2:1的比例取加權平均來計算。所以相對k值,D值在反應股價的變化上,相對K值來的慢

所以一般認為,K值向上穿越D值,稱為黃金交叉股價後勢預期看漲,反之K值向下穿越D值,稱為死亡交叉則,代表股價後勢看跌

而KD在盤整時期,可能會在中線50的位置附近,頻繁交錯,所以投資達人或是書上常常會在加上幾個過濾條件,那就是黃金交叉而且KD值都低於20當作進場條件,而死亡交叉加上KD值都高於80當作出場條件。

這個策略看似非常簡單,但是真的有用嗎?還有為什麼是20跟80這個魔術數字,85跟25不行嗎?

勳仔不知道大家在看所謂投資達人出書再講這類型的操作時,會不會跟勳仔一樣浮出類似的問題。身為工程師,不自己來驗證看看怎麼行呢!這就是為什麼我們需要學回測框架目的,雖然過去經驗不完全代表未來,但是一套策略在過去績效慘澹,到未來能夠大放異彩的機率相對還是比較低的。

策略說明

我們利用python backtesting.py的框架,寫了KDCross下面這個非常簡單的策略。

基本上這個框架的使用方式很簡單,首先要先準備歷史股價資料,包含了Close/High/Low/Open/Volume這些資訊。

接著我們開始撰寫自己繼承自Strategy class的策略。在init函數要初始化一些需要用到的資料,而重點的策略則放在next函數中。

backtesting.py跑回測的時候,會從餵進來的歷史資料中,從時間序列的最前面開始一個一個時間點都去執行這個next,所以每一個next的呼叫都會判斷當時是否有某種訊號滿足一些預先定義好的行為,如果有,相對應要做什麼樣的交易動作,這些都會在next函數中被呼叫。

當歷史資料處理好,而且策略也寫好之後,就可以新增一個Backtest的類別然後執行他的run函數,則它就會自動開始執行回測,然後執行完之後會把結果回傳,我們在把它印出來看即可,同時也可以用plot函數,輸出並開啟html進行視覺化的分析,一切就是這麼簡單!

範例程式:

import pandas_datareader as web
import pandas as pd
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import matplotlib.pyplot as plt
from talib import abstract
import numpy as np

# download stock history data
#df = web.DataReader(name='0050.TW', data_source='yahoo', start='2014-07-25', end='2022-07-25') 
#df.to_csv("stock_0050_2014_2022_0725.csv")

# build data from csv and select target data
df = pd.read_csv('stock_0050_2014_2022_0725.csv')
df = df.interpolate()                #flll empty data
date_sel = df['Date']>='2014-07-25'  #select target data 
df_sel = df[date_sel]
df_sel['Date'] = pd.to_datetime(df_sel['Date']) #convert type to datetime
df_sel = df_sel.set_index('Date') # set date as index


#calculate KD signal with talib
df_tmp = df_sel
df_tmp.rename(columns = {'High':'high', 'Low':'low','Adj Close':'close','Close':'non_adj close'}, inplace = True) #rename for talib
kd = abstract.STOCH(df_tmp)
kd.index=df_tmp.index
fnl_df = df_tmp.join(kd).dropna() #merge two data frame
fnl_df.rename(columns = {'high':'High', 'low':'Low','close':'Close'}, inplace = True) #rename column name for backtest

def I_bypass(data): # bypass data in Strategy
    return data

class KDCross(Strategy): 
    lower_bound = 20  
    upper_bound = 80  

    def init(self):
        self.k = self.I(I_bypass, self.data.slowk) #K 
        self.d = self.I(I_bypass, self.data.slowd) #D

    def next(self):
        if crossover(self.k, self.d) and self.k<self.lower_bound and self.d<self.lower_bound and not self.position: #long position
            self.buy() 
        elif crossover(self.d, self.k) and self.k>self.upper_bound and self.d>self.upper_bound: 
            if self.position and self.position.is_long:
                self.position.close()
#run backtest
bt = Backtest(fnl_df, KDCross, cash=10000, commission=.002)
rslt = bt.run()
print(rslt)
bt.plot()

當K值跟D值發生低於二十的黃金交叉而且我們目前沒有倉位,則開倉作多。反之,當KD值發生高於80死亡交叉且我們手上有多單,則平倉賣出。

回測區間從2014年的八月六號到2022年的七月二十五號。回測結果如下:

看起來策略績效有點慘啊,居然會是負的,等於單純套用這個策略,不只沒有賺錢還會虧錢。雖然勝率有61%,但是獲利因子(profit factor)只有1.02,代表這個策略比較像是小賺大賠,基本上是不可能可以實際操作的策略。

而我們試著來改善這個交易策略,因為目前獲利因子很低,所以先追求減少虧損交易的虧損金額,希望能提高獲利因子,因此先考慮加入停損機制,讓每次賠錢的交易虧損不會太多,藉由大賺小賠的方式,達到績效的改善。

因此把上述策略多加上停損的條件: “前一天k棒收盤價的99%當作停損價"。一旦觸到這個價格,手上的持倉就會被停損掉。

class KDCross(Strategy): 
    lower_bound = 20  
    upper_bound = 80  
    sl_ratio = 99     # stop loss ratio, 99 means 1% loss
    def init(self):
        self.k = self.I(I_bypass, self.data.slowk) #K 
        self.d = self.I(I_bypass, self.data.slowd) #D

    def next(self):
        if crossover(self.k, self.d) and self.k<self.lower_bound and self.d<self.lower_bound and not self.position: #long position
            self.buy(size=.99,sl=self.data.Close[-1]*self.sl_ratio/100) 
        elif crossover(self.d, self.k) and self.k>self.upper_bound and self.d>self.upper_bound: 
            if self.position and self.position.is_long:
                self.position.close()

再跑一次回測結果:

這次的總報酬總算翻正的,有19.69%的總報酬,雖然還是慘輸單純買進持有的77.62%的回報率,不過至少策略修正看起來是有效的。因為停損點設得很敏感,所以很容易觸發到停損點,所以勝率剩下30.77%,不過因為控制了虧損交易的最大虧損,所以獲利因子就提升到了1.63,這個策略的型態就變得比較像是大賺小賠了。

縱使加了停損點讓報酬率變正,不過這個離實戰都還很遠,還有很多進出場條件的濾網可以去試,例如趨勢盤的時候,KD指標會遇到鈍化的問題,所以一種可能的濾網就是拿可以判斷震盪或是趨勢型態的指標當作是否要暫停這個策略的判斷依據,只在震盪盤的時候使用,如此一來應該還能再改善一點報酬率。

上述例子只是一個很簡單的例子在說明回測的重要性,不要看了書或是一些財經達人介紹某某策略,讓它財富翻倍,就照單全收,直接進場用一樣的策略操作。通常虧錢的機會比較多,如果有回測框架,我們就可以先拿來驗證可信度,至少下次有人跟你說0050在KD低於20的黃金交叉買進,KD高於80的死亡交叉賣出這套策略可以賺錢,你就可以回他說,我可能還是無腦buy & hold就好,哈哈。

心得:

對於程式交易者來說,把策略做回測是很重要的,而對於一般投資人來說,縱使不打算使用程式交易,但是利用回測去證實投資達人書上提到的方法是否有用,也可以幫助自己不會被書上影響,走到歪路,畢竟,「盡信書不如無書」,用量化數據佐證,也更能在心理層面支持投資決策,下單有憑有據,心態也比較穩定

這篇示範了幾個非常簡單的case,如今python相對簡單易懂的免費程式語言再加上簡單易懂的回測框架,勳仔真的鼓勵投資朋友們可以勇於嘗試看看,其實真的挺有趣好玩的。

免責聲明: 本篇文章僅就公開資訊進行主觀分析,僅供投資朋友參考,不應當做投資依據,投資朋友仍然需要根據自身風險承擔能力作為投資判斷,盈虧自負。

如果覺得這篇文章對您有幫助的話,歡迎點擊下方分享按鈕,分享這篇文章給你的FB或是Twitter的親朋好友,或是加入>>勳仔的理財小角落 FB專頁<<,接收最新的理財文章資訊

延伸閱讀(一): 技術分析到底有沒有用呢? 聊聊隨機漫步以及效率市場假說

延伸閱讀(二): 賭徒與投機者的資金控管經典-凱利公式

延伸閱讀(二): 新冠肺炎疫情啟示錄 – 被動現金流量的重要性 – 2020年回顧

有沒有最適合懶人的投資法? ETF (Exchange Traded Funds)可能就是你要的答案

懶人投資的敲門磚 – 初窺ETF

平常上班就好累了,下班當然要追劇或是打手遊,投資什麼的看起來好難,又好複雜,好懶的學,有沒有懶人包阿? 隨手按個鈕,就什麼都搞定了

下班只想懶懶躺在沙發的上班族

我想非常多的朋友不想踏出定存或是儲蓄險的理財方式,主要是因為覺得投資很難,上班都很累了,下班只想好好放鬆,享受生活,不想再花時間去研究這些複雜的理財商品或是研究產業以及個股。

所以最近幾年,投資ETF似乎變成顯學,幾乎接觸過一段時間的投資人,都會配置部分的資金在ETF上,許多知名的財經部落客也都推薦ETF來當作踏進投資領域的入門磚,這篇文章就讓我們來初窺ETF吧。

什麼是ETF(Exchange Traded Funds)?

台灣證券交易所給ETF的正式中文名稱叫做指數股票型基金,這個意思照字面翻就是在說明ETF是一種投資組合的組成是追蹤某種特定指數(EX: 股票指數/原物料指數/債券指數 ),並且可以直接在股票市場交易的基金

初學者乍聽之下似乎有點難理解,不過從英文字義直接翻譯的話也有人叫做交易所買賣基金。所以最簡單的說法,ETF就是可以直接在股票交易所(就是指股票市場)買賣的基金。

ETF也是基金,那跟平常我們聽到的共同基金,差別在哪呢?

在ETF蔚為流行之前,大家除了投資股票,最常的就是去買共同基金(Mutual Fund)。至少勳仔十幾年前最早開始的投資不是股票反而是跟銀行申購共同基金 (當然現在看起來在銀行申購基金是一個不太划算的事情),原因主要也是那時候還年輕,想說太多不懂了,就把錢交給專業基金的經理人跟團隊代為投資,基金經理團隊有著廣泛的財經跟金融背景,應該可以幫我投資讓我賺錢吧,秉持著這樣的信念,就這樣開始了我的投資生涯。

不過基金理財看似有著專業經理人幫我們打理一切,而且投資標的分散,讓雞蛋不要放在同個籃子裡,這應該是非常棒的懶人投資法,但是最近幾年,越來越多聲浪,主張去投資ETF而不要去投資共同基金,這是為什麼呢?

天下沒有白吃的午餐,共同基金的專業經理團隊是要收費的

共同基金是基金公司所成立的,並且每檔共同基金,會有一群負責研究如何選擇投資標的以及研究投資策略的團隊,這些人可能都是各大財經或是金融相關科系的頂尖人才,所以請他們操作,是需要付出不少錢的。

這些錢,基金公司會每年從它們所管理的資金裡面自動扣取,也就是每年先不管投資績效,我們的錢就會流失一定的比例來給於基金公司用於支付這些研究團隊的費用以及其他一些管銷的費用,這個被扣取的比例在共同基金中可能會高到1~2%。

ETF的投資組合只跟追蹤的指數有關,不需要主動選股

ETF的投資組合,發行的公司不需要養一大批人去做研究來當作挑選投資組合的依據,ETF只需要根據追蹤指數的組成變化,來被動調整自身的投資組合跟比例。只要指數的成分股跟比例沒有變化,ETF就不會主動去做投資組合的調整,因為ETF的最高宗旨就是要盡可能的貼近所追蹤的指數。

因為各家研究機構編制的指數,雖然會有不同種篩選成分股以及成分股比例的作法,但是成分股的組成通常也都還是蠻多的,仍然會有將雞蛋放在不同籃子這種降低風險的意義,所以ETF追蹤這些指數,等於會將非常多的成分股納入投資組合,跟共同基金一樣能降低單押個股所造成的風險ˋ。

另外這種被動的操作,帶來的好處就是,基金公司不需要付出很多的薪水去養大批的研究團隊,因為操作唯一的準則就是盡可能的複製所追蹤的指數,所以對於基金公司來說,成本相對來的低,回饋在投資人身上的就是將基金的內扣費用給減少。

以台股為例,元大0050(追蹤台股指數)ETF的內扣費用大約是0.4%左右,這個數字已經比投資台灣股票的共同基金的內扣費用還要低不少了,但以世界上第一支ETF美股代號SPY(追蹤的是標普500指數)來說,目前的內扣費則更是低到了0.0945%。

看似0050雖然內扣費用已經比共同基金低,但是對比美股的SPY(追蹤標普500)還是有一段不小的差距。不過ETF通常會隨著規模擴大,而慢慢降低內扣費用。因為通常基金被動管理所需要的費用不會隨著規模而等比例增長,當規模變大,管裡費用換算的比例就會相對比較小,所以對於台股投資人,或許可以期待當0050規模繼續擴大的時候,會有機會慢慢調降內扣費用來嘉惠台股投資人。

真正長期能打敗指數的共同基金寥寥可數

前面提到,雖然共同基金的內扣費用對比ETF高出了不少,可能投資人會想說,高一點就高一點,不過只要我多付一點錢請到一堆高手,讓基金的投資績效可以一直超越ETF追蹤的指數,那我也甘之如飴。

不過近幾年ETF變成顯學,就是有越來越多的研究發現,絕大部分的共同基金僅能短暫打敗指數,當時間拉長,真正能長期持續一直打敗指數的共同基金非常非常少

也就是說我每年都多花了1~2%的投資付錢給這些專業經理人,但是卻不能換到超額報酬,那我不如節省成本,選擇這種以追蹤指數為目標的ETF,我至少能拿到指數的報酬率。而指數反映的就是多空力道平衡後的結果,也就是說我拿到指數的報酬率,代表我贏過一半的投資人,以懶人投資的角度,算是非常划算了。

不要當誤入叢林的小白兔,不同種類的ETF特性還是要先了解再開始投資

最近幾年,ETF真的是大鳴大放,不僅僅在美股有著琳瑯滿目的ETF商品,台股的ETF種類也越來越多元。 從一開始最單純連結股票或是債券指數的ETF,到後來連結原物料的ETF,或是反向還是有槓桿的ETF也都在市場上找的到。

正因為後來ETF不斷演進,越來越多種特別的ETF都在市場上發行,對新手投資人需要特別注意的是,縱使ETF看似是個很妥當的懶人投資法,但是剛開始,還是建議以連結股票或是債券指數的ETF為一開始的選擇,而盡量晚點再開始投資原物料或是反向以及有槓桿的ETF。

主要的原因是因為買進連結股票指數或是債券的ETF,可以想成是實際持有一籃子的股票或債券,你長期持有的狀況下,因為有著股息或是債息,長期報酬率是可以期待的。

但是如果持有原物料/反向/槓桿的ETF,這邊都會需要利用期貨或是期權的一些交易,這樣隱含的就是這類型的ETF,會有到期日需要轉倉所額外造成的成本,這個成本在你長期持有的狀況下,是會持續傷害你的報酬的。 所以這類型的ETF比較適合短期的操作,但是對剛踏入投資領域的投資人相對是難度比較高而且風險也比較高的,就需要特別小心