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

TradeAnalyzer appears to be incorrect



  • Hi,

    I am having an issue with the TradeAnalyzer. I have added a TradeAnalyzer to one of the examples on the backtrader website (more specifically, to the 'Visual Inspection: Plotting' example from https://www.backtrader.com/docu/quickstart/quickstart.html#our-first-strategy).

    To add the TradeAnalyzer, I have insert code snippets from https://backtest-rookies.com/2017/06/11/using-analyzers-backtrader/.

    I have also commented some of the log calls, to make the example more clear.

    The resulting code is as follows:

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    
    import datetime  # For datetime objects
    import os.path  # To manage paths
    import sys  # To find out the script name (in argv[0])
    
    # Import the backtrader platform
    import backtrader as bt
    
    
    # Create a Stratey
    class TestStrategy(bt.Strategy):
        params = (
            ('maperiod', 15),
        )
    
        def log(self, txt, dt=None):
            ''' Logging function fot this strategy'''
            dt = dt or self.datas[0].datetime.date(0)
            print('%s, %s' % (dt.isoformat(), txt))
    
        def __init__(self):
            # Keep a reference to the "close" line in the data[0] dataseries
            self.dataclose = self.datas[0].close
    
            # To keep track of pending orders and buy price/commission
            self.order = None
            self.buyprice = None
            self.buycomm = None
    
            # Add a MovingAverageSimple indicator
            self.sma = bt.indicators.SimpleMovingAverage(
                self.datas[0], period=self.params.maperiod)
    
            # Indicators for the plotting show
            bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
            bt.indicators.WeightedMovingAverage(self.datas[0], period=25,
                                                subplot=True)
            bt.indicators.StochasticSlow(self.datas[0])
            bt.indicators.MACDHisto(self.datas[0])
            rsi = bt.indicators.RSI(self.datas[0])
            bt.indicators.SmoothedMovingAverage(rsi, period=10)
            bt.indicators.ATR(self.datas[0], plot=False)
    
        def notify_order(self, order):
            if order.status in [order.Submitted, order.Accepted]:
                # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                return
    
            # Check if an order has been completed
            # Attention: broker could reject order if not enough cash
            if order.status in [order.Completed]:
                if order.isbuy():
                    #self.log(
                    #    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    #    (order.executed.price,
                    #     order.executed.value,
                    #     order.executed.comm))
    
                    self.buyprice = order.executed.price
                    self.buycomm = order.executed.comm
                # else:  # Sell
                    #self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    #         (order.executed.price,
                    #          order.executed.value,
                    #          order.executed.comm))
    
                self.bar_executed = len(self)
    
            elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                self.log('Order Canceled/Margin/Rejected')
    
            # Write down: no pending order
            self.order = None
    
        def notify_trade(self, trade):
            if not trade.isclosed:
                return
    
            self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
    
        def next(self):
            # Simply log the closing price of the series from the reference
            #self.log('Close, %.2f' % self.dataclose[0])
    
            # Check if an order is pending ... if yes, we cannot send a 2nd one
            if self.order:
                return
    
            # Check if we are in the market
            if not self.position:
    
                # Not yet ... we MIGHT BUY if ...
                if self.dataclose[0] > self.sma[0]:
    
                    # BUY, BUY, BUY!!! (with all possible default parameters)
                    # self.log('BUY CREATE, %.2f' % self.dataclose[0])
    
                    # Keep track of the created order to avoid a 2nd order
                    self.order = self.buy()
    
            else:
    
                if self.dataclose[0] < self.sma[0]:
                    # SELL, SELL, SELL!!! (with all possible default parameters)
                    # self.log('SELL CREATE, %.2f' % self.dataclose[0])
    
                    # Keep track of the created order to avoid a 2nd order
                    self.order = self.sell()
    
    def printTradeAnalysis(analyzer):
        '''
        Function to print the Technical Analysis results in a nice format.
        '''
        #Get the results we are interested in
        total_open = analyzer.total.open
        total_closed = analyzer.total.closed
        total_won = analyzer.won.total
        total_lost = analyzer.lost.total
        win_streak = analyzer.streak.won.longest
        lose_streak = analyzer.streak.lost.longest
        pnl_net = round(analyzer.pnl.net.total,2)
        strike_rate = (total_won / total_closed) * 100
        #Designate the rows
        h1 = ['Total Open', 'Total Closed', 'Total Won', 'Total Lost']
        h2 = ['Strike Rate','Win Streak', 'Losing Streak', 'PnL Net']
        r1 = [total_open, total_closed, total_won, total_lost]
        r2 = [strike_rate, win_streak, lose_streak, pnl_net]
        #Check which set of headers is the longest.
        if len(h1) > len(h2):
            header_length = len(h1)
        else:
            header_length = len(h2)
        #Print the rows
        print_list = [h1,r1,h2,r2]
        row_format ="{:<15}" * (header_length + 1)
        print("Trade Analysis Results:")
        for row in print_list:
            print(row_format.format('',*row))
    
    if __name__ == '__main__':
        # Create a cerebro entity
        cerebro = bt.Cerebro()
    
        # Add a strategy
        cerebro.addstrategy(TestStrategy)
    
        # Datas are in a subfolder of the samples. Need to find where the script is
        # because it could have been called from anywhere
        modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
        datapath = os.path.join(modpath, './datas/orcl-1995-2014.txt')
    
        # Create a Data Feed
        data = bt.feeds.YahooFinanceCSVData(
            dataname=datapath,
            # Do not pass values before this date
            fromdate=datetime.datetime(2000, 1, 1),
            # Do not pass values before this date
            todate=datetime.datetime(2000, 12, 31),
            # Do not pass values after this date
            reverse=False)
    
        # Add the Data Feed to Cerebro
        cerebro.adddata(data)
    
        # Set our desired cash start
        cerebro.broker.setcash(1000.0)
    
        # Add a FixedSize sizer according to the stake
        cerebro.addsizer(bt.sizers.FixedSize, stake=10)
    
        # Set the commission
        cerebro.broker.setcommission(commission=0.0)
    
        # Print out the starting conditions
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
        # Add the analyzers we are interested in
        cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="ta")
    
        # Run over everything
        strategies = cerebro.run()
        firstStrat = strategies[0]
    
        # print the analyzers
        printTradeAnalysis(firstStrat.analyzers.ta.get_analysis())
    
        # Print out the final result
        print('Ending Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
    

    This produces the following result:

    Starting Portfolio Value: 1000.00
    2000-03-31, OPERATION PROFIT, GROSS 100.00, NET 100.00
    2000-04-12, OPERATION PROFIT, GROSS -28.70, NET -28.70
    2000-04-20, OPERATION PROFIT, GROSS -24.00, NET -24.00
    2000-05-05, OPERATION PROFIT, GROSS -22.50, NET -22.50
    2000-05-09, OPERATION PROFIT, GROSS -8.20, NET -8.20
    2000-05-19, OPERATION PROFIT, GROSS -28.10, NET -28.10
    2000-06-23, OPERATION PROFIT, GROSS 37.90, NET 37.90
    2000-06-28, OPERATION PROFIT, GROSS -1.60, NET -1.60
    2000-06-30, OPERATION PROFIT, GROSS -8.40, NET -8.40
    2000-07-05, OPERATION PROFIT, GROSS -21.50, NET -21.50
    2000-07-24, OPERATION PROFIT, GROSS -1.60, NET -1.60
    2000-07-28, OPERATION PROFIT, GROSS 1.50, NET 1.50
    2000-08-02, OPERATION PROFIT, GROSS -10.90, NET -10.90
    2000-09-11, OPERATION PROFIT, GROSS 38.70, NET 38.70
    2000-10-02, OPERATION PROFIT, GROSS -8.00, NET -8.00
    2000-10-31, OPERATION PROFIT, GROSS -35.00, NET -35.00
    2000-11-21, OPERATION PROFIT, GROSS 5.00, NET 5.00
    2000-12-15, OPERATION PROFIT, GROSS 30.60, NET 30.60
    2000-12-21, OPERATION PROFIT, GROSS -21.90, NET -21.90
    Trade Analysis Results:
                   Total Open     Total Closed   Total Won      Total Lost     
                   1                       19           6              6              
                   Strike Rate    Win Streak     Losing Streak  PnL Net        
                   31.57894736842105 2              2              -6.7           
    Ending Portfolio Value: 980.10
    

    As can be seen in the output, the TradeAnalyzer is correct in the total number of closed trades (19), the total number of winning trades (6), and the winning streak (2). However, the number of lost trades, the losing streak, and the PnL Net appear to be incorrect.

    Can anyone explain what I am doing wrong or what is going on?

    Many thanks in advance.

    Ad


  • administrators

    This is a very recent error introduced with this commit: https://github.com/backtrader/backtrader/commit/8f537a1c2c271eb5cfc592b373697732597d26d6

    In attempting to fix the bool problem if you only had 1 trade the proper not to identify lost trades was lost.

    This is now fixed in this push to the development branch: https://github.com/backtrader/backtrader/commit/cc2751a5f53166f68c5340eb876579f1a5590bf5



  • Thanks, this fixes the problem :)



  • Hi @advandenoord @backtrader
    I have updated the traderanalyzer.py as in branch: https://github.com/backtrader/backtrader/commit/cc2751a5f53166f68c5340eb876579f1a5590bf5
    but now when I run my backtrader, Python returns this:

      File "D:\Anaconda3\lib\site-packages\backtrader\analyzers\tradeanalyzer.py", line 73, in stop
        super(TradeAnalyzer, self).stop()
    
    TypeError: super(type, obj): obj must be an instance or subtype of type
    

    Do you guys know why this happens? How should I fix it? Thanks!


  • administrators

    Do you care let us know how you use it?. The tests work and it also works for @advandenoord. And the change introduced has nothing to do with the error you report.



  • @backtrader Hi, I changed traderanalyzer.py file script as instructed on Github and that is when it went wrong, then I downloaded a traderanalyzer.py directly from Github, reinstalled Backtrader and copied and pasted the traderanalyzer.py into the backtrader module, it is working now. Though I couldn't figure out the reason for that error to occur.

    By the way, would you mind taking a look at this question https://community.backtrader.com/topic/1303/how-do-i-set-up-correct-position-close-logic/4 ?
    I have been scratching my heads over how to use self.close() order to close only long or short positions when I have both existing long and short positions. It is a very important question for me, and many thanks if you can clarify it for me cos I didn't find a page on the website explaining how it works.

    Have a good weekend.


  • administrators

    @bernardlin said in TradeAnalyzer appears to be incorrect:

    Hi, I changed traderanalyzer.py file script as instructed on Github

    I fear you didn't simply use the development branch. There is no reason to manually change/download a single file.