Interpreting optimization results when using multiple strategies
-
Hello gentlemen,
I would like to integrate Bokeh plotting for optimization results but I have a problem interpreting optimization results when using multiple strategies.
Let's say I have this test code:
cerebro.optstrategy(TestStrategy, buydate=range(14, 16, 1)) cerebro.optstrategy(TestStrategy2, buydate=range(11, 13, 1)) cerebro.addanalyzer(bt.analyzers.TradeAnalyzer) data = ... cerebro.adddata(data) res = cerebro.run()
Now I have a list of 4 entries in the variable
res
(all possible strategy combinations), which is fine for me. Each of these entities contain a list 2OptReturn
objects. One for each strategy involved.
But how can I know which of theseOptReturn
objects belongs to which of the two strategies? I mean: I have 2OptReturn
objects and I have 2 strategies. They only contain the set of optimization parameters and a list of analyzers.Thank you!
-
They are returned in the same order in which they were inserted. You can also deactivate the return of the
OptReturn
objects and get the strategies by settingoptreturn=False
-
@backtrader
Thanks for quick answer.So I would need to have access to strategy list which means I need a reference to the
cerebro
object to be able to process theOptReturn
objects programmatically, right?As far as I can see the plotting interface (which is called when trying to plot the
OptReturn
) does not provide a reference to the thecerebro
object in the call:
https://github.com/backtrader/backtrader/blob/development/backtrader/plot/plot.py#L116So my idea would be to pass a reference to the
cerebro
object (or maybe just to the strategy list) in the constructor call to myBokeh
plotting object (only needed when planning to plot optimization results withoptreturn=True
). Then when plottingOptReturns
thecerebro
can be used to find the correspondingStrategy
object and add, for example, the name of the strategy.Does this sound reasonable or are there maybe better approaches to get the corresponding strategies?
EDIT:
Hm, another problem:
Theplot
function is called for everyOptReturn
object seperately, so I don't know the current index in the list which I need to access theStrategy
list... hm... -
@vbs said in Interpreting optimization results when using multiple strategies:
So I would need to have access to strategy list
No. Your best bet is to use
optreturn=False
and getStrategy
instances returned instead ofOptReturn
objects.@vbs said in Interpreting optimization results when using multiple strategies:
The plot function is called for every OptReturn object seperately, so I don't know the current index in the list which I need to access the Strategy list... hm..
This because if you are optimizing you can end up with hundreds or thousands of Strategy instances. Plotting was deemed to be meaningless. Hence the optimization in the returned values.
-
I agree that plotting is not useful when having a certain amount of outputs. I just thought it would make sense to implement it since
cerebro
actually is trying to plot optimization results. However the integrated plotter seems to yield an exception when trying to plot opt results. -
End users are (sometimes) also responsible for their actions, i.e.: trying to plot an optimization run.
-
Sorry for coming back to this but I have a problem with this again:
I was able to get around my previous problem by getting the index of the currentoptresult
and to use that index to accesscerebro.runningstrats
like this:def _get_strategy(self, opt_result): """Finds and returns the strategy the opt result is based upon""" optidx = self._find_optreturn_idx(opt_result) return self._cerebro.runningstrats[optidx]
This worked fine so far but it does not work anymore when using
maxcpu
with a value greater than1
. In that case thecerebro
object does not contain the fieldrunningstrats
. I guess this field is only created in the subprocesses but is not available then in the parent process.So currently I am not able to find the corresponding
strategy
(orstrategy class
) that belongs to anoptresult
object. Is thestrategy
object available somewhere else?
Maybe it would be possible to changebacktrader
so that a reference to the correspondingstrategy
is added to everyoptresult
?
I am aware that full data is returned when usingoptresult=False
. But I am afraid this would need alot of RAM when dealing with a huge number of results. -
@vbs said in Interpreting optimization results when using multiple strategies:
I guess this field is only created in the subprocesses but is not available then in the parent process.
Because the parent process is only responsible for distributing the work to subprocesses.
@vbs said in Interpreting optimization results when using multiple strategies:
Maybe it would be possible to change backtrader so that a reference to the corresponding strategy is added to every optresult?
If you add a reference to the
Strategy
you automatically pull in the data feeds, indicators and associated Observers, because they are referenced in theStrategy
, in which case you have artificially recreatedoptreturn=False
-
If you add a reference to the
Strategy
you automatically pull in the data feeds, indicators and associated Observers, because they are referenced in theStrategy
, in which case you have artificially recreatedoptreturn=False
How about not adding the
strategy
itself but a reference to the correspondingstrategy class
? That would help alot and should not pull in too much data? -
As a proposal, it could be as simple as this?
cerebro.py
, line 1314if self._dooptimize and self.p.optreturn: # Results can be optimized results = list() for strat in runstrats: for a in strat.analyzers: a.strategy = None a._parent = None for attrname in dir(a): if attrname.startswith('data'): setattr(a, attrname, None) oreturn = OptReturn(strat.params, analyzers=strat.analyzers, strategycls=type(strat)) results.append(oreturn)
Only added
, strategycls=type(strat)
-
I see where you want to go, but you can have the same Strategy class in the same optimization mix several times with different parameters. You could argue that you won't, but the solution has to be generic.
Solving the conumdrum should not add even more obscurity to the mix.
-
@backtrader
I totally agree that a non-generic solution that introduces limiting assumptions is bad and should not be added.But I have a problem to understand your concerns in this case. The code iterates over
runstrats
, so in every iterationstrat
contains exactly one strategy object, right?Then this line:
oreturn = OptReturn(strat.params, analyzers=strat.analyzers, strategycls=type(strat))
Builds an
OptReturn
object and captures the current strategy's parameters and it's analyzers. Now in addition it also captures the class of the current strategy.
So in the end, in the variableresults
there will be a list ofOptReturn
objects and every one of them carries the corresponding strategy class.
I mean it also carries a parameter object taken from a strategy object so the strategy class it belongs to has to be distinct anyway, no?I hope I don't suffer from a bigger misconeption on my side here... :/
-
The thread has probably gone too long and you seem to want something different now that you wanted at the beginning. Let's try to recapitatulate:
-
You wanted a reference to the
Strategy
which had been run.But Python doesn't give references ... it gives you the entire object so to say, which would pull-in all other referenced objects like data feeds (which is what one tries to avoid with
OptReturn
instances -
Now you want a reference to the
Strategy
classBut this would still not give you the actual instance which was run, even if
OptReturn
instances contain theStrategy
class.And you may have several
OptReturn
instances with the sameStrategy
class, which is by itself the problem when trying to recognize which strategy was run.
I may be missing something about your intent. Just let me know.
-
-
Ok you are right. I will try to re-explain what I have in mind and I will try to bring it more on-point.
The background this:
I am thinking about (and also coding) helper functions that are able to process cerebro results in different ways. Use cases are displaying, plotting, archiving, filtering etc.One possible type of result you can get out of cerebro is when you run an optimization using
optreturn=True
. So it would be desirable (imo) to be able to process this type of result programmatically in a generic way. This means you have to be able to get all relevant information out of the result itself (or at least in conjunction with the usedcerebro
). In my opinion this is currently not the case.To be sure we are on the same page I prepared a minimal example:
class GreatStrategy(bt.Strategy): params = ( ('buydate', 2), ) def next(self): pass class BestStrategy(bt.Strategy): params = ( ('period', 4), ) def next(self): pass if __name__ == '__main__': cerebro = bt.Cerebro(maxcpus=2, optreturn=True) cerebro.optstrategy(GreatStrategy, buydate=range(2, 4, 1)) cerebro.optstrategy(BestStrategy, period=range(4, 6, 1)) data = bt.feeds.YahooFinanceCSVData(dataname="datas/orcl-1995-2014.txt", fromdate=datetime.datetime(2000, 1, 1), todate=datetime.datetime(2001, 2, 28)) cerebro.adddata(data) cerebro.addanalyzer(bt.analyzers.TradeAnalyzer) res = cerebro.run()
So there are 2 strategies involved. Both target of optimization. They have two different sets of parameters. Both only containing a single parameter.
Running this gives you list of 4 results in
res
. The first looks like this (removed fieldp
cause duplication ofparams
):0 = {list} <class 'list'>: [<backtrader.cerebro.OptReturn object at 0x00000000058235F8>, <backtrader.cerebro.OptReturn object at 0x0000000005823EB8>] 0 = {OptReturn} <backtrader.cerebro.OptReturn object at 0x00000000058235F8> analyzers = {ItemCollection} <backtrader.metabase.ItemCollection object at 0x0000000005823908> params = {AutoInfoClass_LineRoot_LineMultiple_LineSeries_LineIterator_DataAccessor_StrategyBase_Strategy_GreatStrategy} <backtrader.metabase.AutoInfoClass_LineRoot_LineMultiple_LineSeries_LineIterator_DataAccessor_StrategyBase_Strategy_GreatStrategy object at 0x00000000058235C0> 1 = {OptReturn} <backtrader.cerebro.OptReturn object at 0x0000000005823EB8> analyzers = {ItemCollection} <backtrader.metabase.ItemCollection object at 0x0000000005823F60> params = {AutoInfoClass_LineRoot_LineMultiple_LineSeries_LineIterator_DataAccessor_StrategyBase_Strategy_BestStrategy} <backtrader.metabase.AutoInfoClass_LineRoot_LineMultiple_LineSeries_LineIterator_DataAccessor_StrategyBase_Strategy_BestStrategy object at 0x0000000005823EF0>
In my understanding one
OptReturn
object is the result of one distinct run with one distinct strategy object involved. The contained fieldparams
describes the "configuration" of the used strategy.
But my problem is that you only have theparams
object (e.g. the configuration) but you do not know to which strategy class this parameters belong to. So how to know ifres[0][0]
is the result ofGreatStrategy
or ofBestStrategy
?In other words: a strategy instance can be described by a combination two things:
- A strategy class
- A parameter object describing the "configuration" of the strategy
But currently the
OptReturn
object only carries the parameter part.I had a workaround that involved grabbing the strategy class from the used
cerebro
. But that does not work when usingmaxcpu>1
because then thecerebro
does not has the fieldrunningstrats
.So my proposal is to add a field
strategycls
toOptReturn
that carries the used strategy class that belongs to thatOptReturn
object. In my understanding there is a 1:1 relation between anOptReturn
object and a strategy class. -
I am using this approach since a while now and I have to say that I am quite happy with it:
https://github.com/verybadsoldier/backtrader/commit/f03a0ed115338ed8f074a942f6520b31c630bcfbMakes it possible for me to evaluate
OptReturn
objects without the need of any further input (like acerebro
object).