Example #1
0
class DrawDown(bt.Analyzer):
    '''This analyzer calculates trading system drawdowns stats such as drawdown
    values in %s and in dollars, max drawdown in %s and in dollars, drawdown
    length and drawdown max length

    Params:

      - *None*

    Methods:

      - ``get_analysis``

        Returns a dictionary (with . notation support and subdctionaries) with
        drawdown stats as values, the following keys/attributes are available:

        - ``drawdown`` - drawdown value in 0.xx %
        - ``moneydown`` - drawdown value in monetary units
        - ``len`` - drawdown length

        - ``max.drawdown`` - max drawdown value in 0.xx %
        - ``max.moneydown`` - max drawdown value in monetary units
        - ``max.len`` - max drawdown length
    '''

    def create_analysis(self):
        self.rets = AutoOrderedDict()  # dict with . notation

        self.rets.len = 0
        self.rets.drawdown = 0.0
        self.rets.moneydown = 0.0

        self.rets.max.len = 0.0
        self.rets.max.drawdown = 0.0
        self.rets.max.moneydown = 0.0

        self._maxvalue = float('-inf')  # any value will outdo it

    def stop(self):
        self.rets._close()  # . notation cannot create more keys

    def notify_cashvalue(self, cash, value):
        self._value = value  # record current value
        self._maxvalue = max(self._maxvalue, value)  # update peak value

    def next(self):
        r = self.rets

        # calculate current drawdown values
        r.moneydown = moneydown = self._maxvalue - self._value
        r.drawdown = drawdown = 100.0 * moneydown / self._maxvalue

        # maxximum drawdown values
        r.max.moneydown = max(r.max.moneydown, moneydown)
        r.max.drawdown = maxdrawdown = max(r.max.drawdown, drawdown)

        r.len = r.len + 1 if drawdown else 0
        r.max.len = max(r.max.len, r.len)
Example #2
0
    def create_analysis(self):
        #print('C**T TRADE RECORDER')
        # Keep dict of trades based on tradeid
        # Note: must be a unique tradeid defined for each trade else will
        # get overwritten..
        self.rets = AutoOrderedDict()
        self.rets.openTrades = []  # List of all open trades..
        self.rets.closedTrades = []  # List of all closed trades..

        # Hidden from user, destroyed at end, useful to track trades..
        self._tradeDict = {}  # Temp store Trade objects..
Example #3
0
    def create_analysis(self):
        self.rets = AutoOrderedDict()  # dict with . notation

        self.rets.len = 0
        self.rets.drawdown = 0.0
        self.rets.moneydown = 0.0

        self.rets.max.len = 0.0
        self.rets.max.drawdown = 0.0
        self.rets.max.moneydown = 0.0

        self._maxvalue = float('-inf')  # any value will outdo it
Example #4
0
    def generate_strategy_params(self, strategy, optimize=False):
        if strategy == bt.strategies.STADTDBreakoutStrategy:
            for args in self._generate_STADTDB(optimize=optimize):
                yield args

        else:
            yield AutoOrderedDict()
    def create_analysis(self):
        # Keep dict of trades based on tradeid.
        # Note: must be a unique tradeid defined for each trade else will
        # get overwritten.
        self.rets = AutoOrderedDict()
        self.rets.openTrades = []  # List of all open trades..
        self.rets.closedTrades = []  # List of all closed trades..
        self.rets.equityCurve = []  # List of dictonary

        # Hidden from user, destroyed at end, useful to track trades.
        self._tradeDict = {}  # Temp store Trade objects..
        self._cumulativeEquity = None  # Keep track of our equity..

        # Check user parameters are valid - if not raise Exception.
        if self.p.mode not in ['trades', 'equity', 'trades+equity']:
            raise Exception(
                "TradeRecorder Analyzer must have parameter " +
                "'mode' set to either 'trades', 'equity' or 'trades+equity'." +
                f"\nInstead you have set it to '{self.p.mode}'.")
Example #6
0
    def create_analysis(self):
        # Set up variables..

        # Variables hidden from user..

        # Depending on the input parameter user provides for 'filter',
        # append 'LONG' or 'SHORT' in table heading output to user.
        # Helps user identify the types of trades stats calculated on,
        # either long, short or all (i.e. both)..
        if self.p.filter == 'long':
            self._tableLongShort =  'LONG'  # Append 'LONG' to table heading..
        elif self.p.filter == 'short':
            self._tableLongShort =  'SHORT' # Append 'SHORT' to table heading..
        elif self.p.filter == 'all':
            self._tableLongShort =  'TRADES'  # Blank char appended to our table..
        else:
            raise Exception("Parameter 'filter' must be 'long', 'short', or" +
                            " 'all' not '%s'." % str(self.p.filter))

        self._all_pnl_list=[]    # hidden from user - all trades pnl.
        self._won_pnl_list=[]    # hidden from user - win trades pnl.
        self._lost_pnl_list=[]   # hidden from user - lost trades pnl.
        self._curStreak = None  # Current streak type [None, 'Won', 'Lost']
        self._wonStreak_list=[]    # Store each won streak in list..
        self._lostStreak_list=[]    # Store each loss streak in list..

        # Variables output to user..
        o = self.rets = AutoOrderedDict()   # Return user object..
        # Stats applied to all trades (winners and losers)..
        o.all.trades.total = 0
        o.all.trades.open = 0
        o.all.trades.closed = 0
        o.all.pnl.total = None
        o.all.pnl.average = None
        o.all.streak.zScore = None
        o.all.stats.profitFactor = None
        o.all.stats.winFactor = None
        o.all.stats.winRate = None
        o.all.stats.rewardRiskRatio = None
        o.all.stats.expectancyPercentEstimated = None
        o.all.stats.kellyPercent = None

        for each in ['won', 'lost']:
            oWL=self.rets[each]
            oWL.trades.closed = 0
            oWL.trades.percent = None
            oWL.pnl.total = None
            oWL.pnl.average = None
            oWL.pnl.median = None
            oWL.pnl.max = None
            oWL.streak.current = 0
            oWL.streak.max = None
            oWL.streak.average = None
            oWL.streak.median = None
Example #7
0
    def create_analysis(self):
        self.rets = AutoOrderedDict()  # dict with . notation

        self.rets.len = 0
        self.rets.drawdown = 0.0
        self.rets.moneydown = 0.0

        self.rets.max.len = 0.0
        self.rets.max.drawdown = 0.0
        self.rets.max.moneydown = 0.0

        self._maxvalue = float('-inf')  # any value will outdo it
Example #8
0
    def _generate_STADTDB(self, optimize=False):
        if optimize:
            yield AutoOrderedDict(entry_td_max=-1,
                                  close_td_reversal=False)

            for factor in [2.0, 3.0, 4.0]:
                for st_period in [3, 7, 21, 50]:
                    for use_wick in [True, False]:
                        for entry_td_max in [2, 4, 7]:
                            for close_td_reversal in [True, False]:
                                # 3 * 4 * 2 * 3 * 2 = 144
                                yield AutoOrderedDict(
                                    entry_td_max=entry_td_max,
                                    close_td_reversal=close_td_reversal,
                                    st_factor=factor,
                                    st_period=st_period,
                                    st_use_wick=use_wick,
                                    )

        else:
            yield AutoOrderedDict(entry_td_max=4,
                                  close_td_reversal=True)
Example #9
0
class MyAnalyzer(Analyzer):
    def __init__(self):
        self.rets = AutoOrderedDict()
        self.info = AutoOrderedDict()
        self.info.signals = 0

    def get_analysis(self):
        return dict(self.info)

    def stop(self):
        s = self.strategy
        self.info = analysis(
            s.closed_trades,
            s.active_trades,
            s.cash,
            s.p.initial_cash,
            self.data,
            self.info,
        )
        self.rets._close()

    def next(self):
        if self.strategy.new_trade:
            self.info.signals += 1
Example #10
0
    def create_analysis(self):
        self.rets = AutoOrderedDict()
        self.rets.trades.total = 0
        self.rets.trades.positives = 0
        self.rets.trades.negatives = 0

        self.accumulate = 0
        self.accumulate_profit = 0
        self.accumulate_loss = 0

        self.rets.avg.trade = 0
        self.rets.avg.profit_trade = 0
        self.rets.avg.loss_trade = 0

        self._value = self.strategy.broker.get_cash()
Example #11
0
    def create_analysis(self):
        #self.rets is backtrader analyzer dictionary - need to use to print results.  self.rets.total.closed means "total" will print, followed by sub heading "closed"
        self.rets = AutoOrderedDict()

        self.rets.expectancy_dollars = 0
        self.rets.winratio = 0
        self.rets.lossratio = 0
        self.rets.pnl.gross.total = 0
        self.rets.pnl.net.total = 0
        self.rets.total.total = 0
        self.rets.total.open = 0
        self.rets.total.closed = 0
        self.rets.won.pnl.average = 0
        self.rets.lost.pnl.average = 0
        self.rets.won.total = 0
        self.rets.lost.total = 0
Example #12
0
class TradeAnalyzer(Analyzer):
    '''
    Provides statistics on closed trades (keeps also the count of open ones)

      - Total Open/Closed Trades

      - Streak Won/Lost Current/Longest

      - ProfitAndLoss Total/Average

      - Won/Lost Count/ Total PNL/ Average PNL / Max PNL

      - Long/Short Count/ Total PNL / Average PNL / Max PNL

          - Won/Lost Count/ Total PNL/ Average PNL / Max PNL

      - Length (bars in the market)

        - Total/Average/Max/Min

        - Won/Lost Total/Average/Max/Min

        - Long/Short Total/Average/Max/Min

          - Won/Lost Total/Average/Max/Min

    Note:

      The analyzer uses an "auto"dict for the fields, which means that if no
      trades are executed, no statistics will be generated.

      In that case there will be a single field/subfield in the dictionary
      returned by ``get_analysis``, namely:

        - dictname['total']['total'] which will have a value of 0 (the field is
          also reachable with dot notation dictname.total.total
    '''
    def create_analysis(self):
        self.rets = AutoOrderedDict()
        self.rets.total.total = 0

    def stop(self):
        super(TradeAnalyzer, self).stop()
        self.rets._close()

    def notify_trade(self, trade):
        if trade.justopened:
            # Trade just opened
            self.rets.total.total += 1
            self.rets.total.open += 1

        elif trade.status == trade.Closed:
            trades = self.rets

            res = AutoDict()
            # Trade just closed

            won = res.won = trade.pnlcomm >= 0.0
            lost = res.lost = not res.won
            tlong = res.tlong = trade.long
            tshort = res.tshort = not trade.long

            trades.total.open -= 1
            trades.total.closed += 1

            # Streak
            for wlname in ['won', 'lost']:
                wl = res[wlname]

                trades.streak[wlname].current *= wl
                trades.streak[wlname].current += wl

                ls = trades.streak[wlname].longest or 0
                trades.streak[wlname].longest = \
                    max(ls, trades.streak[wlname].current)

            trpnl = trades.pnl
            trpnl.gross.total += trade.pnl
            trpnl.gross.average = trades.pnl.gross.total / trades.total.closed
            trpnl.net.total += trade.pnlcomm
            trpnl.net.average = trades.pnl.net.total / trades.total.closed

            # Won/Lost statistics
            for wlname in ['won', 'lost']:
                wl = res[wlname]
                trwl = trades[wlname]

                trwl.total += wl  # won.total / lost.total

                trwlpnl = trwl.pnl
                pnlcomm = trade.pnlcomm * wl

                trwlpnl.total += pnlcomm
                trwlpnl.average = trwlpnl.total / (trwl.total or 1.0)

                wm = trwlpnl.max or 0.0
                func = max if wlname == 'won' else min
                trwlpnl.max = func(wm, pnlcomm)

            # Long/Short statistics
            for tname in ['long', 'short']:
                trls = trades[tname]
                ls = res['t' + tname]

                trls.total += ls  # long.total / short.total
                trls.pnl.total += trade.pnlcomm * ls
                trls.pnl.average = trls.pnl.total / (trls.total or 1.0)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]
                    pnlcomm = trade.pnlcomm * wl * ls

                    trls[wlname] += wl * ls  # long.won / short.won

                    trls.pnl[wlname].total += pnlcomm
                    trls.pnl[wlname].average = \
                        trls.pnl[wlname].total / (trls[wlname] or 1.0)

                    wm = trls.pnl[wlname].max or 0.0
                    func = max if wlname == 'won' else min
                    trls.pnl[wlname].max = func(wm, pnlcomm)

            # Length
            trades.len.total += trade.barlen
            trades.len.average = trades.len.total / trades.total.closed
            ml = trades.len.max or 0
            trades.len.max = max(ml, trade.barlen)

            ml = trades.len.min or MAXINT
            trades.len.min = min(ml, trade.barlen)

            # Length Won/Lost
            for wlname in ['won', 'lost']:
                trwl = trades.len[wlname]
                wl = res[wlname]

                trwl.total += trade.barlen * wl
                trwl.average = trwl.total / (trades[wlname].total or 1.0)

                m = trwl.max or 0
                trwl.max = max(m, trade.barlen * wl)
                if trade.barlen * wl:
                    m = trwl.min or MAXINT
                    trwl.min = min(m, trade.barlen * wl)

            # Length Long/Short
            for lsname in ['long', 'short']:
                trls = trades.len[lsname]  # trades.len.long
                ls = res['t' + lsname]  # tlong/tshort

                barlen = trade.barlen * ls

                trls.total += barlen  # trades.len.long.total
                total_ls = trades[lsname].total  # trades.long.total
                trls.average = trls.total / (total_ls or 1.0)

                # max/min
                m = trls.max or 0
                trls.max = max(m, barlen)
                m = trls.min or MAXINT
                trls.min = min(m, barlen or m)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]  # won/lost

                    barlen2 = trade.barlen * ls * wl

                    trls_wl = trls[wlname]  # trades.len.long.won
                    trls_wl.total += barlen2  # trades.len.long.won.total

                    trls_wl.average = \
                        trls_wl.total / (trades[lsname][wlname] or 1.0)

                    # max/min
                    m = trls_wl.max or 0
                    trls_wl.max = max(m, barlen2)
                    m = trls_wl.min or MAXINT
                    trls_wl.min = min(m, barlen2 or m)
Example #13
0
class TradeRecorder(Analyzer):
    '''
    Summary:

        Keep a track of all trades made in a dictionary (hidden to user).
        After strategy has ran, generate two lists of Trade objects;
        1) A list of closed trades and 2) A list of trades still open.

        These can then be accessed by user to e.g. plot trades entry and exit
        on a chart.

        Allow access of trades after strategy ran via;
            self.rets.openTrades
            self.rets.closedTrades

    Params:

         - No parameters are needed.

    Methods:

      - get_analysis

        Returns a dictionary holding the list of open and closed trades.


    [This 'traderecorder.py' was coded by Richard O'Regan (London) Nov 2017]
    '''
    def create_analysis(self):
        #print('C**T TRADE RECORDER')
        # Keep dict of trades based on tradeid
        # Note: must be a unique tradeid defined for each trade else will
        # get overwritten..
        self.rets = AutoOrderedDict()
        self.rets.openTrades = []  # List of all open trades..
        self.rets.closedTrades = []  # List of all closed trades..

        # Hidden from user, destroyed at end, useful to track trades..
        self._tradeDict = {}  # Temp store Trade objects..

    def notify_trade(self, trade):

        # Add trade to our trade dict which records all trades..
        # Great thing is, don't care if open or closed, if an open trade
        # becomes closed, it is simply rewritten.
        # Note: must be a unique tradeid for each trade else overwritten.
        #print('type trade = ',type(trade))
        self._tradeDict[trade.tradeid] = trade

    def stop(self):
        super().stop  # Check if we need this..

        # Create our output list of closed and open trades we have tracked..
        for n in self._tradeDict:

            trade = self._tradeDict[n]  # Get value (ie Trade object)

            # Create dictionary object of essential attributes of Trade object..
            # Note: we dont save Trade objects because they are inefficient and
            # each Trade saves market data (retarded), also when I split data into
            # three, open only, normal, close only. Created problems with optimiser
            # pickling..

            if trade.isopen:
                # Trade open..
                _trade = {}
                _trade['entry_price'] = trade.entry_price
                _trade['entry_date'] = trade.open_datetime()
                _trade['pnl'] = trade.pnl
                _trade['pnlcomm'] = trade.pnlcomm
                _trade['tradeid'] = trade.tradeid
                if hasattr(trade, 'R'):
                    _trade['R'] = trade.R  # Apend R stop if it exists..
                self.rets.openTrades.append(_trade)  # Save open trade dict

            else:
                # Trade closed, add attributes above + exit price & date..
                _trade = {}
                _trade['entry_price'] = trade.entry_price
                _trade['entry_date'] = trade.open_datetime()
                _trade['exit_price'] = trade.exit_price
                _trade['exit_date'] = trade.close_datetime()
                _trade['pnl'] = trade.pnl
                _trade['pnlcomm'] = trade.pnlcomm
                _trade['tradeid'] = trade.tradeid
                if hasattr(trade, 'R'):
                    _trade['R'] = trade.R  # Apend R stop if it exists..
                self.rets.closedTrades.append(_trade)  # Save closed trade dict
                self.rets.equityCurve.append({
                    'date': trade.open_datetime(),
                    'equity': trade.pnlcomm()
                })

        # Now kill the internal trade dict of Trade objects..
        # Highly inefficient, we don't want it to be saved with the
        # BackTrader Cerebro optimisation feature..
        self._tradeDict = None

        self.rets._close()  # Check if we need this..

    def print(self, *args, **kwargs):
        '''
        Overide print method to display length of list rather than contents.
        (We don't want e.g. 1000 trades to be displayed.)
        '''
        print('TradeRecorder:')
        print(f'  - openTrades = list of length {len(self.rets.openTrades)}')
        print(
            f'  - closedTrades = list of length {len(self.rets.closedTrades)}')
Example #14
0
def dict_filter(old_dict, mykeys):
    new_dict = AutoOrderedDict({your_key: old_dict[your_key] for your_key in mykeys})
    return new_dict
Example #15
0
class DrawDown(bt.Analyzer):
    '''This analyzer calculates trading system drawdowns stats such as drawdown
    values in %s and in dollars, max drawdown in %s and in dollars, drawdown
    length and drawdown max length

    Params:

      - ``fund`` (default: ``None``)

        If ``None`` the actual mode of the broker (fundmode - True/False) will
        be autodetected to decide if the returns are based on the total net
        asset value or on the fund value. See ``set_fundmode`` in the broker
        documentation

        Set it to ``True`` or ``False`` for a specific behavior

    Methods:

      - ``get_analysis``

        Returns a dictionary (with . notation support and subdctionaries) with
        drawdown stats as values, the following keys/attributes are available:

        - ``drawdown`` - drawdown value in 0.xx %
        - ``moneydown`` - drawdown value in monetary units
        - ``len`` - drawdown length

        - ``max.drawdown`` - max drawdown value in 0.xx %
        - ``max.moneydown`` - max drawdown value in monetary units
        - ``max.len`` - max drawdown length
    '''

    params = (
        ('fund', None),
    )

    def start(self):
        super(DrawDown, self).start()
        if self.p.fund is None:
            self._fundmode = self.strategy.broker.fundmode
        else:
            self._fundmode = self.p.fund

    def create_analysis(self):
        self.rets = AutoOrderedDict()  # dict with . notation

        self.rets.len = 0
        self.rets.drawdown = 0.0
        self.rets.moneydown = 0.0

        self.rets.max.len = 0.0
        self.rets.max.drawdown = 0.0
        self.rets.max.moneydown = 0.0

        self._maxvalue = float('-inf')  # any value will outdo it

    def stop(self):
        self.rets._close()  # . notation cannot create more keys

    def notify_fund(self, cash, value, fundvalue, shares):
        if not self._fundmode:
            self._value = value  # record current value
            self._maxvalue = max(self._maxvalue, value)  # update peak value
        else:
            self._value = fundvalue  # record current value
            self._maxvalue = max(self._maxvalue, fundvalue)  # update peak

    def next(self):
        r = self.rets

        # calculate current drawdown values
        r.moneydown = moneydown = self._maxvalue - self._value
        r.drawdown = drawdown = 100.0 * moneydown / self._maxvalue

        # maxximum drawdown values
        r.max.moneydown = max(r.max.moneydown, moneydown)
        r.max.drawdown = maxdrawdown = max(r.max.drawdown, drawdown)

        r.len = r.len + 1 if drawdown else 0
        r.max.len = max(r.max.len, r.len)
Example #16
0
 def pack(strategy, **kwargs):
     return AutoOrderedDict(strategy=strategy,
                            params=AutoOrderedDict(**kwargs))
Example #17
0
class TradeAnalyzer(Analyzer):
    '''
    Provides statistics on closed trades (keeps also the count of open ones)

      - Total Open/Closed Trades

      - Streak Won/Lost Current/Longest

      - ProfitAndLoss Total/Average

      - Won/Lost Count/ Total PNL/ Average PNL / Max PNL

      - Long/Short Count/ Total PNL / Average PNL / Max PNL

          - Won/Lost Count/ Total PNL/ Average PNL / Max PNL

      - Length (bars in the market)

        - Total/Average/Max/Min

        - Won/Lost Total/Average/Max/Min

        - Long/Short Total/Average/Max/Min

          - Won/Lost Total/Average/Max/Min

    Note:

      The analyzer uses an "auto"dict for the fields, which means that if no
      trades are executed, no statistics will be generated.

      In that case there will be a single field/subfield in the dictionary
      returned by ``get_analysis``, namely:

        - dictname['total']['total'] which will have a value of 0 (the field is
          also reachable with dot notation dictname.total.total
    '''
    def create_analysis(self):
        self.rets = AutoOrderedDict()
        self.rets.total.total = 0

    def stop(self):
        super(TradeAnalyzer, self).stop()
        self.rets._close()

    def notify_trade(self, trade):
        if trade.justopened:
            # Trade just opened
            self.rets.total.total += 1
            self.rets.total.open += 1

        elif trade.status == trade.Closed:
            trades = self.rets

            res = AutoDict()
            # Trade just closed

            won = res.won = int(trade.pnlcomm >= 0.0)
            lost = res.lost = int(not won)
            tlong = res.tlong = trade.long
            tshort = res.tshort = not trade.long

            trades.total.open -= 1
            trades.total.closed += 1

            # Streak
            for wlname in ['won', 'lost']:
                wl = res[wlname]

                trades.streak[wlname].current *= wl
                trades.streak[wlname].current += wl

                ls = trades.streak[wlname].longest or 0
                trades.streak[wlname].longest = \
                    max(ls, trades.streak[wlname].current)

            trpnl = trades.pnl
            trpnl.gross.total += trade.pnl
            trpnl.gross.average = trades.pnl.gross.total / trades.total.closed
            trpnl.net.total += trade.pnlcomm
            trpnl.net.average = trades.pnl.net.total / trades.total.closed

            # Won/Lost statistics
            for wlname in ['won', 'lost']:
                wl = res[wlname]
                trwl = trades[wlname]

                trwl.total += wl  # won.total / lost.total

                trwlpnl = trwl.pnl
                pnlcomm = trade.pnlcomm * wl

                trwlpnl.total += pnlcomm
                trwlpnl.average = trwlpnl.total / (trwl.total or 1.0)

                wm = trwlpnl.max or 0.0
                func = max if wlname == 'won' else min
                trwlpnl.max = func(wm, pnlcomm)

            # Long/Short statistics
            for tname in ['long', 'short']:
                trls = trades[tname]
                ls = res['t' + tname]

                trls.total += ls  # long.total / short.total
                trls.pnl.total += trade.pnlcomm * ls
                trls.pnl.average = trls.pnl.total / (trls.total or 1.0)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]
                    pnlcomm = trade.pnlcomm * wl * ls

                    trls[wlname] += wl * ls  # long.won / short.won

                    trls.pnl[wlname].total += pnlcomm
                    trls.pnl[wlname].average = \
                        trls.pnl[wlname].total / (trls[wlname] or 1.0)

                    wm = trls.pnl[wlname].max or 0.0
                    func = max if wlname == 'won' else min
                    trls.pnl[wlname].max = func(wm, pnlcomm)

            # Length
            trades.len.total += trade.barlen
            trades.len.average = trades.len.total / trades.total.closed
            ml = trades.len.max or 0
            trades.len.max = max(ml, trade.barlen)

            ml = trades.len.min or MAXINT
            trades.len.min = min(ml, trade.barlen)

            # Length Won/Lost
            for wlname in ['won', 'lost']:
                trwl = trades.len[wlname]
                wl = res[wlname]

                trwl.total += trade.barlen * wl
                trwl.average = trwl.total / (trades[wlname].total or 1.0)

                m = trwl.max or 0
                trwl.max = max(m, trade.barlen * wl)
                if trade.barlen * wl:
                    m = trwl.min or MAXINT
                    trwl.min = min(m, trade.barlen * wl)

            # Length Long/Short
            for lsname in ['long', 'short']:
                trls = trades.len[lsname]  # trades.len.long
                ls = res['t' + lsname]  # tlong/tshort

                barlen = trade.barlen * ls

                trls.total += barlen  # trades.len.long.total
                total_ls = trades[lsname].total   # trades.long.total
                trls.average = trls.total / (total_ls or 1.0)

                # max/min
                m = trls.max or 0
                trls.max = max(m, barlen)
                m = trls.min or MAXINT
                trls.min = min(m, barlen or m)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]  # won/lost

                    barlen2 = trade.barlen * ls * wl

                    trls_wl = trls[wlname]  # trades.len.long.won
                    trls_wl.total += barlen2  # trades.len.long.won.total

                    trls_wl.average = \
                        trls_wl.total / (trades[lsname][wlname] or 1.0)

                    # max/min
                    m = trls_wl.max or 0
                    trls_wl.max = max(m, barlen2)
                    m = trls_wl.min or MAXINT
                    trls_wl.min = min(m, barlen2 or m)
Example #18
0
 def start(self):
     self.trades = AutoOrderedDict()
Example #19
0
 def start(self):
     self.trades = AutoOrderedDict()
     self.trades.total.total = 0
Example #20
0
 def start(self):
     self.trades = AutoOrderedDict()
Example #21
0
class TradeAnalyzer(Analyzer):
    '''
    Provides statistics on closed trades (keeps also the count of open ones)

      - Total Open/Closed Trades

      - Streak Won/Lost Current/Longest

      - ProfitAndLoss Total/Average

      - Won/Lost Count/ Total PNL/ Average PNL / Max PNL

      - Long/Short Count/ Total PNL / Average PNL / Max PNL

          - Won/Lost Count/ Total PNL/ Average PNL / Max PNL

      - Length (bars in the market)

        - Total/Average/Max/Min

        - Won/Lost Total/Average/Max/Min

        - Long/Short Total/Average/Max/Min

          - Won/Lost Total/Average/Max/Min
    '''
    def start(self):
        self.trades = AutoOrderedDict()

    def stop(self):
        self.trades._close()

    def notify_trade(self, trade):
        if trade.justopened:
            # Trade just opened
            self.trades.total.total += 1
            self.trades.total.open += 1

        elif trade.status == trade.Closed:
            res = AutoDict()
            # Trade just closed

            won = res.won = trade.pnlcomm >= 0.0
            lost = res.lost = not res.won
            tlong = res.tlong = trade.long
            tshort = res.tshort = not trade.long

            trades = self.trades

            trades.total.open -= 1
            trades.total.closed += 1

            # Streak
            for wlname in ['won', 'lost']:
                wl = res[wlname]

                trades.streak[wlname].current *= wl
                trades.streak[wlname].current += wl

                ls = trades.streak[wlname].longest or 0
                trades.streak[wlname].longest = \
                    max(ls, trades.streak[wlname].current)

            trpnl = trades.pnl
            trpnl.gross.total += trade.pnl
            trpnl.gross.average = trades.pnl.gross.total / trades.total.closed
            trpnl.net.total += trade.pnlcomm
            trpnl.net.average = trades.pnl.net.total / trades.total.closed

            # Won/Lost statistics
            for wlname in ['won', 'lost']:
                wl = res[wlname]
                trwl = trades[wlname]

                trwl.total += wl  # won.total / lost.total

                trwlpnl = trwl.pnl
                pnlcomm = trade.pnlcomm * wl

                trwlpnl.total += pnlcomm
                trwlpnl.average = trwlpnl.total / (trwl.total or 1.0)

                wm = trwlpnl.max or 0.0
                func = max if wlname == 'won' else min
                trwlpnl.max = func(wm, pnlcomm)

            # Long/Short statistics
            for tname in ['long', 'short']:
                trls = trades[tname]
                ls = res['t' + tname]

                trls.total += ls  # long.total / short.total
                trls.pnl.total += trade.pnlcomm * ls
                trls.pnl.average = trls.pnl.total / (trls.total or 1.0)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]
                    pnlcomm = trade.pnlcomm * wl * ls

                    trls[wlname] += wl * ls  # long.won / short.won

                    trls.pnl[wlname].total += pnlcomm
                    trls.pnl[wlname].average = \
                        trls.pnl[wlname].total / (trls[wlname] or 1.0)

                    wm = trls.pnl[wlname].max or 0.0
                    func = max if wlname == 'won' else min
                    trls.pnl[wlname].max = func(wm, pnlcomm)

            # Length
            trades.len.total += trade.barlen
            trades.len.average = trades.len.total / trades.total.closed
            ml = trades.len.max or 0
            trades.len.max = max(ml, trade.barlen)

            ml = trades.len.min or sys.maxint
            trades.len.min = min(ml, trade.barlen)

            # Length Won/Lost
            for wlname in ['won', 'lost']:
                trwl = trades.len[wlname]
                wl = res[wlname]

                trwl.total += trade.barlen * wl
                trwl.average = trwl.total / (trades[wlname].total or 1.0)

                m = trwl.max or 0
                trwl.max = max(m, trade.barlen * wl)
                if trade.barlen * wl:
                    m = trwl.min or sys.maxint
                    trwl.min = min(m, trade.barlen * wl)

            # Length Long/Short
            for lsname in ['long', 'short']:
                trls = trades.len[lsname]  # trades.len.long
                ls = res['t' + lsname]  # tlong/tshort

                barlen = trade.barlen * ls

                trls.total += barlen  # trades.len.long.total
                total_ls = trades[lsname].total   # trades.long.total
                trls.average = trls.total / (total_ls or 1.0)

                # max/min
                m = trls.max or 0
                trls.max = max(m, barlen)
                m = trls.min or sys.maxint
                trls.min = min(m, barlen or m)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]  # won/lost

                    barlen2 = trade.barlen * ls * wl

                    trls_wl = trls[wlname]  # trades.len.long.won
                    trls_wl.total += barlen2  # trades.len.long.won.total

                    trls_wl.average = \
                        trls_wl.total / (trades[lsname][wlname] or 1.0)

                    # max/min
                    m = trls_wl.max or 0
                    trls_wl.max = max(m, barlen2)
                    m = trls_wl.min or sys.maxint
                    trls_wl.min = min(m, barlen2 or m)

    def get_analysis(self):
        return self.trades
 def start(self):
     self.trades = AutoOrderedDict()
     self.trades.total.total = 0
Example #23
0
 def create_analysis(self):
     self.rets = AutoOrderedDict()
     self.rets.total.total = 0
Example #24
0
 def create_analysis(self):
     '''Replace default implementation to instantiate an AutoOrdereDict
     rather than an OrderedDict'''
     self.rets = AutoOrderedDict()
Example #25
0
class TradeAnalyzer(Analyzer):
    def start(self):
        self.trades = AutoOrderedDict()

    def stop(self):
        self.trades._close()

    def notify_trade(self, trade):
        if trade.justopened:
            # Trade just opened
            self.trades.total.total += 1
            self.trades.total.open += 1

        elif trade.status == trade.Closed:
            res = AutoDict()
            # Trade just closed

            won = res.won = trade.pnlcomm >= 0.0
            lost = res.lost = not res.won
            tlong = res.tlong = trade.long
            tshort = res.tshort = not trade.long

            trades = self.trades

            trades.total.open -= 1
            trades.total.closed += 1

            for wlname in ['won', 'lost']:
                wl = res[wlname]

                trades.total[wlname] += wl

                trades.streak[wlname].current *= wl
                trades.streak[wlname].current += wl

                ls = trades.streak[wlname].longest or 0
                trades.streak[wlname].longest = \
                    max(ls, trades.streak[wlname].current)

            trpnl = trades.pnl
            trpnl.gross.total += trade.pnl
            trpnl.gross.average = trades.pnl.gross.total / trades.total.closed
            trpnl.net.total += trade.pnlcomm
            trpnl.net.average = trades.pnl.net.total / trades.total.closed

            # Long/Short statistics
            for tname in ['long', 'short']:
                trls = trades[tname]
                ls = res['t' + tname]

                trls.total += ls  # long.total / short.total
                trls.pnl.total += trade.pnlcomm * ls
                trls.pnl.average = trls.pnl.total / (trls.total or 1.0)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]
                    pnlcomm = trade.pnlcomm * wl * ls

                    trls[wlname] += wl * ls  # long.won / short.won

                    trls.pnl[wlname].total += pnlcomm
                    trls.pnl[wlname].average = \
                        trls.pnl[wlname].total / (trls[wlname] or 1.0)

                    wm = trls.pnl[wlname].max or 0.0
                    func = max if wlname == 'won' else min
                    trls.pnl[wlname].max = func(wm, pnlcomm)

            # Length
            trades.len.total += trade.barlen
            trades.len.average = trades.len.total / trades.total.closed
            ml = trades.len.max or 0
            trades.len.max = max(ml, trade.barlen)

            ml = trades.len.min or sys.maxint
            trades.len.min = min(ml, trade.barlen)

            # Length Won/Lost
            for wlname in ['won', 'lost']:
                trwl = trades.len[wlname]
                wl = res[wlname]

                trwl.total += trade.barlen * wl
                trwl.average = trwl.total / (trades.total[wlname] or 1.0)

                m = trwl.max or 0
                trwl.max = max(m, trade.barlen * wl)
                if trade.barlen * wl:
                    m = trwl.min or sys.maxint
                    trwl.min = min(m, trade.barlen * wl)

            # Length Long/Short
            for lsname in ['long', 'short']:
                trls = trades.len[lsname]  # trades.len.long
                ls = res['t' + lsname]  # tlong/tshort

                barlen = trade.barlen * ls

                trls.total += barlen  # trades.len.long.total
                total_ls = trades[lsname].total  # trades.long.total
                trls.average = trls.total / (total_ls or 1.0)

                # max/min
                m = trls.max or 0
                trls.max = max(m, barlen)
                m = trls.min or sys.maxint
                trls.min = min(m, barlen or m)

                for wlname in ['won', 'lost']:
                    wl = res[wlname]  # won/lost

                    barlen2 = trade.barlen * ls * wl

                    trls_wl = trls[wlname]  # trades.len.long.won
                    trls_wl.total += barlen2  # trades.len.long.won.total

                    trls_wl.average = \
                        trls_wl.total / (trades[lsname][wlname] or 1.0)

                    # max/min
                    m = trls_wl.max or 0
                    trls_wl.max = max(m, barlen2)
                    m = trls_wl.min or sys.maxint
                    trls_wl.min = min(m, barlen2 or m)

    def get_analysis(self):
        return self.trades
Example #26
0
 def create_analysis(self):
     self.rets = AutoOrderedDict()
     self.rets.total.total = 0
Example #27
0
 def __init__(self):
     self.rets = AutoOrderedDict()
     self.info = AutoOrderedDict()
     self.info.signals = 0
class TradeRecorder(Analyzer):
    '''

    Summary:

        TradeRecorder enables user to save all trades produced by strategy and
        also the corresponding equity curve.

        NOTE: to record trades, the Trade object needs to be modified because
        'exit_price' and 'exit_date' do not exist in the public Backtrader
        version. So you need to use the hacked 'trade.py' modification by
        Richard O'Regan that includes these extra attributes.

        [Or you can add them yourself in the Trade.update() code.
        At very end of the method add;

                self.entry_price = self.price
                self.exit_price = price

        This code only works for simple trades with one exit and one entry.
        Assume one unit traded, not tested with different trade sizes.]


        TRADE RECORDING MODE
        After strategy has ran, if mode = "trade" or "trade+equity", a DataFrame
        of open trades and a DataFrame of closed trades can be accessed via;

                self.rets.openTrades
                self.rets.closedTrades

        The above information can be used to e.g. visulise all trades produced
        with a strategy by plotting them on top of the market data chart.
        The trades can be visually checked to ensure the strategy has been coded
        correctly.


        EQUITY RECORDING MODE
        If mode = "equity" or "trade+equity", a DataFrame representing the
        equity curve can be accessed via;

            self.rets.equityCurve

        The above information can be used to visulise the equity curve for a
        strategy and it's particular parameters.

        USE
        It is advised to set the parameter to record only the data you need.
        This will save space in memory (and storage if you write to disk).


    Params:

           - ``mode``: (default: ``trade+equity``)

           - options are ``trades``, ``equity`` or ``trades+equity``

            If ``trades`` option, record all data related to trade enough to
            be able to plot on a chart the exact entry & exit date and price.

            Data recorded:

                'entry_price' from Trade.entry_price
                'entry_date' from Trade.open_datetime()
                *'exit_price' from Trade.exit_price
                *'exit_date' from Trade.close_datetime()
                'pnl' from Trade.pnl
                'pnlcomm' from Trade.pnlcomm
                'tradeid' from Trade.tradeid


            If ``equity`` option record enough data to be able to plot an equity curve.

            Data recorded:

                'exit_date' from Trade.close_datetime()
                'equity' from cumulative sum of Trade.pnlcomm

                NOTE: The actual account equity is *NOT* recorded,
                instead cumulative equity from each trade. Always starts from 0.


            If ``trades+equity`` option, record all of data mentioned above.


    Methods:

      - get_analysis

        Returns threes DataFrame, one each for;
                Open trades     ---> Records trades entries and exits.
                Closed trades   ---> Records trades entries and exits.
                Equity curve    ---> Records cumulative pnl.


    [This 'traderecorder.py' was coded by Richard O'Regan (London) Nov 2017]
    '''

    # Declare parameters user can pass to this Analyzer..
    params = (('mode', 'trades+equity'), )

    def create_analysis(self):
        # Keep dict of trades based on tradeid.
        # Note: must be a unique tradeid defined for each trade else will
        # get overwritten.
        self.rets = AutoOrderedDict()
        self.rets.openTrades = []  # List of all open trades..
        self.rets.closedTrades = []  # List of all closed trades..
        self.rets.equityCurve = []  # List of dictonary

        # Hidden from user, destroyed at end, useful to track trades.
        self._tradeDict = {}  # Temp store Trade objects..
        self._cumulativeEquity = None  # Keep track of our equity..

        # Check user parameters are valid - if not raise Exception.
        if self.p.mode not in ['trades', 'equity', 'trades+equity']:
            raise Exception(
                "TradeRecorder Analyzer must have parameter " +
                "'mode' set to either 'trades', 'equity' or 'trades+equity'." +
                f"\nInstead you have set it to '{self.p.mode}'.")

    def notify_trade(self, trade):
        # Add Trade object to our trade dict which records all trades..
        # Great thing is, don't care if open or closed, if an open trade
        # becomes closed, it is simply rewritten (because same tradeid/key).
        # Note: must be a unique tradeid for each trade else overwritten.
        self._tradeDict[trade.tradeid] = trade

    def stop(self):
        # Create our output list of closed and open trades we have tracked..
        for n in self._tradeDict:

            trade = self._tradeDict[n]  # Get value (ie Trade object)

            # Create DataFrame of essential attributes of Trade object..
            # Note: we dont save Trade objects because they are inefficient and
            # each Trade object appears to save whole market data (retarded)..

            # Common information to store for both open and closed trades..
            # Set up basic dataframe row used for both open & closed trades.
            # We later append columns to this for closed trades..

            if hasattr(trade, 'R'):
                _trade = pd.DataFrame([{
                    'entry_price': trade.entry_price,
                    'entry_date': trade.open_datetime(),
                    'pnl': trade.pnl,
                    'pnlcomm': trade.pnlcomm,
                    'is_long': trade.long,
                    'R_stop': trade.R,  # Append R-stop if it exists..
                    'tradeid': trade.tradeid
                }])

            else:
                _trade = pd.DataFrame([{
                    'entry_price': trade.entry_price,
                    'entry_date': trade.open_datetime(),
                    'pnl': trade.pnl,
                    'pnlcomm': trade.pnlcomm,
                    'is_long': trade.long,
                    'tradeid': trade.tradeid
                }])

            # Check if trade open or closed..
            if trade.isopen and self.p.mode in ['trade', 'trades+equity']:
                # This trade is still open..
                # Limited to what data we have because trade not closed out
                # e.g. no exit date and no equity curve change.
                self.rets.openTrades.append(_trade)  # Save open trade dict

            else:
                # Trade closed.
                # Calc & store equity if required..
                if self.p.mode in ['equity', 'trades+equity']:
                    _equity = self.calc_equity(trade)
                    self.rets.equityCurve.append(_equity)

                # Calc & store trades if required..
                if self.p.mode in ['trades', 'trades+equity']:
                    # Need to use the hacked Trade objects that included
                    # exit price and date..
                    # If attributes don't exist, raise an error..
                    try:
                        _trade['exit_date'] = trade.close_datetime()
                        _trade['exit_price'] = trade.exit_price
                    except AttributeError as e:
                        print(
                            e, '\nThe Trade object received by this ' +
                            'TradeRecorder Analyzer is missing extra ' +
                            'attributes\nnot included in the public ' +
                            'Backtrader version. ' +
                            'To use this Analyzer, you need to ' +
                            'hack\n"trade.py" to add in two extra ' +
                            ' attributes. See Rich O\'Regan for details.')
                        raise

                    self.rets.closedTrades.append(_trade)

        # Append single DataFrame to a list, then concatenate list to one big
        # DataFrame because more efficient than lots of appending to DF..
        o = self.rets
        if self.p.mode in ['trades', 'trades+equity']:
            o.closedTrades = (pd.concat(o.closedTrades).reset_index(
                drop=True) if o.closedTrades != [] else None)
            o.openTrades = (pd.concat(o.openTrades).reset_index(
                drop=True) if o.openTrades != [] else None)
        else:
            o.closedTrades = o.openTrades = None  # Trades not required..

        if self.p.mode in ['equity', 'trades+equity']:
            o.equityCurve = (pd.concat(o.equityCurve).reset_index(
                drop=True) if o.equityCurve != [] else None)
        else:
            o.equityCurve = None  # Equity not required by user..

        # 'Kill' internal list of Trade objects by setting to 'None'.
        # Inefficient if kept. We don't want list of bloated Trade objects to be
        # saved. (BackTrader Cerebro optimisation feature automatically saves
        # this Analyzer and all attributes).
        self._tradeDict = None
        self.rets._close()  # Check if we need this..

    def calc_equity(self, trade):
        # Calculate the equity change for each closed trade.
        # Record date & cumulative pnl, so that an equity curve can be plotted.

        # Mostly straight forward, keep a track of the current pnl , which
        # always starts from 0 (NOT interested in account equity e.g. $10,000).

        # Curve must start from zero, which should be start of data.
        # We don't know the start of the market data (though it could be found
        # with more code), so;
        # The very first value, use the *ENTRY* date of first trade and value 0.
        # For all other closed trades use the *EXIT* date and the value is found
        # by adding latest pnl to cumulative equity.

        # OUTPUT:
        # A DataFrame of a single row returned (or two rows if first trade).

        # IMPLEMENTATION:
        # The very first trade identified by _cumulativeEquity = None
        # Then two rows are returned;
        # i.e.
        # TRADE1
        # ROW1: entry_date of TRADE1, 0
        #  _cumulativeEquity = 0 + pnl of TRADE1
        # ROW2: exit_date of TRADE1, _cumulativeEquity

        # TRADE2
        # _cumulativeEquity = _cumulativeEquity + pnl of TRADE2
        # ROW3: exit_date of TRADE2, _cumulativeEquity

        if self._cumulativeEquity == None:
            # First time, initialise. Generate two rows..
            self._cumulativeEquity = trade.pnlcomm
            _trade = pd.DataFrame([{
                'date': trade.open_datetime(),
                'equity': 0
            }, {
                'date': trade.close_datetime(),
                'equity': self._cumulativeEquity
            }])

        else:
            # Not first time, so add latest value of pnl to current equity..
            self._cumulativeEquity += trade.pnlcomm
            _trade = pd.DataFrame([{
                'date': trade.close_datetime(),
                'equity': self._cumulativeEquity
            }])

        return _trade

    def print(self, *args, **kwargs):
        '''
        Overide print method to display length of list rather than contents.
        (We don't want e.g. 1000 trades to be displayed.)
        '''
        print('TradeRecorder:')
        print(f'  - openTrades = list of length {len(self.rets.openTrades)}')
        print(
            f'  - closedTrades = list of length {len(self.rets.closedTrades)}')
Example #29
0
 def start(self):
     self.ret = AutoOrderedDict()
     self.pnl = list()
     self.count = 0
Example #30
0
 def get_dict(self):
     return AutoOrderedDict() if not self.p.useStandardDict else dict()
Example #31
0
class DrawDown(bt.Analyzer):
    '''This analyzer calculates trading system drawdowns stats such as drawdown
    values in %s and in dollars, max drawdown in %s and in dollars, drawdown
    length and drawdown max length

    Params:

      - ``fund`` (default: ``None``)

        If ``None`` the actual mode of the broker (fundmode - True/False) will
        be autodetected to decide if the returns are based on the total net
        asset value or on the fund value. See ``set_fundmode`` in the broker
        documentation

        Set it to ``True`` or ``False`` for a specific behavior

    Methods:

      - ``get_analysis``

        Returns a dictionary (with . notation support and subdctionaries) with
        drawdown stats as values, the following keys/attributes are available:

        - ``drawdown`` - drawdown value in 0.xx %
        - ``moneydown`` - drawdown value in monetary units
        - ``len`` - drawdown length

        - ``max.drawdown`` - max drawdown value in 0.xx %
        - ``max.moneydown`` - max drawdown value in monetary units
        - ``max.len`` - max drawdown length
    '''

    params = (
        ('fund', None),
    )

    def start(self):
        super(DrawDown, self).start()
        if self.p.fund is None:
            self._fundmode = self.strategy.broker.fundmode
        else:
            self._fundmode = self.p.fund

    def create_analysis(self):
        self.rets = AutoOrderedDict()  # dict with . notation

        self.rets.len = 0
        self.rets.drawdown = 0.0
        self.rets.moneydown = 0.0

        self.rets.max.len = 0.0
        self.rets.max.drawdown = 0.0
        self.rets.max.moneydown = 0.0

        self._maxvalue = float('-inf')  # any value will outdo it

    def stop(self):
        self.rets._close()  # . notation cannot create more keys

    def notify_fund(self, cash, value, fundvalue, shares):
        if not self._fundmode:
            self._value = value  # record current value
            self._maxvalue = max(self._maxvalue, value)  # update peak value
        else:
            self._value = fundvalue  # record current value
            self._maxvalue = max(self._maxvalue, fundvalue)  # update peak

    def next(self):
        r = self.rets

        # calculate current drawdown values
        r.moneydown = moneydown = self._maxvalue - self._value
        r.drawdown = drawdown = 100.0 * moneydown / self._maxvalue

        # maxximum drawdown values
        r.max.moneydown = max(r.max.moneydown, moneydown)
        r.max.drawdown = maxdrawdown = max(r.max.drawdown, drawdown)

        r.len = r.len + 1 if drawdown else 0
        r.max.len = max(r.max.len, r.len)