# Python筆記 : 股票策略回測 by Vectorbt [Vectorbt官網](https://vectorbt.dev/) ## 大致介紹 vectorbt是一套拿來進行量化分析的套件,特別的點在於他有numpy的速度,以及pandas的方便性。 因此比起其他的回測套件,他擁有極佳的速度,可以在短時間之內分析大量的策略。其中,套到這個套件裡面的所有參數都可以進行向量化,允許我們同時對所有元素執行相同的操作。另外,也使用 Numba 解決了與向量化相關的路徑依賴問題。 這邊大部分使用的套件說明都可以在[這裡](https://vectorbt.pro/api/)看到詳細說明。 ## 模組 ```python= import vectorbt as vbt # import plotly 可不用,因vectorbt有自帶視覺化套件 import datetime import pandas as pd import numpy as np ``` ## 時間設定以及視覺化分析資料的位置 ```python= filename='某個位置路徑' end_date=datetime.datetime.now() start_date=end_date - datetime.timedelta(days=3) #限制在3天之內 ``` 這邊設3天是因為等等要抓取的時間單位為一分鐘,故三天已經夠長了。 ## Data Import ```python= btc_price = vbt.YFData.download('BTC-USD', #['BTC-USD','ETH-USD'], 可以用列表同時import多重的data interval='1m', #改變時間的單位 start = start_date, end=end_date, missing_idnex='drop').get('Close') ``` vectorbt會自動抓取yfinace的api,只要yfinance有的,都可以抓的到。 標的可以分為: | 台股 | 美股 | 加密貨幣 | | -------- | -------- | -------- | | Ex : 2330.TW | Ex : TSLA | Ex : BTC-USD | ## 交易策略 這裡使用的交易策略相當簡單,因為主要目的是為了熟悉vectorbt的使用。 | 情況 | 操作 | | ------------------------------------ | ---- | | rsi>設定的高標 | 賣出 | | rsi<設定的低標 且 最近的收盤價低於ma | 買入 | 當rsi大於我們設定的高標,代表目前股市可能進入過熱的壯台,則我們可以選擇在這時候進行賣出的操作。當相反情況出現,rsi低於我們設定的低標的話,而且這時候的收盤價低於ma線,表示這時候或許是一個很好的入場點,則我們可以在這時候進行買入的操作。(這邊都是極度簡單的操作,要是這樣就可以穩穩贏過大盤就太感謝了...) ## 客製化訊號以及策略 這邊主要可以分成三個區塊,分別為: 1. Define一個函數,裡面主要用來計算出是否可以進入市場的訊號 -> 1及-1。 2. 算是一個用來做出策略的食譜,裡面放著等等產出的資料變數名稱以及一些默認變數。 3. 實際執行策略,裡面包含一些變數的範圍。 ### 1. Define a function ```python= def custom_indicator(close, #每當在這邊增加一個參數,就要在第二步驟的ind裡面的param_names裡面增加 rsi_window = 14, ma_window = 50, entry=30, exits=70 ): close_5m = close.resample('5T').last() #將資料型態從1min變成5min rsi = vbt.RSI.run(close_5m, window = rsi_window).rsi #rsi中的rsi值(因為在這裡的前面rsi出來的不會只有單純的rsi) rsi, _ =rsi.align(close, #將5min的資料重新展開成1min broadcast_axis=0, method='ffill', #並將空值以第5分鐘的copy塞進去 join='right' #rsi是right table,close是left table ) #by doing so, rsi跟close有same shape,只是rsi是5min的資料,close是1分鐘的資料 close = close.to_numpy() rsi = rsi.to_numpy() ma = vbt.MA.run(close, window = ma_window).ma.to_numpy() trend = np.where(rsi > exits, -1, 0) #要是rsi>70,則賣出(-1),否則甚麼都不做(0) trend = np.where((rsi < entry)&(close < ma), 1, trend) #要是rsi<30而且收盤價<ma,則買入(1),如果沒有的話就按照原本的trend return trend ``` ### 2. 策略的食譜 ```python= ind = vbt.IndicatorFactory( class_name = 'Combination', short_name = 'comb', input_names=['close'], #輸入的parameter的名字 #param_names=['window'], #hyper parameter的名字,也可用列表呈現 param_names=['rsi_window','ma_window','entry','exits'], output_names=['value'] #output出來的名字 ).from_apply_func( #提供一些默認值給recipe,就是上面define的function裡面的parameter custom_indicator, rsi_window=14, ma_window=50, entry=70, exits=30, keep_pd=True #保持資料型態為pandas,避免變成numpy arrays ) ``` ### 3. 實際執行策略 ```python= res = ind.run( #run 一個策略 btc_price, #rsi_window = [14,35,21], #給定特定值 rsi_window = np.arange(10,40,step=3,dtype=int), #給定一個範圍 #ma_window = [21,50,100], ma_window = np.arange(20,200,step=15,dtype=int), #entry=[30,40], entry = np.arange(10,40,step=4,dtype=int), #exits=[60,70], exits = np.arange(60,85,step=4,dtype=int), param_product=True) #如果沒有這個的話,只會按照list的順序,並不會兩兩配對 ``` 這邊有兩種險方式來定義變數,一個是給定特定值的list,Ex:[14,35,21],表示將rsi_window分別設定為14、35、21,代表只會嘗試這三個數值。另一種方式則是使用np.arange,將在這個range的數值全部帶進去進行計算,進而求出每一種狀況下的獲利。 ### 執行結果 ```python= print(res.value) ``` output: ``` comb_rsi_window 10 ... 37 comb_ma_window 20 ... 185 comb_entry 10 ... 38 comb_exits 60 64 ... 80 84 symbol BTC-USD ETH-USD BTC-USD ... ETH-USD BTC-USD ETH-USD Datetime ... 2022-08-11 03:07:00+00:00 0 0 0 ... 0 0 0 2022-08-11 03:09:00+00:00 0 0 0 ... 0 0 0 2022-08-11 03:11:00+00:00 0 0 0 ... 0 0 0 2022-08-11 03:13:00+00:00 0 0 0 ... 0 0 0 2022-08-11 03:15:00+00:00 0 0 0 ... 0 0 0 ... ... ... ... ... ... ... 2022-08-14 03:00:00+00:00 0 0 0 ... 0 0 0 2022-08-14 03:01:00+00:00 0 0 0 ... 0 0 0 2022-08-14 03:02:00+00:00 0 0 0 ... 0 0 0 2022-08-14 03:03:00+00:00 0 0 0 ... 0 0 0 2022-08-14 03:04:00+00:00 0 0 0 ... 0 0 0 [3690 rows x 13440 columns] ``` 如果將結果攤開來看,會呈現 ![](https://i.imgur.com/cffT5wr.png) 以及 ![](https://i.imgur.com/BATll7T.png) 代表著我們進出場的訊號。 ## 將訊號帶入策略執行 這時我們將剛剛得到的res進行處裡,其中1設定為entries,-1設定為exits。 ```python= entries = res.value == 1 exits = res.value == -1 ``` 實際執行 ```python= pf = vbt.Portfolio.from_signals(btc_price, entries, exits) ``` 試著看看執行結果 ```python= print(pf.stats().to_string()) #to_string()可以將全部結果攤開 ``` 會得到: ``` Output from spyder call 'get_namespace_view': Start 2022-08-11 03:07:00+00:00 End 2022-08-14 03:04:00+00:00 Period 3690 Start Value 100.0 End Value 100.179789 Total Return [%] 0.179789 Benchmark Return [%] 3.590516 Max Gross Exposure [%] 83.020833 Total Fees Paid 0.0 Max Drawdown [%] 2.078304 Max Drawdown Duration 2193.187668 Total Trades 4.26994 Total Closed Trades 3.966369 Total Open Trades 0.303571 Open Trade PnL 0.076599 Win Rate [%] 63.829362 Best Trade [%] 0.680632 Worst Trade [%] -0.39365 Avg Winning Trade [%] 0.607418 Avg Losing Trade [%] -0.6351 Avg Winning Trade Duration 260.345941 Avg Losing Trade Duration 445.916595 Profit Factor inf Expectancy 0.202725 ``` 執行此段程式碼則可以獲得所有變數組合的報酬 ```python= print(pf.total_return().to_string()) ``` 只擷取其中一小段 ```python 170 10 60 BTC-USD 0.000000 ETH-USD 0.000000 64 BTC-USD 0.000000 ETH-USD 0.000000 68 BTC-USD 0.000000 ETH-USD 0.000000 72 BTC-USD 0.000000 ETH-USD 0.000000 76 BTC-USD 0.000000 ETH-USD 0.000000 80 BTC-USD 0.000000 ETH-USD 0.000000 84 BTC-USD 0.000000 ETH-USD 0.000000 14 60 BTC-USD 0.000000 ETH-USD 0.000000 64 BTC-USD 0.000000 ETH-USD 0.000000 68 BTC-USD 0.000000 ETH-USD 0.000000 72 BTC-USD 0.000000 ETH-USD 0.000000 76 BTC-USD 0.000000 ETH-USD 0.000000 80 BTC-USD 0.000000 ETH-USD 0.000000 84 BTC-USD 0.000000 ETH-USD 0.000000 ``` 只抽取此策略的報酬率 ```python= returns = pf.total_return() print(returns.max()) #最大的報酬率 print(returns.idxmax()) #最大的組合 print(returns.to_string()) #所有組合 ``` 可以得到 ```python 0.06338762101981515 (37, 20, 30, 84, 'ETH-USD') #分別代表rsi_window, ma_window, entry, exits ``` ## 資料視覺化 vectorbt套件中有附兩種資料視覺化的模式,分別為: 1. Heatmap 2. Volume ### Heatmap ```python= fig = returns.vbt.heatmap( x_level = 'comb_rsi_window', #y_level = 'comb_ma_window', y_level = 'comb_entry', slider_level = 'symbol' #如果同時分析不同標的,則可以透過slider切換 ) fig.write_html(filename,auto_open=True)#圖片儲存並自動展開 ``` ![](https://i.imgur.com/A3dTzS8.png) ### Volume 將資料以3d的樣式呈現,可同時比較多個變數。 ```python= fig = returns.vbt.volume( x_level = 'comb_exits', y_level = 'comb_ma_window', z_level = 'comb_entry', slider_level = 'symbol' ) fig.write_html(filename,auto_open=True)#圖片儲存並自動展開 ``` ![](https://i.imgur.com/RN85DH1.png)