vector-bt 例程介紹

import vectorbt as vbt
import numpy as np
import pandas as pd
import matplotlib
from matplotlib import pyplot as plt
matplotlib.rcParams['figure.figsize'] = (12, 5)

數(shù)據(jù)

從 cryptocurrency exchange Poloniex 處獲取 OHLC 數(shù)據(jù)

# period = 300, 900, 1800, 7200, 14400, or 86400
ohlc_df = vbt.data.load_cryptopair('USDT_ETH', vbt.data.ago_dt(days=180), vbt.data.now_dt(), period=vbt.data.Period.D1)
ohlc_df.head()

done. 1.71s


# No future data
rate_sr = ohlc_df.O
# Fees and slippage
# 手續(xù)費(fèi)和滑點(diǎn)
investment = 1000
fees = 0.0025
slippage_factor = 0.25
slippage = (ohlc_df['H'] - ohlc_df['L']) * slippage_factor / rate_sr
vbt.graphics.plot_line(rate_sr)

輸出:

   count        mean         std    min  25%       50%        75%      max
O  180.0  713.444128  233.235322  369.0  529.683513  681.0821  858.69125   1382.0  
價(jià)格曲線

indicators 指標(biāo)

計(jì)算均線 EMA 指標(biāo)

fast_ma_sr = vbt.indicators.EMA(rate_sr, span=5)
slow_ma_sr = vbt.indicators.EMA(rate_sr, span=21)

signals 交易信號(hào)

根據(jù)條件計(jì)算交易信號(hào)

  1. 當(dāng)快速均線上穿慢速均線時(shí),做多,反之,做空
ma_entries = vbt.signals.DMAC_entries(fast_ma_sr, slow_ma_sr)
ma_exits = vbt.signals.DMAC_exits(fast_ma_sr, slow_ma_sr)

多空向量都是0/1序列,方便進(jìn)行快速向量運(yùn)算。

因?yàn)槲覀円揖€交叉點(diǎn),所以我們只需要這兩個(gè)向量0/1序列的第一個(gè)信號(hào)。

ma_entries = vbt.bitvector.first(ma_entries)
ma_exits = vbt.bitvector.first(ma_exits)
  1. 無論何時(shí),如果價(jià)格下跌10%就做空。
# trailstop_exits = vbt.signals.trailstop_exits(rate_sr, ma_entries, 0.1 * rate_sr)

結(jié)合均線退出策略和跟蹤止損策略(trailing stop)

# ma_exits = vbt.bitvector.OR(ma_exits, trailstop_exits)
# ma_exits = vbt.bitvector.first(ma_exits)

如果還要應(yīng)用其它的策略,生成你自己的 bit 向量然后使用 vector.AND/OR/XOR 進(jìn)行結(jié)合。

positions 頭寸

從多空策略向量生成頭寸向量 Generete positions out of both vectors (merge and reduce).

pos_sr = vbt.positions.from_signals(rate_sr, ma_entries, ma_exits)
pos_sr.head()

輸出:

                O
date
2018-01-05      1
2018-01-18     -1
2018-01-29      1
2018-02-02     -1
2018-02-18      1
dtype: int32
ff = pd.DataFrame(ma_entries).set_index(rate_sr.index)
ff[ff==1].dropna()

輸出:

                O
date
2018-01-05    1.0
2018-01-29    1.0
2018-02-18    1.0
2018-02-20    1.0
2018-04-14    1.0
ff = pd.DataFrame(ma_exits).set_index(rate_sr.index)
ff[ff==1].dropna()

輸出:

                O
date
2018-01-18    1.0
2018-02-02    1.0
2018-02-19    1.0
2018-02-21    1.0
2018-05-23    1.0
pos_sr
date
2018-01-05    1
2018-01-18   -1
2018-01-29    1
2018-02-02   -1
2018-02-18    1
2018-02-19   -1
2018-02-20    1
2018-02-21   -1
2018-04-14    1
2018-05-23   -1
dtype: int32

頭寸序列是一個(gè)二元序列,1表示買入,-1表示賣出。因?yàn)槲覀兿敫宄販y(cè)評(píng)一個(gè)策略(只考慮策略邏輯對(duì)凈值的影響,不考慮投資額度的大小和分配),所以頭寸序列中的一行只有多或空,沒有兩個(gè)同時(shí)的頭寸。

