Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Nested Backtesting Objects #896

KitHaywood started this conversation in General
Discussion options

Hi All,

I am trying to do a Multi-Year Backtest (MYB) - using minutely resolution time series data. I have a set of bespoke indicators whose behaviour triggers trading. These indicators are arranged as a 3d grid of shape (a x b x len(prices) stored as a dictionary: The indicators are based solely on autoregressive style maths - they only take into account the time-series data of the instrument.

{ ( a1 , b1 ) : vals@len(prices) , ( a1 , b2 ) : vals@len(prices) , ... }.

I preload these indicators as usual and then feed the (a,b) hyperparameter grid into the backtest optimiser as:

bt.optimize(
 a = range(1,10),
 b = range(1,10),
 maximize=maximiser,
 method="grid"
)

Within the strategy object I access the values but passing the (a,b) parameters as keys to the dictionary and use self.I to create the indicator(s). There is an eventuality where I will look at indicators crossing so in the example below I have included 2 for clarity. All of this is standard stuff.

HOWEVER - I need to add a layer of complexity to this which I can't figure out. In real life, I will be re-optimising my strategy parameters based on the current equity performance of the model. So midway through the MYB, I will need to make use of different (a,b) parameters, and not just find out which (a,b) parameters worked over that full-term period. This will happen at undetermined intervals based on the nature of the times-series data and the performance of the model. I need to nest backtests.

Again - in real-life - the MYB occurs over multiple years, but the strategy parameter selection done at outset (and at any point mid-way through) will occur over only a few days, maybe less - that's user defined. As far as I can see - at the outset I could bt.optimize on prices.iloc[:5000] and then bt.run on prices.iloc[5000:] using the results from the optimisation. But this does not allow for any mid-MYB re-optimisation.

MY THINKING SO FAR:

I can access the current equity of the model during 'run-time' via the protected broker._equity object (I patiently await your scorn), I can use that to assess whether or not to perform a re-optimisation, so the condition for mid-MYB re-optimisation is easy. However, I am stumped at how I go about actually re-optimising mid-way through. And then equally, after doing this how I pass the new (a,b) results from the mid-MYB bt.optimize back into the higher-leverl MYB strategy object.

class MyStrategy(Strategy):
 # precompute full indicator grid and initialise hyper-params
 inds: dict = CustomIndicator().indicators
 a1: int = 1
 b1: int = 10
 a2: int = 10
 b2: int = 1
 def init(self,):
 
 self.ind1 = self.I(lambda x: x, self.inds[(self.a1,self.b1)])
 self.ind2 = self.I(lambda x: x, self.inds[(self.a2,self.b2)])
 
 def next(self):
 if broker._equity < broker._cash_initial * (some_max_allowable_loss):
 inner_cash = self._cash
 # do I have to recreate a Strategy Class here?
 innerbt = Backtest(
 self._data[-5000:],
 ["WHAT GOES HERE AND HOW?"], # Should be a Strategy Object I think?
 cash=inner_cash,
 )
 new_result = bt.optimize(
 a = range(1,10),
 b = range(1,10),
 maximize=maximiser,
 method="grid"
 )
 # Can I update the currently working indicators of the MYB?
 self.ind1 = self.I(lambda x: x, self.inds[(new_result._strategy.a1,new_result._strategy.b1)]
 self.ind2 = self.I(lambda x: x, self.inds[(new_result._strategy.a2,new_result._strategy.b2)]
 if crossover(self.ind1 ,self.ind2):
 if not self.position:
 self.buy()
 elif self.position.is_short:
 self.position.close()
 self.buy()
 
 elif crossover(self.ind2 ,self.ind1):
 if not self.position:
 self.sell()
 elif self.position.is_long:
 self.position.close()
 self.sell()
 else:
 pass

MY QUESTION:

Is any of this possible? Am I looking at this in the right way? How would you go about this? How do I define an inner-strategy which draws some of its information from an outer-strategy? Do I need to define a new strategy class within the conditional?

Any pointer here would be very helpful!

Thanks in advance,

K

You must be logged in to vote

Replies: 1 comment

Comment options

I can access the current equity of the model during 'run-time' via the protected broker._equity object (I patiently await your scorn)

Underscored properties, other than keys on stats, are private and implementation-specific and may disappear/change from one framework version to the next without notice.

That out of the way, I hope you find helpful the following example. I certainly think you need two separate strategy classes.

initial_cash = 1_000_000
class ABCrossStrat(Strategy):
 a: int = 1
 b: int = 10
 def next(self):
 if crossover(...):
 ...
 ...
class MYBStrat(Strategy):
 def next(self):
 if self.equity < initial_cash * some_max_allowable_loss:
 inner_bt = Backtest(self.data[-5000:], ABCrossStrat)
 stats = inner_bt.optimize(...)
 
 # Do further investing according to best ABCrossStrat
 self.a, self.b = stats._strategy.a, stats._strategy.b
 # Calling by reference here to avoid code dup
 ABCrossStrat.next(self)
stats = Backtest(df, MYBStrat, cash=initial_cash)
You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants

AltStyle によって変換されたページ (->オリジナル) /