---
# System prepended metadata

title: 'Python筆記 : 股票策略回測 by Vectorbt'

---

# 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)