買賣點(diǎn)的可視化

vbt.positions.plot(rate_sr, pos_sr)

輸出:

   count       mean         std         min   25%    50%        75%  \
0    5.0 -19.503679  137.281554 -205.209166 -61.0 -57.21  76.436671   

          max  
0  149.464099  

買賣點(diǎn)

注:綠色三角為買,紅色倒三角為賣

equity 凈值

基于交易手續(xù)費(fèi)和滑點(diǎn),從頭寸生成凈值。

equity_sr = vbt.equity.from_positions(rate_sr, pos_sr, investment, fees, slippage)
equity_sr.head()

輸出:

date
2018-01-04            NaN
2018-01-05     973.100223
2018-01-06     999.309720
2018-01-07    1040.616509
2018-01-08    1155.616055
dtype: float64

在建立第一個(gè)頭寸之前的凈值都是 NaN。

基準(zhǔn)和報(bào)價(jià)凈值(base and quote equities)的可視化

vbt.equity.plot(rate_sr, equity_sr)

輸出:

base: equity - hold
   count       mean         std         min       25%         50%         75%  \
0  179.0  44.435796  169.491093 -305.699824 -73.27329  110.092595  153.878813   

          max  
0  323.737977  

凈值曲線

注:紅色為跑輸股價(jià),綠色為跑贏股價(jià)

quote: equity - hold
   count      mean       std      min       25%       50%       75%       max
0  179.0  0.130039  0.277426 -0.33907 -0.077732  0.193859  0.267966  0.769767
凈值-報(bào)價(jià) 曲線

returns 回報(bào)率

生成回報(bào)率

returns_sr = vbt.returns.from_equity(equity_sr)
returns_sr.head()

輸出:

date
2018-01-04    0.000000
2018-01-05    0.000000
2018-01-06    0.026934
2018-01-07    0.041335
2018-01-08    0.110511
dtype: float64

回報(bào)率可視化

vbt.returns.plot(vbt.returns.resample(returns_sr, 'W'))

輸出:

   count      mean       std       min  25%  50%  75%       max
0   27.0 -0.003166  0.132375 -0.292969  0.0  0.0  0.0  0.374422
回報(bào)率曲線

performance 績(jī)效

顯示多個(gè)基于回報(bào)的KPI總結(jié)。

vbt.performance.summary(returns_sr)

輸出:

distribution         count         180.000000
                     mean           -0.000616
                     std             0.038778
                     min            -0.197584
                     25%             0.000000
                     50%             0.000000
                     75%             0.000000
                     max             0.133985
performance          profit         -0.219902
                     avggain         0.049820
                     avgloss         0.053396
                     winrate         0.500000
                     expectancy     -0.003547
                     maxdd           0.577767
risk/return profile  sharpe         -0.015882
                     sortino        -0.014195
dtype: float64

optimizer.gridsearch 模型優(yōu)化/參數(shù)搜索

傳統(tǒng)的的優(yōu)化方法就是網(wǎng)格搜索(或者說窮舉)。這種方法窮舉生成所有可能的參數(shù)組合。

這種方法的優(yōu)點(diǎn):

  • 容易實(shí)現(xiàn)
  • 二維組合可以通過熱力圖可視化
  • 可以用于發(fā)現(xiàn)未知的組合模式
  • 可以高度并行化處理

但是也有一些不足之處:

  • 不夠靈活,難以適應(yīng)不斷變化的金融市場(chǎng)
  • 易于“過擬合”
  • 沒有中間回饋信號(hào)

網(wǎng)格搜索包括 3-4 階段:

層級(jí) 目的 模塊 結(jié)構(gòu)
1 計(jì)算頭寸、凈值、回報(bào) srmap {param: pd.Series}
2 計(jì)算 KPIs nummap pd.Series
3 (可選) 組合多個(gè)KPI指標(biāo)為一個(gè)分值并比較 scoremap pd.Series
4 構(gòu)造熱力圖以查看某些未知的模式 matrix pd.DataFrame

最后我們就可以比較不同交易策略的績(jī)效水平了。

L1 第一階段

srmap 模塊

import vectorbt.optimizer.gridsearch as grids

對(duì)一系列均線組合計(jì)算回報(bào)。

