...

【程式交易系列】#4 用Python進行回測

量化交易


這篇文章將會按照上一篇【程式交易系列】#3 回測(Backtesting)的架構來實作,讓大家可以透過Python程式碼能又更具體的感受。


抓取資料

先利用我們在【網路爬蟲】臺灣證券交易所歷史資料教學(2)的爬蟲方法將股價資料抓下來,並存成Excel檔案,前面均不變僅在最後一行加上了將DataFrame產出為Excel的指令。

import pandas as pd
import numpy as np
import json
import requests
import datetime
import time


def Get_StockPrice(Symbol, Date):

    url = f'https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date={Date}&stockNo={Symbol}'
    print(url)
    data = requests.get(url).text
    json_data = json.loads(data)

    Stock_data = json_data['data']

    StockPrice = pd.DataFrame(Stock_data, columns = ['Date','Volume','Volume_Cash','Open','High','Low','Close','Change','Order'])

    StockPrice['Date'] = StockPrice['Date'].str.replace('/','').astype(int) + 19110000
    StockPrice['Date'] = pd.to_datetime(StockPrice['Date'].astype(str))
    StockPrice['Volume'] = StockPrice['Volume'].str.replace(',','').astype(float)/1000
    StockPrice['Volume_Cash'] = StockPrice['Volume_Cash'].str.replace(',','').astype(float)
    StockPrice['Order'] = StockPrice['Order'].str.replace(',','').astype(float)

    StockPrice['Open'] = StockPrice['Open'].str.replace(',','').astype(float)
    StockPrice['High'] = StockPrice['High'].str.replace(',','').astype(float)
    StockPrice['Low'] = StockPrice['Low'].str.replace(',','').astype(float)
    StockPrice['Close'] = StockPrice['Close'].str.replace(',','').astype(float)

    StockPrice = StockPrice.set_index('Date', drop = True)


    StockPrice = StockPrice[['Open','High','Low','Close','Volume']]
    print(StockPrice)
    return StockPrice

if __name__ == '__main__':   
    Symbol = '00631L'
    Dates = pd.date_range(start = '2010-01-01', end = '2020-09-01', freq = 'MS').astype(str)

    data = Get_StockPrice(Symbol, Dates[0].replace('-',''))

    for Date in Dates[1:]:
        try:
            data = pd.concat([data,Get_StockPrice(Symbol, Date.replace('-',''))], axis = 0)
            time.sleep(5)
        except:
            pass

    data.to_excel(Symbol + '.xlsx')

 

 

分析資料與交易策略發想

首先我們將剛剛生成的Excel檔案讀入,先將歷史資料暫存的目的就是要讓每次測試執行時,不用一直去交易所爬蟲,爬蟲比較耗時間,例如每次爬取一個月後,就必須要sleep幾秒,所以我們這個簡單的範例就先用Excel來暫存,我自己在ㄧ些比較大的專案比較喜歡建立SQL的資料庫。

讀入歷史資料:

import pandas as pd
import numpy as np

df = pd.read_excel('2330.xlsx')
df.info()

 

確認格式均為可計算的浮點位數

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2539 entries, 0 to 2538
Data columns (total 6 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   Date    2539 non-null   datetime64[ns]
 1   Open    2539 non-null   float64       
 2   High    2539 non-null   float64       
 3   Low     2539 non-null   float64       
 4   Close   2539 non-null   float64       
 5   Volume  2539 non-null   float64       
dtypes: datetime64[ns](1), float64(5)
memory usage: 119.1 KB

 

我們先來用一個相當簡單的範例來說明回測的流程,這篇的目的是教大家怎麼樣做回測,不是要告訴你會賺錢的策略,大家別誤會啊!所以我們先用一個老少咸宜的均線交叉策略來說明,我們在這邊假設以短中期均線作為我們的交易策略,當短期均線由下往上穿越中期均線時,則進場買進,當短期均線由上往下穿越中期均線時,則出清持股。

df['MA1'] = df['Close'].rolling(20).mean() #計算短期均線
df['MA2'] = df['Close'].rolling(60).mean() #計算中期均線

df[['Close','MA1','MA2']].plot() #將收盤價、短期均線、中期均線進行作圖

 

交易訊號轉換

我們可以利用np.where的函數來操作,np.where就是矩陣式計算if-else的意思,可以看到下面程式碼中np.where有三個參數,第一個為判斷的條件,我們的條件式短期均線大於中期均線,第二個參數就是符合條件時,df[‘Signal’]將產生出哪個值,如果不符合條件時,產生出的值則放在第三個參數。

df['Signal'] = np.where(df['MA1'] > df['MA2'], 1, 0)
df['Signal'].plot(kind = 'area')

 

回測

進行回測也有幾個動作要處理,第一個是我們要先計算股票的日報酬率,這樣才能在回測時知道每一天的報酬率狀況,計算完股價日報酬率後,另外需建立交易策略下的報酬率,而且我們並假設邏輯如下:當天收盤確認訊號出現,在隔天的收盤價進場

 

df['Return'] = df['Close'].pct_change() #計算股價日報酬率
df['Strategy_Return'] = df['Return'] * df['Signal'].shift(1) #計算交易策略日報酬率

 

先就兩個策略間的日報酬率進行分析

df[['Return','Strategy_Return']].describe()

 

            Return  Strategy_Return
count  2538.000000      2538.000000
mean      0.000858         0.000567
std       0.014856         0.011117
min      -0.069194        -0.067623
25%      -0.007973        -0.002658
50%       0.000000        -0.000000
75%       0.009343         0.004320
max       0.099741         0.099741

 

可以發現整體績效狀況不大明顯的差異,僅有在最低的第25百分位數日報酬有比較少的狀況。

績效評估

我們第一件事就是去產生權益曲線(Equity Curve),由於是使用報酬率來去進行估計報酬率,所以就會有所謂的單利與複利狀況,我們分別假設兩種狀況。

df['EquityCurve(Simple)'] = df['Strategy_Return'].cumsum() #單利
df['EquityCurve(Compound)'] = ( 1 + df['Strategy_Return']).cumprod() -1 #複利

df[['EquityCurve(Simple)','EquityCurve(Compound)']].plot()

 

由於是一個長期向上的曲線,所以橘色線(複利)一定會比藍色線(單利)來得高,而複利其實是比較符合實際金融市場交易的狀況,買進一張股票或一口期貨的市值將會隨著市價的變動而變動,所以投資部位的市值是不斷在改變的,而單利的假設為每天的投資部位市值是不變的!

在產生出交易訊號、策略報酬率及權益曲線後,要計算出完整的策略報酬就只是資料處理的技巧了,就留給大家自己做了,有任何問題都可以留言或在粉專私訊我。

結論

最基本的回測操作就是由上面所教學的內容,大家可以自行去延伸,包含交易策略的設計與績效衡量,甚至是最佳化,在能夠自己完成回測程式後,再回去看上一篇【程式交易系列】#3 回測(Backtesting)的內容,相信大家就能夠對這塊有更深一層的瞭解。