For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Qs about behavior of once_via_next and runonce



  • Hi, new user here trying to better understand the control flow of indicators when runonce=True

    Consider a simple indicator that just copies a signal using a next method:

    class Copy(bt.Indicator):
        lines = ('copy',)
        
        def __init__(self):
            self.x = self.datas[0]
        def next(self):
            self.lines.copy[0] = self.x[0]
    

    If I use this copy indicator on some indicator with a non-zero period and run cerebro with runonce=True , the ouput of the indicator becomes all nans.

    class TestStrategy(bt.Strategy):
    
        def __init__(self):
            self.signal = bt.ind.PercentChange(
                self.datas[0], period=1)
            self.signal_copy = Copy(self.signal)
    
    cerebro = bt.Cerebro(runonce=True)
    cerebro.adddata(data)
    cerebro.addstrategy(TestStrategy)
    
    strats_out = cerebro.run()
    strat = strats_out[0]
    
    # Id expect these two to be the same
    strat.signal.array[:10]
    strat.signal_copy.array[:10]
    

    (However, with run_once=False, the results are as expected.)

    After reading https://community.backtrader.com/topic/242/is-backtrader-event-driven-or-vectorized/2 and realizing there is a once_via_next method in Indicator, I thought that even if run_once=True, the signal would be copied batch-wise after the nested signal finishes evaluating.
    (Q1) Why is this copy indicator using next evaluating to all nans when run_once=True. It feels like maybe dependencies in nested indicators are not resolved as I'd hoped(?).
    I thought it might have to do with _minperiod not being carried over properly, but it seems _minperiod for both the signal and signal_copy are both 2 as expected.

    So then I thought, OK, let me override the once method instead of relying on once_via_next like so:

    class Copy(bt.Indicator):
        lines = ('copy',)
        
        def __init__(self):
            self.x = self.datas[0]
        
        def next(self):
            self.lines.copy[0] = self.x[0]
            
        def once(self, start, end):
            print(f'running once: {start},{end}')
            for i in range(start, end):
                self.lines.copy.array[i] = self.x[i]
    

    And this works... kind of. The signal_copy now has values from signal; however, the signal is shifted by 1. Where signal begins with 1 nan, signal_copy begins with 2. I have a debug statement that prints the start and end arguments outputting:

    running once: 1,2
    running once: 2,1000
    

    This hints at why signal_copy has 2 nans, but (Q2) I'm wondering why once is ran before the minimum period. In the docs I see that:
    once(self, start, end)
    Called when the minimum period has been met. The internal array must be processed between start and end which are zero based from the start of the internal array

    And writing the copy indicator using the __init__ method works as expected...

    class Copy(bt.Indicator):
        lines = ('copy',)
        
        def __init__(self):
            self.x = self.datas[0]
            self.lines.copy = self.x
    

    but of course, I'm actually trying to write something more complex that requires the usage of next.

    Any hints would be much appreciated!



  • Sorry, meant to post this in general-code-help



  • Looks like if you want to run your indicator in the vectorized mode, you need __init__() and once() methods to be present in the indicator. next() is not processed in the vectorized mode.


Log in to reply
 

});