跳到主要內容

用Python進行回測




 =IMPORTDATA("https://www.twse.com.tw/exchangeReport/BWIBBU_ALL?response=open_data")


爬蟲

https://blog.raymond-investment.com/web-crawler-twse-2/


使用Python進行回測需要熟悉時間序列資料,建議學習Pandas套件。這套件讓回測變得簡單,可以建立固定模板。將交易訊號轉換成報酬,未來只需更新訊號,即可生成累積報酬的權益曲線。

資料來源:

https://raymond-investment.com/blog/python-backtesting

抓取資料

先利用我們在【網路爬蟲】臺灣證券交易所歷史資料教學(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()

 

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

資料來源

https://raymond-investment.com/Home

留言

這個網誌中的熱門文章

workshop list

  小孩最開始說的那幾句話 小孩商品的紙盒回收製作 小孩商品回收製作  奶瓶  各種紙箱與透明紙

書面審核、所得額標準與同業利潤標準的差異

1. 擴大書面審核 目的 :鼓勵小型營利事業按擴大書審純益率標準申報,以簡化稽徵成本和程序。 適用條件 : 全年營業收入及非營業收入(不包括土地及其定著物之交易增益)合計不超過3000萬元。 結算申報書表齊全。 自行調整之純益率高於擴大書審純益率標準。 申報期限內繳清應納稅款。 計算公式 : ( 營收淨額 + 業外收入 ) × 擴大書審純益率 × 17 % = 應納稅額 ( 營收淨額 + 業外收入 ) × 擴大書審純益率 × 20% = 應納稅額 2. 所得額標準 適用情況 :稽徵機關每年依實際申報狀況,核定該業所得額標準。 收入合計 : <3000萬 :可依擴大書審純益率標準自行調整申報。 >3000萬 :如申報所得額高於該業標準,應進行書面審核。 計算公式 : ( 營收淨額 × 所得額標準 + 業外收入 − 業外損失 ) × 17 % = 應納稅額 ( 營收淨額 × 所得額標準 + 業外收入 − 業外損失 ) ×20 % = 應納稅額 3. 同業利潤標準 適用情況 :營利事業無法提供帳證或資料不健全時,稽徵機關可依同業利潤標準認定所得。 標準 : 淨利率標準 :未提供營業收入、成本及費用時適用。 毛利率標準 :未提供營業收入及成本時適用。 注意 :此標準具懲罰性,建議避免使用。 建議 費用不足、營業額高(年營收超過2000萬) :建議採所得額標準,調帳機會低。 費用不足、營業額低(年營收2000萬以下) :可採書審申報,雖有風險,但調帳機會相對較低,即使調帳也僅需按照同業利潤標準補稅。

項目2-2應用

使用條件判斷和迴圈的應用 結合條件判斷和迴圈,可以執行更複雜的邏輯。以下是一個示例: python Copy code # 找出列表中的偶數並列印 numbers = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] for num in numbers: if num % 2 == 0 : print (num) 在這個例子中,我們使用 for 迴圈遍歷列表中的數字,並使用條件判斷找出偶數。 函數的進階應用 函數可以有預設參數值和返回多個值。以下是一個例子: python Copy code # 函數帶有預設參數值 def greet ( name, greeting= "Hello" ): print (greeting + ", " + name + "!" ) # 調用函數 greet( "Alice" ) greet( "Bob" , "Good morning" ) 在這個例子中, greet 函數有一個預設的問候語,當然你也可以提供自己的問候語。 列表生成式 使用列表生成式可以簡潔地創建列表。以下是一個例子: python Copy code # 使用列表生成式創建平方數列表 squares = [x** 2 for x in range ( 1 , 6 )] print (squares) 在這個例子中,我們使用列表生成式創建了包含1到5的數字平方的列表。 字典的進階應用 字典可以進行遍歷並取得鍵值對。以下是一個例子: python Copy code # 遍歷字典並列印鍵和值 person = { "名字" : "小明" , "年齡" : 25 , "城市" : "台北" } for key, value in person.items(): print (key + ": " + str (value)) 在這個例子中,我們使用 items 方法遍歷字典中的鍵值對並進行列印。 類別和物件 Pyt...