2.6 MiB
2.6 MiB
Walk Forward Optimization¶
In [63]:
import vectorbtpro as vbt import pandas as pd import numpy as np import sys sys.path.append("..") from custom_indicators import * from custom_functions import * vbt.settings.plotting["layout"]["template"] = "vbt_dark" vbt.settings.plotting["layout"]["width"] = 1200 vbt.settings.plotting['layout']['height'] = 500 vbt.settings.wrapping["freq"] = "1d" vbt.settings.portfolio['size_granularity'] = 1 vbt.settings.portfolio['init_cash'] = 10000
Get our Data¶
In [2]:
data = get_fx_majors(datapath='../Data/FOREX/oanda', end='2015')
Loading FOREX BID major pairs.
0%| | 0/28 [00:00<?, ?it/s]
c:\Users\bjorn\anaconda3\envs\vectorbt-dev\lib\site-packages\vectorbtpro\data\base.py:723: UserWarning: Symbols have mismatching index. Setting missing data points to NaN. data = cls.align_index(data, missing=missing_index, silence_warnings=silence_warnings)
Develop Strategy¶
In [7]:
import talib as ta
In [40]:
def sma_cross(close, slow_period, fast_period): # Fix our Data close = vbt.nb.ffill_1d_nb(close) # Calculate Moving Averages slow_ma = ta.SMA(close, slow_period) fast_ma = ta.SMA(close, fast_period) # Calculate Signals # Long Entry long_entry = vbt.nb.crossed_above_1d_nb(fast_ma, slow_ma) # Short Entry short_entry = vbt.nb.crossed_below_1d_nb(fast_ma, slow_ma) # Trend trend = fast_ma > slow_ma return long_entry, short_entry, trend
In [41]:
strat = vbt.IF( class_name='strat', short_name='strat', input_names=['close'], param_names=['slow_period', 'fast_period'], output_names=['long_entry', 'short_entry', 'trend'] ).with_apply_func( sma_cross, takes_1d=True, slow_period=200, fast_period=50 )
Calculate Strategy Performance Across Different Parameter Values¶
In [42]:
sma_strat = strat.run( data.close, slow_period=np.arange(40, 150, 10, dtype=int), fast_period=np.arange(10, 35, 5, dtype=int), param_product=True )
In [65]:
pos_size = get_fx_position_size(data, init_cash=10_000, risk=0.02)
In [110]:
pf = vbt.Portfolio.from_signals( data, entries=sma_strat.long_entry, short_entries=sma_strat.short_entry, size=pos_size, size_type='amount' )
In [111]:
stats_df = pf.stats([ 'total_return', 'total_trades', 'win_rate', 'expectancy' ], agg_func=None) stats_df.groupby(('symbol'))['Expectancy'].idxmax()
Out[111]:
symbol AUDCAD (130, 30, AUDCAD) AUDCHF (140, 25, AUDCHF) AUDJPY (140, 25, AUDJPY) AUDNZD (140, 25, AUDNZD) AUDUSD (140, 15, AUDUSD) CADCHF (140, 25, CADCHF) CADJPY (140, 30, CADJPY) CHFJPY (110, 15, CHFJPY) EURAUD (110, 10, EURAUD) EURCAD (100, 30, EURCAD) EURCHF (110, 20, EURCHF) EURGBP (110, 15, EURGBP) EURJPY (110, 25, EURJPY) EURNZD (90, 20, EURNZD) EURUSD (140, 30, EURUSD) GBPAUD (60, 30, GBPAUD) GBPCAD (130, 30, GBPCAD) GBPCHF (120, 30, GBPCHF) GBPJPY (140, 25, GBPJPY) GBPNZD (110, 20, GBPNZD) GBPUSD (140, 30, GBPUSD) NZDCAD (120, 25, NZDCAD) NZDCHF (40, 25, NZDCHF) NZDJPY (120, 15, NZDJPY) NZDUSD (100, 30, NZDUSD) USDCAD (120, 30, USDCAD) USDCHF (110, 10, USDCHF) USDJPY (110, 30, USDJPY) Name: Expectancy, dtype: object
In [112]:
fig = pf.plot(settings=dict(bm_returns=True), column=(110, 10, 'USDCHF'), width=1890, height=900) fig.write_html('backtest.html', full_html=True) #fig.show()
Walk Forward Optimization: The Splitter¶
In [91]:
d_IS, d_OOS, _ = get_optimized_split(len(data.close.index), 0.75, 10) print(d_IS, d_OOS)
751 253
In [92]:
def split_func(splits, bounds, index, length_IS=20, length_OOS=30): if len(splits) == 0: new_split = (slice(0, length_IS), slice(length_IS, length_OOS+length_IS)) else: # Previous split, second set, right bound prev_end = bounds[-1][1][1] # Split Calculation new_split = ( slice(prev_end-length_IS, prev_end), slice(prev_end, prev_end + length_OOS) ) if new_split[-1].stop > len(index): return None return new_split
In [93]:
splitter = vbt.Splitter.from_split_func( data.close.index, split_func, split_args=( vbt.Rep("splits"), vbt.Rep("bounds"), vbt.Rep("index"), ), split_kwargs={ 'length_IS':d_IS, 'length_OOS':d_OOS }, set_labels=["IS", "OOS"] )
In [105]:
fig = splitter.plot() fig.show() #fig.write_html('wfo.html', full_html=False)
c:\Users\bjorn\anaconda3\envs\vectorbt-dev\lib\site-packages\jupyter_client\session.py:719: UserWarning: Message serialization failed with: Out of range float values are not JSON compliant Supporting this message is deprecated in jupyter-client 7, please make sure your message is JSON-compliant
Strategy Performance: Training Set¶
In [95]:
@vbt.parameterized(merg_func='concat') def perf(data, ind, pos_size, metric='sharpe_ratio'): pf = vbt.Portfolio.from_signals( data, entries=ind.long_entry, short_entries=ind.short_entry, size=pos_size, size_type='amount' ) result = getattr(pf, metric) return result
In [96]:
train_perf = splitter.apply( perf, vbt.Takeable(data), vbt.Takeable(sma_strat), vbt.Takeable(pos_size), metric='sharpe_ratio', _execute_kwargs=dict( show_progress=False, clear_cache=50, collect_garbage=50 ), set_='IS', merge_func='concat', execute_kwargs=dict(show_progress=True), )
0%| | 0/10 [00:00<?, ?it/s]
In [97]:
train_perf
Out[97]:
split strat_slow_period strat_fast_period symbol
0 40 10 AUDCAD -0.253708
AUDCHF -0.859335
AUDJPY -0.424655
AUDNZD -0.140546
AUDUSD -0.970369
...
9 140 30 NZDJPY 0.402278
NZDUSD -0.692106
USDCAD 0.096427
USDCHF -0.442222
USDJPY 0.465793
Name: sharpe_ratio, Length: 15400, dtype: float64
In [98]:
best = train_perf.groupby(['split', 'symbol']).idxmax() best[:] = [(i[1], i[2], i[3]) for i in best] best
Out[98]:
split symbol
0 AUDCAD (140, 25, AUDCAD)
AUDCHF (120, 10, AUDCHF)
AUDJPY (140, 20, AUDJPY)
AUDNZD (120, 15, AUDNZD)
AUDUSD (100, 10, AUDUSD)
...
9 NZDJPY (80, 25, NZDJPY)
NZDUSD (90, 30, NZDUSD)
USDCAD (110, 25, USDCAD)
USDCHF (90, 30, USDCHF)
USDJPY (140, 10, USDJPY)
Name: sharpe_ratio, Length: 280, dtype: object
In [99]:
optimized_long_entry = [] optimized_short_entry = [] for i in best.index.get_level_values('split').unique(): _opt_long = splitter['OOS'].take(sma_strat.long_entry)[i][best[i]].droplevel(['strat_slow_period', 'strat_fast_period'], axis=1) _opt_short = splitter['OOS'].take(sma_strat.short_entry)[i][best[i]].droplevel(['strat_slow_period', 'strat_fast_period'], axis=1) optimized_long_entry.append(_opt_long) optimized_short_entry.append(_opt_short)
In [100]:
optimized_long_entry = pd.concat(optimized_long_entry) optimized_short_entry = pd.concat(optimized_short_entry)
Walk Forward Optimization!¶
In [101]:
pf = vbt.Portfolio.from_signals( data, entries=optimized_long_entry, short_entries=optimized_short_entry, size=pos_size, size_type='amount', group_by=[0]*len(data.close.columns) )
In [102]:
fig = pf.plot(settings=dict(bm_returns=True)) fig.show()
c:\Users\bjorn\anaconda3\envs\vectorbt-dev\lib\site-packages\vectorbtpro\generic\plots_builder.py:398: UserWarning: Subplot 'orders' does not support grouped data c:\Users\bjorn\anaconda3\envs\vectorbt-dev\lib\site-packages\vectorbtpro\generic\plots_builder.py:398: UserWarning: Subplot 'trade_pnl' does not support grouped data
In [ ]: