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

Question on making an observer to track individual instrument values



  • Hello, I am learning backtrader and really like it. I want to make an observer to track the values of individual instrument in the portfolio, say 5 different futures. I am thinking to have one line for each instrument, but how should I define the lines beforehand since the number of instruments could be different?

    I look at the examples especially the trades observer, but it seems like they all have a fixed number of lines defined. The trades observer has pnlplus/pnlminus lines defined, and use different markers to track individual instrument.

    Am I missing something obvious here? Can I initialize the lines tuple with number of instruments as parameter? Sorry for the simple question, I am still new to python. Thank you.



  • Try starting your journey with this analyzer, PositionsValue, which works over any number of datas and returns the position value at each bar along with the cash position I believe.



  • Thank you for the pointer. The PositionsValue analyzer does what I want. May I ask a further question? I know I can plot the result of this analyzer, but is it possible to make it an observer just like the Datatrades observer? This way it can be plotted directly using cerebro.plot() or Bokeh.

    My main issue is how to define class variable "lines" dynamically, since its numbers/names depends on how many instruments in the datas. I see in the datatrades observer, it is produced by the metaclass MetaDataTrades, and the lines are created there dynamically. I want to follow the same pattern. I am trying to single-step into DataTrades and see what's being created at runtime. But somehow even if I set all the breakpoints in DataTrades (using VScode), and added the observer into cerebro, they never get triggered. The observer is generating correct plots, so it is working for sure. The debug mode works fine in other parts of code, like single-step in strategy/indicators. I guess I must be missing something here regarding how the datatrades class is being generated.

    class MetaDataTrades(Observer.__class__):
        def donew(cls, *args, **kwargs):
            _obj, args, kwargs = super(MetaDataTrades, cls).donew(*args, **kwargs)
    
            # Recreate the lines dynamically
            if _obj.params.usenames:
                lnames = tuple(x._name for x in _obj.datas)
            else:
                lnames = tuple('data{}'.format(x) for x in range(len(_obj.datas)))
    
            # Generate a new lines class
            linescls = cls.lines._derive(uuid.uuid4().hex, lnames, 0, ())
    
            # Instantiate lines
            _obj.lines = linescls()
    ......
    class DataTrades(with_metaclass(MetaDataTrades, Observer)):
    


  • ok, just figured it out. I can reuse the DataTrades observer. No change needed for the MetaDataTrades class. It will generate multiple lines for the assets. The author sure constructed backtrader to be versatile!

    Here is my code to show pnl in individual asset. Just accumulate daily settled pnl for each asset. Haven't checked the exact numbers yet. Eyeballing it looks alright comparing to the total account pnl.

    class Aseet_monitor(bt.utils.py3.with_metaclass(MetaDataTrades, bt.observer.Observer)):
        _stclock = True
    
        params = (('usenames', True),)
    
        plotinfo = dict(plot=True, subplot=True, plothlines=[0.0],
                        plotymargin=0.10, plotlinelabels=True)
    
        plotlines = dict()
    
        def next(self):
            strat = self._owner
            for inst in strat.datas:
                pos = strat.broker.positions[inst]
                cur_line = getattr(self.lines, inst._name)
                comminfo = strat.broker.getcommissioninfo(inst)
                if len(self) == 1:
                    cur_line[0] = 0
                else:
                    if pos.size != 0:
                        cur_pnl = comminfo.profitandloss(pos.size, inst.close[-1],
                                                            inst.close[0])                
                    else:
                        cur_pnl = 0
                
                    cur_line[0] = cur_line[-1] + cur_pnl
    

    Here is the output. The whole strategy is carried by HG_copper, while CL and ZN are underwater almost the whole time.

    27d7e3a1-67bf-4af6-8bad-4f74d5f11155-image.png

    Comparing to the Value observer.
    83d6a6fd-66c5-489a-b0ab-a1ffd3320da7-image.png


Log in to reply
 

});