Optimizer for simple MA crypto strategy
-
hi!
Can you help in running the optimizer for this simple strategy?
import qnt.data as qndata import qnt.ta as qnta import qnt.output as qnout import qnt.stats as qns import qnt.log as qnlog import qnt.optimizer as qnop import qnt.backtester as qnbt import xarray as xr def single_pass_strategy(data, ma_slow_param=50, ma_fast_param=10): close= data.sel(field="close") ma_slow= qnta.lwma(close, ma_slow_param).isel(time=-1) ma_fast= qnta.lwma(close, ma_fast_param).isel(time=-1) return xr.where(ma_fast > ma_slow, 1, -1) def load_data(period): """Loads the BTC Futures data for the BTC Futures contest""" return qndata.cryptofutures.load_data(tail=period, dims=("time","field","asset")) data = qndata.cryptofutures.load_data(min_date='2014-01-01') result = qnop.optimize_strategy( data, single_pass_strategy, qnop.full_range_args_generator( ma_slow_param=range(10, 150, 5), # min, max, step ma_fast_param=range(5, 100, 5) # min, max, step ), workers=1 # you can set more workers on your PC ) qnop.build_plot(result) print("---") print("Best iteration:") display(result['best_iteration'])
-
@magenta-grimer Your strategy selects only the last MA values but you need them all to get full backtest results (single pass). By changing these lines
ma_slow= qnta.lwma(close, ma_slow_param)#.isel(time=-1) ma_fast= qnta.lwma(close, ma_fast_param)#.isel(time=-1)
I get
---
Best iteration:{'args': {'ma_slow_param': 125, 'ma_fast_param': 10},
'result': {'equity': 149.87344883417938,
'relative_return': -0.07047308274265918,
'volatility': 0.6090118757480503,
'underwater': -0.07047308274265918,
'max_drawdown': -0.7355866115241121,
'sharpe_ratio': 0.9776088443081092,
'mean_return': 0.5953753960199653,
'bias': -1.0,
'instruments': 1.0,
'avg_turnover': 0.06404024691932551,
'avg_holding_time': 72.70270270270271},
'weight': 0.9776088443081092,
'exception': None} -
@magenta-grimer hi, answer by @antinomy is correct.
Sorry for the confusion, we need a balance between fast execution (single pass) and safe one (multi pass).
-
There is a way to use the optimizer with a (stateful) mulit pass algo, but depending on the total number of changed parameters it can take a very long time. However, if it runs on a local computer with many workers this can still be useful.
We could run the backtester with the multi pass algo to get all the weights for the test period and pass these weights to the optimizer.
There's just one problem with this: you can't pass changed parameters to the strategy using the backtester.
In order to solve this I created a nested function where the outer function takes the changed parameters from the optimizer. The inner function is the actual multi pass strategy and doesn't define the params but just uses the ones from the outer function. Still within the outer function we run the backtester with one set of params, get the weights it returns and return them to the optimizer.The time it takes to run the optimization would roughly be
(time for 1 multi pass backtest) x (total number of parameter changes) / (number of workers that are able to run)
So if one multi pass takes 1 minute, you want to optimize 10 parameter changes and can run 5 workers it would take about 2 minutes.Here's an example based on the one above with 2 parameter changes and 2 workers:
import qnt.data as qndata import qnt.ta as qnta import qnt.optimizer as qnop import qnt.backtester as qnbt import xarray as xr def load_data(period): """Loads the BTC Futures data for the BTC Futures contest""" return qndata.cryptofutures.load_data(tail=period, dims=("time", "field", "asset")) def multi_pass_strategy(data, ma_slow_param=50, ma_fast_param=10): """The outer function gets called by the optimizer with changed params, the inner function gets passed to the backtester.""" def strategy(data, state): # The state isn't used in this example, this is just to show that it can be used while optimizing. if state is None: state = 0 state += 1 close = data.sel(field="close") ma_slow = qnta.lwma(close, ma_slow_param).isel(time=-1) ma_fast = qnta.lwma(close, ma_fast_param).isel(time=-1) weights = xr.zeros_like(close.isel(time=-1)) weights[:] = 1 if ma_fast > ma_slow else -1 return weights, state """The backtester returns all weights for the test period which will then be returned to the optimizer""" weights, state = qnbt.backtest( strategy=strategy, competition_type="cryptofutures", load_data=load_data, lookback_period=700, start_date='2014-01-01', build_plots=False, ) return weights data = qndata.cryptofutures.load_data(min_date='2014-01-01') result = qnop.optimize_strategy( data, multi_pass_strategy, qnop.full_range_args_generator( ma_slow_param=range(50, 60, 5), # min, max, step # ma_fast_param=range(5, 100, 5) # min, max, step ), workers=2 # you can set more workers on your PC ) print("---") print("Best iteration:") print(result['best_iteration']) qnop.build_plot(result)
There might be more efficient ways to do this, so if anyone has one feel free to post it here.