1. 先計(jì)算所有的均線。
# Init
ma_func = lambda span: vbt.indicators.EMA(rate_sr, span=span)
min_ma, max_ma, step = 1, 100, 1
fees = 0.0025

# Cache moving averages
param_range = grids.params.range_params(min_ma, max_ma, step)
ma_cache = dict(grids.srmap.from_func(ma_func, param_range))

cores: 8
processes: 1
starmap: False
calcs: 100 (~5.26s) ..
done. 0.03s

注:上面輸出中的 calcs 的意思是需計(jì)算次數(shù)和預(yù)計(jì)總的計(jì)算時(shí)間
而 done 后面是使用多核處理實(shí)際花費(fèi)的時(shí)間,這里可以看到其加速了170倍!
下面計(jì)算頭寸需要5050次計(jì)算,其加速超過700倍?。?!

ma_cache Dict數(shù)據(jù)類型:

  • keys: 1..100

  • Values: key對(duì)應(yīng)的日均線,Series類型數(shù)據(jù),如 key=5:MA5

    1. 對(duì)每個(gè)均線組合,生成頭寸向量。
# Params
ma_space = grids.params.combine_rep_params(min_ma, max_ma, step, 2)

# Func
def ma_positions_func(fast_ma, slow_ma):
    # Cache
    fast_ma_sr = ma_cache[fast_ma]
    slow_ma_sr = ma_cache[slow_ma]
    # Signals
    entries = vbt.signals.DMAC_entries(fast_ma_sr, slow_ma_sr)
    entries = vbt.bitvector.first(entries)
    exits = vbt.signals.DMAC_exits(fast_ma_sr, slow_ma_sr)
    exits = vbt.bitvector.first(exits)
    # Positions
    pos_sr = vbt.positions.from_signals(rate_sr, entries, exits)
    return pos_sr
ma_positions_srmap = grids.srmap.from_func(ma_positions_func, ma_space)

cores: 8
processes: 1
starmap: True
calcs: 5050 (~1453.19s) ..
done. 2.07s

ma_positions_srmap 為Dict數(shù)據(jù)類型:

  • key: 均線組合 (fast, slow) tuple類型,如,(5, 21)為5日21日均線組合

  • value: 頭寸向量,Series類型

    1. 對(duì)每個(gè)頭寸向量,生成回報(bào)向量。

我們需要分開計(jì)算頭寸和回報(bào),因?yàn)槲覀冃枰總€(gè)均線組合的頭寸向量長(zhǎng)度來進(jìn)行隨機(jī)映射。

def ma_returns_func(fast_ma, slow_ma):
    # Equity
    pos_sr = ma_positions_srmap[(fast_ma, slow_ma)]
    equity_sr = vbt.equity.from_positions(rate_sr, pos_sr, investment, fees, slippage)
    # Returns
    returns_sr = vbt.returns.from_equity(equity_sr)
    return returns_sr
ma_returns_srmap = grids.srmap.from_func(ma_returns_func, ma_space)

cores: 8
processes: 1
starmap: True
calcs: 5050 (~1451.34s) ..
done. 5.68s

ma_returns_srmap 為Dict數(shù)據(jù)類型:

  • key: 均線組合 (fast, slow) tuple類型,如,(5, 21)為5日21日均線組合
  • value: 回報(bào)向量,Series類型,這個(gè)Series是每天的回報(bào),如0.012表示回報(bào)1.2%

為每個(gè)均線組合生成同樣長(zhǎng)度的隨機(jī)頭寸向量和相應(yīng)的回報(bào)向量。

# Params
random_space = [(fma, sma, len(np.flatnonzero(ma_positions_srmap[(fma, sma)].values))) for fma, sma in ma_space]

# Func
def random_returns_func(slow_ma, fast_ma, n):
    # Positions
    pos_sr = vbt.positions.random(rate_sr, n)
    # Equity
    equity_sr = vbt.equity.from_positions(rate_sr, pos_sr, investment, fees, slippage)
    # Returns
    returns_sr = vbt.returns.from_equity(equity_sr)
    return returns_sr
random_returns_srmap = grids.srmap.from_func(random_returns_func, random_space)

cores: 8
processes: 1
starmap: True
calcs: 5050 (~1445.95s) ..
done. 7.49s

random_returns_srmap 為Dict數(shù)據(jù)結(jié)構(gòu):

  • keys tuple類型, (短線的天數(shù),長(zhǎng)線的天數(shù),交易信號(hào)數(shù)量)
  • value Series類型,每天的收益率

L2 第二階段

nummap 模塊

對(duì)每個(gè)回報(bào)向量,計(jì)算 KPI 指標(biāo)。

注:此處對(duì)回報(bào)進(jìn)行了累積得到了累積回報(bào)

if_i_hold = vbt.performance.profit(rate_sr.pct_change())
profit = lambda r: vbt.performance.profit(r) - if_i_hold
ma_profit_nummap = grids.nummap.from_srmap(ma_returns_srmap, vbt.performance.profit)

cores: 8
processes: 1
starmap: False
calcs: 5050 (~4.29s) ..
done. 2.42s
min (1, 7): -0.7055079244549305
max (11, 13): 0.033964270132262

sharpe = lambda r: vbt.performance.sharpe(r, nperiods=252)
ma_sharpe_nummap = grids.nummap.from_srmap(ma_returns_srmap, sharpe)

cores: 8
processes: 1
starmap: False
calcs: 5050 (~1411.08s) ..
done. 0.97s
min (1, 7): -1.909136002820758
max (11, 13): 0.3736374454176382

random_profit_nummap = grids.nummap.from_srmap(random_returns_srmap, vbt.performance.profit)

cores: 8
processes: 1
starmap: False
calcs: 5050 (~4.86s) ..
done. 2.38s
min (2, 4, 31): -0.8685124600763209
max (75, 85, 4): 1.6696239049805262

比較EMA均線策略和隨機(jī)策略的各個(gè)分位數(shù)分布情況。

grids.nummap.compare_quantiles(ma_profit_nummap, random_profit_nummap)
           count      mean       std       min       25%       50%       75%  \
nummap     5050.0 -0.204439  0.132031 -0.705508 -0.306606 -0.216076 -0.116936   
benchmark  5050.0 -0.295012  0.305769 -0.868512 -0.521892 -0.350998 -0.122543   
               max  
nummap     0.033964  
benchmark  1.669624  

分位數(shù)分布情況

注:X號(hào)標(biāo)識(shí)了最好和最壞情況
綠色表示均線跑贏隨機(jī),紅色相反
從這里看出,大約75%的情況均線策略是勝過隨機(jī)策略的;隨機(jī)策略可能出現(xiàn)比較極端的情況;最后,只做多頭,在股價(jià)大幅下跌50%的情況下,兩種策略都很少有取得正收益的參數(shù)配置 :-(

比較 KPI 分布情況。

cmap = plt.cm.rainbow_r
norm = plt.Normalize()
grids.nummap.compare_hists(ma_profit_nummap, random_profit_nummap, 50, cmap, norm)
           count      mean       std       min       25%       50%       75%  \
nummap     5050.0 -0.204439  0.132031 -0.705508 -0.306606 -0.216076 -0.116936   
benchmark  5050.0 -0.295012  0.305769 -0.868512 -0.521892 -0.350998 -0.122543   
               max  
nummap     0.033964  
benchmark  1.669624  
均線策略KPI分布
隨機(jī)策略KPI分布

L3 第三階段

scoremap 模塊

考慮 KPI 多個(gè)指標(biāo)的權(quán)重,生成一個(gè)1-100的分?jǐn)?shù)。

ma_scoremap = grids.scoremap.from_nummaps([ma_profit_nummap, ma_sharpe_nummap], [2/3, 1/3], [False, False])

done. 0.01s
min (1, 7): 1.0
max (11, 13): 99.99999999999999

L4 第四階段

matrix 模塊

把二維參數(shù)網(wǎng)格映射到矩陣向量

ma_matrix = grids.matrix.from_nummap(ma_profit_nummap, symmetric=True).fillna(0)

done. 1.94s

顯示為熱力圖

cmap = plt.cm.rainbow_r
norm = plt.Normalize()
matplotlib.rcParams['figure.figsize'] = (12, 9)
grids.matrix.plot(ma_matrix, cmap, norm)
matplotlib.rcParams['figure.figsize'] = (12, 5)
    count      mean       std       min       25%       50%       75%  \
0  10000.0 -0.206483  0.131083 -0.705508 -0.306749 -0.223663 -0.116936   
       max  
0  0.033964  
各參數(shù)組合熱力圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容