Files
David Brazda e3da60c647 daily update
2024-10-21 20:57:56 +02:00

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 [ ]: