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

converting pinescript indicators



  • Hi

    I have worked on converting a Pinescript indicator to backtrader.

    I will describe some findings in the process, give some tips, show a example and so on.

    variables
    in pinescript, variables seem to work like lines in backtrader. So for every variable used in the script I have added a line:

    pinescript:

    setupCountUp = na
    

    backtrader ind.__init__:

    self.setup_sell = bt.LineNum(float('nan'))
    

    conditions
    to use methods like barssince, valuewhen from pinescript, which allows conditions like: setupCountUp == self.p.setup_bars the easiest method I found is to use numpy for this.

    1. convert a line to a numpy array (using get(size=SIZE) to not work on complete line but only a defined range of data)
    def line2arr(line, size=-1):
        if size <= 0:
            return np.array(line.array)
        else:
            return np.array(line.get(size=size))
    
    1. provide methods which works as methods from pinescript: so I wrote the methods I needed.
    def na(val):
        return val != val
    
    def nz(x, y=None):
        if isinstance(x, np.generic):
            return x.fillna(y or 0)
        if x != x:
            if y is not None:
                return y
            return 0
        return x
    
    def barssince(condition, occurrence=0):
        cond_len = len(condition)
        occ = 0
        since = 0
        res = float('nan')
        while cond_len - (since+1) >= 0:
            cond = condition[cond_len-(since+1)]
            if cond and not cond != cond:
                if occ == occurrence:
                    res = since
                    break
                occ += 1
            since += 1
        return res
    
    def valuewhen(condition, source, occurrence=0):
        res = float('nan')
        since = barssince(condition, occurrence)
        if since is not None:
            res = source[-(since+1)]
        return res
    

    rewriting

    the process of rewriting the pinescript in python using backtrader was simple after this. basically it can be split up to:

    1. look for vars, params, signals used in pinescript
    2. define vars, params, signal in indicator
    3. strip all plot related code from pinescript
    4. rewrite pinescript code in indicators next method

    plotting

    since i only want to have signals as lines in indicator, I did not set all vars used in pinescript in the lines dict, but in init. So I had a better control of what the indicator is plotting. But the indicator had more values then just the signals, so how to handle them?
    The solution was a observer, which takes care of plotting all other information, the indicator has. Here you can setup all the plot related code from pinescript.

    some remaining issues

    maybe some of you guys know answers for this:

    Questions:

    • is it possible to create lines with conditions (outside of __init__) like: self.line1 > val ?
    • is there a way to get something like barssince, valuewhen natively in backtrader?

    Issues:

    • the code is not very good, since a lot of the code can be done better in python, but if you don't want to fiddle to much with it, this should be fine
    • refactoring would make the code better readable

    I hope, this will help some of you!
    I will post the code of a indicator, observer and the pinescript methods in a next post below.



  • below there is code for the indicator from:
    https://www.tradingview.com/script/t08BkTIg-TD-Sequential/



  • pinescript.py

    import numpy as np
    
    
    def line2arr(line, size=-1):
        '''
        Creates an numpy array from a backtrader line
    
        This method wraps the lines array in numpy. This can
        be used for conditions.
        '''
        if size <= 0:
            return np.array(line.array)
        else:
            return np.array(line.get(size=size))
    
    
    def na(val):
        '''
        RETURNS
        true if x is not a valid number (x is NaN), otherwise false.
        '''
        return val != val
    
    
    def nz(x, y=None):
        '''
        RETURNS
        Two args version: returns x if it's a valid (not NaN) number, otherwise y
        One arg version: returns x if it's a valid (not NaN) number, otherwise 0
        ARGUMENTS
        x (val) Series of values to process.
        y (float) Value that will be inserted instead of all NaN values in x series.
        '''
        if isinstance(x, np.generic):
            return x.fillna(y or 0)
        if x != x:
            if y is not None:
                return y
            return 0
        return x
    
    
    def barssince(condition, occurrence=0):
        '''
        Impl of barssince
    
        RETURNS
        Number of bars since condition was true.
        REMARKS
        If the condition has never been met prior to the current bar, the function returns na.
        '''
        cond_len = len(condition)
        occ = 0
        since = 0
        res = float('nan')
        while cond_len - (since+1) >= 0:
            cond = condition[cond_len-(since+1)]
            # check for nan cond != cond == True when nan
            if cond and not cond != cond:
                if occ == occurrence:
                    res = since
                    break
                occ += 1
            since += 1
        return res
    
    
    def valuewhen(condition, source, occurrence=0):
        '''
        Impl of valuewhen
        + added occurrence
    
        RETURNS
        Source value when condition was true
        '''
        res = float('nan')
        since = barssince(condition, occurrence)
        if since is not None:
            res = source[-(since+1)]
        return res
    
    


  • indicator.py

    import backtrader as bt
    from utils.pinescript import line2arr, barssince, valuewhen, na, nz
    
    
    class TDSequentialInd(bt.Indicator):
    
        '''
        TD Sequential
    
        This indicator implements a flexible rendition of TD Sequentials
        Reference: DeMark Indicators by Jason Perl
    
        based on:
        https://www.tradingview.com/script/t08BkTIg-TD-Sequential/
        http://practicaltechnicalanalysis.blogspot.com/2013/01/tom-demark-sequential.html
    
    
        TD Indicators:
        - TD Price Flips (setup_count_up/setup_count_down = 1)
        - TD Setup count up (self.setup_count_up)
        - TD Setup count down (self.setup_count_down)
        - TD Sell Setup (self.setup_sell: price, self.l.setup_sell: signal)
        - TD Sell Setup perfected (self.setup_sell_perf: price, self.l.setup_sell_perf: signal), can be deferred
        - TD Buy Setup (self.setup_buy: price, self.l.setup_buy: signal)
        - TD Buy Setup perfected (self.setup_buy_perf: price, self.l.setup_buy_perf: signal), can be deferred
        - TD Setup Trend support (self.setup_trend_support)
        - TD Setup Trend resistance (self.setup_trend_resistance)
        - TD Countdown up (self.countdown_up)
        - TD Countdown down (self.countdown_down)
        - TD Sell Countdown qualify bar (self.countdown_count_up_imp == self.p.countdown_qual_bar)
        - TD Sell Countdown deferred (self.countdown_sell_defer: price, self.l.countdown_sell_defer: signal)
        - TD Sell Countdown (self.countdown_sell: price, self.l.countdown_sell: signal)
        - TD Buy Countdown qualify bar (self.countdown_count_down_imp == self.p.countdown_qual_bar)
        - TD Buy Countdown deferred (self.countdown_buy_defer: price, self.l.countdown_buy_defer: signal)
        - TD Buy Countdown (self.countdown_buy: price, self.l.countdown_buy: signal)
        - TD Countdown Recycling (self.countdown_count_up_recycle: price, self.l.countdown_count_up_recycle: signal,
                                 self.countdown_count_down_recycle: price, self.l.countdown_count_down_recycle: signal)
            Note: Only one aspect of recycling is implemented where,
                if Setup Up/Down Count == 2 * 'Setup: Bars', then the
                present Countdown is cancelled.
                Trend momentum has intensified.
        - TD Risk Level (self.risk_level)
        '''
    
        DATA_CANDLES = 200  # use up to 200 candles for analysis
    
        '''
        setupIsPerfected = 2
        setupIsDeferred = 1
        '''
        SETUP_IS_PERFECTED = 2
        SETUP_IS_DEFERRED = 1
        '''
        cntdwnIsQualified = 2
        cntdwnIsDeferred = 1
        '''
        COUNTDOWN_IS_QUALIFIED = 2
        COUNTDOWN_IS_DEFERRED = 1
    
        plotinfo = dict(subplot=True)
    
        lines = (
            'setup_sell',                   # sell setup
            'setup_buy',                    # buy setup
            'setup_sell_perf',              # sell setup perfected
            'setup_buy_perf',               # buy setup perfected
            'countdown_sell',               # sell countdown
            'countdown_buy',                # buy countdown
            'countdown_sell_defer',
            'countdown_buy_defer',
            'countdown_count_up_recycle',
            'countdown_count_down_recycle',
        )
    
        params = dict(
            # data source to use, either close, hgih or low (Default: close)
            data_source='close',
            # the last Setup count (traditionally 9).
            setup_bars=9,
            # defines the previous bar to compare for counting (traditionally 4).
            setup_lookback_bars=4,
            # defines the previous count to evaluate for a perfected setup
            # (this count and the next). Traditionally 3, i.e compare
            # count 6 and 7 to count 8 or count 9.
            setup_perf_lookback=3,
            # If enabled, allow >= or <= in price comparisons.
            setup_equal_enable=False,
            # If disabled, only look back to the beginning of
            # this Buy/Sell Setup event
            # to find trends, low(support for Sell) or high(resistance for Buy)
            # If enabled, look back beyond this Buy/Sell Setup event to
            # the previous Setup event of the same kind.
            setup_trend_extend=False,
            # the last Countdown count (traditionally 13).
            # Called the Buy/Sell Countdown event in this code, i.e. the
            # last up/down count becomes the Buy/Sell Countdown event.
            countdown_bars=13,
            # define previous bar to compare for counting (traditionally 2).
            countdown_lookback_bars=2,
            # the bar in the Countdown sequence used to qualifiy the price
            # of the Buy/Sell Countdown event(traditionally 8).
            # If a countdown event doesn't qualify, counting continues.
            # Note: If the Qualifier Bar is set >= "countdown_lookback_bars",
            # qualification is disabled. Countdown events are still
            # determined, just not qualified.
            countdown_qual_bar=8,
            # Use aggressive comparison. E.g. for Sell Countdown,
            # instead of "Price: Source" >= high of "countdown_lookback_bars",
            # use high >= high of "countdown_lookback_bars". Disabled by default.
            countdown_aggressive=False,
        )
    
        def __init__(self):
            super().__init__()
            self.addminperiod(self.p.setup_lookback_bars + 1)
    
            # data source to use
            if self.p.data_source == 'high':
                self.source = self.data_high
            elif self.p.data_source == 'low':
                self.source = self.data_low
            else:
                self.source = self.data_close
    
            # setup prices
            self.setup_price_up = (
                self.source(0) > self.source(-self.p.setup_lookback_bars))
            self.setup_price_down = (
                self.source(0) < self.source(-self.p.setup_lookback_bars))
            self.setup_price_equal = (
                self.source(0) == self.source(-self.p.setup_lookback_bars))
    
            # countdown prices
            if self.p.countdown_aggressive:
                self.countdown_price_up = (
                    self.data_high(0)
                    >= self.data_high(-self.p.countdown_lookback_bars))
                self.countdown_price_down = (
                    self.data_low(0)
                    <= self.data_low(-self.p.countdown_lookback_bars))
            else:
                self.countdown_price_up = (
                    self.source(0)
                    >= self.data_high(-self.p.countdown_lookback_bars))
                self.countdown_price_down = (
                    self.source(0)
                    <= self.data_low(-self.p.countdown_lookback_bars))
    
            # indicators
            self.tr = bt.ind.TrueRange()
    
            # values
            self.setup_sell = bt.LineNum(float('nan'))
            self.setup_buy = bt.LineNum(float('nan'))
            self.setup_count_up = bt.LineNum(float('nan'))
            self.setup_count_down = bt.LineNum(float('nan'))
            self.setup_sell_perf_price = bt.LineNum(float('nan'))
            self.setup_sell_perf_mask = bt.LineNum(float('nan'))
            self.setup_sell_perf = bt.LineNum(float('nan'))
            self.setup_buy_perf_price = bt.LineNum(float('nan'))
            self.setup_buy_perf_mask = bt.LineNum(float('nan'))
            self.setup_buy_perf = bt.LineNum(float('nan'))
            self.setup_sell_count = bt.LineNum(float('nan'))
            self.setup_buy_count = bt.LineNum(float('nan'))
            self.setup_trend_support = bt.LineNum(float('nan'))
            self.setup_trend_resistance = bt.LineNum(float('nan'))
            self.countdown_count_up = bt.LineNum(float('nan'))
            self.countdown_count_down = bt.LineNum(float('nan'))
            self.countdown_count_up_imp = bt.LineNum(float('nan'))
            self.countdown_count_down_imp = bt.LineNum(float('nan'))
            self.countdown_count_up_recycle = bt.LineNum(float('nan'))
            self.countdown_count_down_recycle = bt.LineNum(float('nan'))
            self.countdown_sell = bt.LineNum(float('nan'))
            self.countdown_sell_defer = bt.LineNum(float('nan'))
            self.countdown_sell_qual_price = bt.LineNum(float('nan'))
            self.countdown_sell_qual_mask = bt.LineNum(float('nan'))
            self.countdown_sell_qual_mask_imp = bt.LineNum(float('nan'))
            self.countdown_buy = bt.LineNum(float('nan'))
            self.countdown_buy_defer = bt.LineNum(float('nan'))
            self.countdown_buy_qual_price = bt.LineNum(float('nan'))
            self.countdown_buy_qual_mask = bt.LineNum(float('nan'))
            self.countdown_buy_qual_mask_imp = bt.LineNum(float('nan'))
            self.risk_level = bt.LineNum(float('nan'))
    
        def next(self):
    
            #
            # ---- TD Setups ----------------------------
            #
    
            if self.p.setup_equal_enable:
                if (self.setup_price_up[0]
                    or (self.setup_count_up[-1]
                        and self.setup_price_equal[0])):
                    self.setup_count_up[0] = nz(self.setup_count_up[-1]) + 1
                if (self.setup_price_down[0]
                    or (self.setup_count_down[-1]
                        and self.setup_price_equal[0])):
                    self.setup_count_down[0] = nz(self.setup_count_down[-1]) + 1
            else:
                if self.setup_price_up[0]:
                    self.setup_count_up[0] = nz(self.setup_count_up[-1]) + 1
                if self.setup_price_down[0]:
                    self.setup_count_down[0] = nz(self.setup_count_down[-1]) + 1
    
            if self.setup_count_up[0] == self.p.setup_bars:
                setupCountUp = line2arr(self.setup_count_up, self.DATA_CANDLES)
                priceSource = line2arr(self.source, self.DATA_CANDLES)
                self.setup_sell[0] = valuewhen(
                    setupCountUp == self.p.setup_bars,
                    priceSource,
                    0)
    
            if self.setup_count_down[0] == self.p.setup_bars:
                setupCountDown = line2arr(self.setup_count_down, self.DATA_CANDLES)
                priceSource = line2arr(self.source, self.DATA_CANDLES)
                self.setup_buy[0] = valuewhen(
                    setupCountDown == self.p.setup_bars,
                    priceSource,
                    0)
    
            self.setup_sell_count[0] = barssince(
                line2arr(self.setup_sell, self.DATA_CANDLES))
            self.setup_buy_count[0] = barssince(
                line2arr(self.setup_buy, self.DATA_CANDLES))
            if self.setup_count_up[0] == self.p.setup_bars:
                val = None
                setupCountUp = line2arr(self.setup_count_up, self.DATA_CANDLES)
                high = line2arr(self.data.high, self.DATA_CANDLES)
                cond1 = valuewhen(
                    setupCountUp == (self.p.setup_bars
                                     - self.p.setup_perf_lookback),
                    high, 0)
                cond2 = valuewhen(
                    setupCountUp == (self.p.setup_bars
                                     - self.p.setup_perf_lookback + 1),
                    high, 0)
                if (cond1 >= cond2):
                    val = cond1
                else:
                    val = cond2
                self.setup_sell_perf_price[0] = val
            else:
                self.setup_sell_perf_price[0] = nz(self.setup_sell_perf_price[-1])
    
            if self.setup_count_down[0] == self.p.setup_bars:
                val = None
                setupCountDown = line2arr(self.setup_count_down, self.DATA_CANDLES)
                low = line2arr(self.data.high, self.DATA_CANDLES)
                cond1 = valuewhen(
                    setupCountDown == (self.p.setup_bars
                                       - self.p.setup_perf_lookback),
                    low, 0)
                cond2 = valuewhen(
                    setupCountDown == (self.p.setup_bars
                                       - self.p.setup_perf_lookback + 1),
                    low, 0)
                if cond1 >= cond2:
                    val = cond1
                else:
                    val = cond2
                self.setup_buy_perf_price[0] = val
            else:
                self.setup_buy_perf_price[0] = nz(self.setup_buy_perf_price[-1])
    
            if not (nz(self.setup_sell_perf_mask[-1])
                    >= self.SETUP_IS_PERFECTED or (not na(self.setup_buy[0]))):
                val = None
                if self.setup_count_up[0] == self.p.setup_bars:
                    high = line2arr(self.data_high, self.DATA_CANDLES)
                    setupCountUp = line2arr(
                        self.setup_count_up, self.DATA_CANDLES)
                    if (
                        (valuewhen(setupCountUp == (self.p.setup_bars-1), high, 0)
                            >= self.setup_sell_perf_price[0])
                        or (valuewhen(setupCountUp == self.p.setup_bars, high, 0)
                            >= self.setup_sell_perf_price[0])):
                        val = self.SETUP_IS_PERFECTED
                    else:
                        val = self.SETUP_IS_DEFERRED
                elif not na(self.setup_sell_perf_mask[-1]):
                    if self.data_high[0] >= self.setup_sell_perf_price[0]:
                        val = self.SETUP_IS_PERFECTED
                    else:
                        val = self.setup_sell_perf_mask[-1]
                if val is not None:
                    self.setup_sell_perf_mask[0] = val
    
            if not (nz(self.setup_buy_perf_mask[-1])
                    >= self.SETUP_IS_PERFECTED or (not na(self.setup_sell[0]))):
                val = None
                if self.setup_count_down[0] == self.p.setup_bars:
                    low = line2arr(self.data_low, self.DATA_CANDLES)
                    setupCountDown = line2arr(
                        self.setup_count_down, self.DATA_CANDLES)
                    if (
                        (valuewhen(setupCountDown == (self.p.setup_bars-1), low, 0)
                            <= self.setup_buy_perf_price[0]) or
                        (valuewhen(setupCountDown == self.p.setup_bars, low, 0)
                            <= self.setup_buy_perf_price[0])):
                        val = self.SETUP_IS_PERFECTED
                    else:
                        val = self.SETUP_IS_DEFERRED
                elif not na(self.setup_sell_perf_mask[-1]):
                    if self.data_low[0] >= self.setup_sell_perf_price[0]:
                        val = self.SETUP_IS_PERFECTED
                    else:
                        val = self.setup_buy_perf_mask[-1]
                if val is not None:
                    self.setup_buy_perf_mask[0] = val
    
            if self.setup_sell_perf_mask[0] == self.SETUP_IS_PERFECTED:
                self.setup_sell_perf[0] = self.source[0]
            if self.setup_buy_perf_mask[0] == self.SETUP_IS_PERFECTED:
                self.setup_buy_perf[0] = self.source[0]
            if self.setup_sell[0]:
                if self.p.setup_trend_extend and self.setup_sell_count[-1] > 0:
                    extend = int((
                        ((self.setup_sell_count[-1]
                          - (self.setup_sell_count[-1] % self.p.setup_bars))
                         / self.p.setup_bars)
                        + 1) * self.p.setup_bars)
                    self.setup_trend_support[0] = min(
                        self.data_low.get(size=extend))
                else:
                    self.setup_trend_support[0] = min(
                        self.data_low.get(size=self.p.setup_bars))
            else:
                self.setup_trend_support[0] = nz(self.setup_trend_support[-1])
    
            if self.setup_buy[0]:
                if self.p.setup_trend_extend and self.setup_buy_count[-1] > 0:
                    extend = int((
                        ((self.setup_buy_count[-1]
                          - (self.setup_buy_count[-1] % self.p.setup_bars))
                         / self.p.setup_bars)
                        + 1) * self.p.setup_bars)
                    self.setup_trend_resistance[0] = max(
                        self.data_high.get(size=extend))
                else:
                    self.setup_trend_resistance[0] = max(
                        self.data_high.get(size=self.p.setup_bars))
            else:
                self.setup_trend_resistance[0] = nz(self.setup_trend_resistance[-1])
    
            if self.setup_count_up[0] == (2 * self.p.setup_bars):
                setupCountUp = line2arr(self.setup_count_up, self.DATA_CANDLES)
                priceSource = line2arr(self.source, self.DATA_CANDLES)
                self.countdown_count_up_recycle[0] = (
                    valuewhen(
                        (setupCountUp == (2 * self.p.setup_bars)),
                        priceSource,
                        0)
                )
            if self.setup_count_down[0] == (2 * self.p.setup_bars):
                setupCountDown = line2arr(self.setup_count_down, self.DATA_CANDLES)
                priceSource = line2arr(self.source, self.DATA_CANDLES)
                self.countdown_count_down_recycle[0] = (
                    valuewhen(
                        (setupCountDown == (2 * self.p.setup_bars)),
                        priceSource,
                        0)
                )
    
            if na(self.countdown_price_up[0]):
                self.countdown_count_up[0] = self.countdown_count_up[-1]
            else:
                val = None
                if not (
                        (not na(self.setup_buy[0])
                            or (self.source[0] < self.setup_trend_support[0]))
                        or (not na(self.countdown_count_up_recycle[0]))):
                    if not na(self.setup_sell[0]):
                        if self.countdown_price_up[0]:
                            val = 1
                        else:
                            val = 0
                    elif not na(self.countdown_count_up[-1]):
                        if self.countdown_price_up[0]:
                            val = self.countdown_count_up[-1] + 1
                        else:
                            val = self.countdown_count_up[-1]
                if val is not None:
                    self.countdown_count_up[0] = val
            if na(self.countdown_price_down[0]):
                self.countdown_count_down[0] = self.countdown_count_down[-1]
            else:
                val = None
                if not (
                        (not na(self.setup_sell[0])
                            or (self.source[0] > self.setup_trend_resistance[0]))
                        or (not na(self.countdown_count_down_recycle[0]))):
                    if not na(self.setup_buy[0]):
                        if self.countdown_price_down[0]:
                            val = 1
                        else:
                            val = 0
                    elif not na(self.countdown_count_down[-1]):
                        if self.countdown_price_down[0]:
                            val = self.countdown_count_down[-1] + 1
                        else:
                            val = self.countdown_count_down[-1]
                if val is not None:
                    self.countdown_count_down[0] = val
    
            if self.countdown_price_up[0]:
                self.countdown_count_up_imp[0] = self.countdown_count_up[0]
            if self.countdown_price_down[0]:
                self.countdown_count_down_imp[0] = self.countdown_count_down[0]
        
            if self.p.countdown_qual_bar < self.p.countdown_bars:
    
                if self.countdown_count_up_imp[0] == self.p.countdown_qual_bar:
                    cntdwnCountUpImp = line2arr(
                        self.countdown_count_up_imp,
                        self.DATA_CANDLES)
                    priceSource = line2arr(
                        self.source,
                        self.DATA_CANDLES)
                    self.countdown_sell_qual_price[0] = valuewhen(
                        cntdwnCountUpImp == self.p.countdown_qual_bar,
                        priceSource,
                        0)
                else:
                    self.countdown_sell_qual_price[0] = self.countdown_sell_qual_price[-1]
    
                if self.countdown_count_down_imp[0] == self.p.countdown_qual_bar:
                    cntdwnCountDownImp = line2arr(
                        self.countdown_count_down_imp,
                        self.DATA_CANDLES)
                    priceSource = line2arr(
                        self.source,
                        self.DATA_CANDLES)
                    self.countdown_buy_qual_price[0] = valuewhen(
                        cntdwnCountDownImp == self.p.countdown_qual_bar,
                        priceSource,
                        0)
                else:
                    self.countdown_buy_qual_price[0] = self.countdown_buy_qual_price[-1]
    
                if not ((nz(self.countdown_sell_qual_mask[-1])
                         >= self.COUNTDOWN_IS_QUALIFIED)
                        or na(self.countdown_count_up[-1])):
                    val = None
                    cntdwnCountUpImp = line2arr(
                        self.countdown_count_up_imp, self.DATA_CANDLES)
                    high = line2arr(self.data_high, self.DATA_CANDLES)
                    if self.countdown_count_up_imp[0] == self.p.countdown_bars:
                        if (valuewhen(
                                cntdwnCountUpImp == self.p.countdown_bars,
                                high,
                                0)) >= self.countdown_sell_qual_price[0]:
                            val = self.COUNTDOWN_IS_QUALIFIED
                        else:
                            val = self.COUNTDOWN_IS_DEFERRED
                    elif not na(self.countdown_sell_qual_mask[-1]):
                        if self.countdown_count_up_imp[0] > self.p.countdown_bars:
                            if (valuewhen(
                                    cntdwnCountUpImp > self.p.countdown_bars,
                                    high,
                                    0)) >= self.countdown_sell_qual_price[0]:
                                val = self.COUNTDOWN_IS_QUALIFIED
                            else:
                                val = self.countdown_sell_qual_mask[-1]
                        else:
                            val = self.countdown_sell_qual_mask[-1]
                    if val is not None:
                        self.countdown_sell_qual_mask[0] = val
    
                if not ((nz(self.countdown_buy_qual_mask[-1])
                         >= self.COUNTDOWN_IS_QUALIFIED)
                        or na(self.countdown_count_down[-1])):
                    val = None
                    cntdwnCountDownImp = line2arr(
                        self.countdown_count_down_imp, self.DATA_CANDLES)
                    low = line2arr(self.data_low, self.DATA_CANDLES)
                    if self.countdown_count_down_imp[0] == self.p.countdown_bars:
                        if (valuewhen(
                                cntdwnCountDownImp == self.p.countdown_bars,
                                low,
                                0)) <= self.countdown_buy_qual_price[0]:
                            val = self.COUNTDOWN_IS_QUALIFIED
                        else:
                            val = self.COUNTDOWN_IS_DEFERRED
                    elif not na(self.countdown_buy_qual_mask[-1]):
                        if self.countdown_count_down_imp[0] > self.p.countdown_bars:
                            if (valuewhen(
                                    cntdwnCountDownImp > self.p.countdown_bars,
                                    low,
                                    0)) <= self.countdown_buy_qual_price[0]:
                                val = self.COUNTDOWN_IS_QUALIFIED
                            else:
                                val = self.countdown_buy_qual_mask[-1]
                        else:
                            val = self.countdown_buy_qual_mask[-1]
                    if val is not None:
                        self.countdown_buy_qual_mask[0] = val
            else:
                if self.countdown_count_up[0] == self.p.countdown_bars:
                    self.countdown_sell_qual_mask[0] = self.COUNTDOWN_IS_QUALIFIED
                if self.countdown_count_down[0] == self.p.countdown_bars:
                    self.countdown_buy_qual_mask[0] = self.COUNTDOWN_IS_QUALIFED
    
            if self.countdown_count_up_imp[0]:
                self.countdown_sell_qual_mask_imp[0] = self.countdown_sell_qual_mask[0]
            if self.countdown_count_down_imp[0]:
                self.countdown_buy_qual_mask_imp[0] = self.countdown_buy_qual_mask[0]
            if self.countdown_sell_qual_mask_imp[0] == self.COUNTDOWN_IS_QUALIFIED:
                cntdwnSellQualMaskImp = line2arr(
                    self.countdown_sell_qual_mask_imp, self.DATA_CANDLES)
                priceSource = line2arr(
                    self.source, self.DATA_CANDLES)
                self.countdown_sell[0] = valuewhen(
                    cntdwnSellQualMaskImp == self.COUNTDOWN_IS_QUALIFIED,
                    priceSource,
                    0)
            if self.countdown_sell_qual_mask_imp[0] == self.COUNTDOWN_IS_DEFERRED:
                cntdwnSellQualMaskImp = line2arr(
                    self.countdown_sell_qual_mask_imp, self.DATA_CANDLES)
                priceSource = line2arr(
                    self.source, self.DATA_CANDLES)
                self.countdown_sell_defer[0] = valuewhen(
                    cntdwnSellQualMaskImp == self.COUNTDOWN_IS_DEFERRED,
                    priceSource,
                    0)
        
            if self.countdown_buy_qual_mask_imp[0] == self.COUNTDOWN_IS_QUALIFIED:
                cntdwnBuyQualMaskImp = line2arr(
                    self.countdown_buy_qual_mask_imp, self.DATA_CANDLES)
                priceSource = line2arr(
                    self.source, self.DATA_CANDLES)
                self.countdown_buy[0] = valuewhen(
                    cntdwnBuyQualMaskImp == self.COUNTDOWN_IS_QUALIFIED,
                    priceSource,
                    0)
            if self.countdown_buy_qual_mask_imp[0] == self.COUNTDOWN_IS_DEFERRED:
                cntdwnBuyQualMaskImp = line2arr(
                    self.countdown_buy_qual_mask_imp, self.DATA_CANDLES)
                priceSource = line2arr(
                    self.source, self.DATA_CANDLES)
                self.countdown_buy_defer[0] = valuewhen(
                    cntdwnBuyQualMaskImp == self.COUNTDOWN_IS_DEFERRED,
                    priceSource,
                    0)
    
            if self.setup_sell[0] or self.countdown_count_up_recycle[0]:
                high = line2arr(self.data_high, self.p.setup_bars)
                tr = line2arr(self.tr, self.p.setup_bars)
                highest = max(high)
                self.risk_level[0] = highest + valuewhen(high == highest, tr, 0)
            elif self.setup_buy[0] or self.countdown_count_down_recycle[0]:
                low = line2arr(self.data_low, self.p.setup_bars)
                tr = line2arr(self.tr, self.p.setup_bars)
                lowest = min(low)
                self.risk_level[0] = lowest - valuewhen(low == lowest, tr, 0)
            elif self.countdown_sell[0]:
                high = line2arr(self.data_high, self.p.countdown_bars)
                tr = line2arr(self.tr, self.p.countdown_bars)
                highest = max(high)
                self.risk_level[0] = highest + valuewhen(high == highest, tr, 0)
            elif self.countdown_buy[0]:
                low = line2arr(self.data_low, self.p.countdown_bars)
                tr = line2arr(self.tr, self.p.countdown_bars)
                lowest = min(low)
                self.risk_level[0] = lowest - valuewhen(low == lowest, tr, 0)
            else:
                self.risk_level[0] = nz(self.risk_level[-1], self.data_low[0])
    
    
            # setup_sell
            self.l.setup_sell[0] = 0
            if not na(self.setup_sell[0]):
                self.l.setup_sell[0] = 1
            # setup_buy
            self.l.setup_buy[0] = 0
            if not na(self.setup_buy[0]):
                self.l.setup_buy[0] = 1
            # setup_sell_perf
            self.l.setup_sell_perf[0] = 0
            if not na(self.setup_sell_perf[0]):
                self.l.setup_sell_perf[0] = 1
            # setup_buy_perf
            self.l.setup_buy_perf[0] = 0
            if not na(self.setup_buy_perf[0]):
                self.l.setup_buy_perf[0] = 1
            # countdown_sell
            self.l.countdown_sell[0] = 0
            if not na(self.countdown_sell[0]):
                self.l.countdown_sell[0] = 1
            # countdown_buy
            self.l.countdown_buy[0] = 0
            if not na(self.countdown_buy[0]):
                self.l.countdown_buy[0] = 1
            # countdown_sell_defer
            self.l.countdown_sell_defer[0] = 0
            if not na(self.countdown_sell_defer[0]):
                self.l.countdown_sell_defer[0] = 1
            # countdown_buy_defer
            self.l.countdown_buy_defer[0] = 0
            if not na(self.countdown_buy_defer[0]):
                self.l.countdown_buy_defer[0] = 1
            # countdown_count_up_recycle
            self.l.countdown_count_up_recycle[0] = 0
            if not na(self.countdown_count_up_recycle[0]):
                self.l.countdown_count_up_recycle[0] = 1
            # countdown_count_down_recycle
            self.l.countdown_count_down_recycle[0] = 0
            if not na(self.countdown_count_down_recycle[0]):
                self.l.countdown_count_down_recycle[0] = 1
    


  • observer.py

    import backtrader as bt
    
    
    class TDSequentialObs(bt.observers.Observer):
    
        plotinfo = dict(plot=True,
                        subplot=False,
                        plotlegend=False,
                        plotlinelegend=False,
                        plotlinelabels=False,
                        plotvaluetags=False,
                        plotlinevalues=True,
                        plotabove=True,
                        plotname="TD Sequential",)
    
        lines = (
            'seq_up',               # up count marker
            'seq_down',             # down count marker
            's_buy',                # setup buy marker
            's_sell',               # setup sell marker
            's_buy_perf',           # setup buy perf markerr
            's_sell_perf',          # setup sell perf marker
            's_buy_perf_price',
            's_sell_perf_price',
            's_trend_support',
            's_trend_resistance',
            'cd_seq_up',            # countdown up marker
            'cd_seq_down',          # countdown down marker
            'cd_buy',               # countdown buy marker
            'cd_sell',              # countdown sell marker
            'cd_buy_defer',         # countdown buy defer marker
            'cd_sell_defer',        # countdown sell defer marker
            'cd_cnt_up_recycle',    # countdown up recycle marker
            'cd_cnt_down_recycle',  # countdown down recycle marker
            'risk_level'
        )
    
        plotlines = dict(
            seq_up=dict(
                marker='*', markersize=4.0, color='black', ls='',),
            seq_down=dict(
                marker='*', markersize=4.0, color='black', ls='',),
            s_buy=dict(
                marker='2', markersize=5.0, color='green',),
            s_sell=dict(
                marker='1', markersize=5.0, color='maroon',),
            s_buy_perf=dict(
                marker='^', markersize=5.0, color='green',),
            s_sell_perf=dict(
                marker='v', markersize=5.0, color='maroon',),
            cd_seq_up=dict(
                marker='*', markersize=5.0, color='blue', ls='',),
            cd_seq_down=dict(
                marker='*', markersize=5.0, color='blue', ls='',),
            cd_buy=dict(
                marker='^', markersize=10.0, color='green',),
            cd_sell=dict(
                marker='v', markersize=10.0, color='maroon',),
            cd_buy_defer=dict(
                marker='$D$', markersize=5.0, color='red', ls='',),
            cd_sell_defer=dict(
                marker='$D$', markersize=5.0, color='red', ls='',),
            cd_count_up_recycle=dict(
                marker='$R$', markersize=5.0, color='blue', ls='',),
            cd_count_down_recycle=dict(
                marker='$R$', markersize=5.0, color='blue', ls='',),
            risk_level=dict(lw=2.0, ls='--',),
        )
    
        params = dict(
            td_seq=None,
            bardist=0.0006,
            plot_setup_count=True,
            plot_setup_signals=True,
            plot_setup_tdst=True,
            plot_countdown_count=True,
            plot_countdown_signals=True,
            plot_risk_level=True
        )
    
        def __init__(self):
            self.td_seq = self.p.td_seq
            self.countdown_up_prev = None
            self.countdown_down_prev = None
    
        def _plotlabel(self):
            return [None]
    
        def _get_line(self, name):
            line = getattr(self.lines, name, False)
            return line
    
        def next(self):
            # setup signals
            if self.p.plot_setup_signals:
                # setup buy / sell signal
                if self.td_seq.l.setup_buy[0]:
                    self.l.s_buy[0] = self.data_low[0] - (3 * self.p.bardist)
                if self.td_seq.l.setup_sell[0]:
                    self.l.s_sell[0] = self.data_high[0] + (3 * self.p.bardist)
                # setup perfected buy / sell signal
                if self.td_seq.l.setup_buy_perf[0]:
                    self.l.s_buy_perf[0] = self.data_low[0] - (4 * self.p.bardist)
                if self.td_seq.l.setup_sell_perf[0]:
                    self.l.s_sell_perf[0] = self.data_high[0] + (4 * self.p.bardist)
    
            # setup count
            if self.p.plot_setup_count:
                # setup count up sequence
                if self.td_seq.setup_count_up[0] > 0:
                    self.l.seq_up[0] = self.data_low[0] + self.p.bardist
                # setup count down sequence
                if self.td_seq.setup_count_down[0] > 0:
                    self.l.seq_down[0] = self.data_high[0] - self.p.bardist
    
            # setup trend support / resistance (TDST)
            if self.p.plot_setup_tdst:
                self.l.s_trend_support[0] = self.td_seq.setup_trend_support[0]
                self.l.s_trend_resistance[0] = self.td_seq.setup_trend_resistance[0]
    
            # countdown count
            if self.p.plot_countdown_count:
                # countdown count up sequence
                if self.td_seq.countdown_count_up_imp[0] >= 0:
                    if self.td_seq.countdown_count_up_imp[0] != self.countdown_up_prev:
                        self.l.cd_seq_up[0] = self.data_low[0] + (2 * self.p.bardist)
                    self.countdown_up_prev = self.td_seq.countdown_count_up_imp[0]
                # countdown count down sequence
                if self.td_seq.countdown_count_down_imp[0] >= 0:
                    if self.td_seq.countdown_count_down_imp[0] != self.countdown_down_prev:
                        self.l.cd_seq_down[0] = self.data_high[0] - (2 * self.p.bardist)
                    self.countdown_down_prev = self.td_seq.countdown_count_down_imp[0]
    
            # countdown signals
            if self.p.plot_countdown_signals:
                # countdown_buy/sell
                if self.td_seq.l.countdown_buy[0]:
                    self.l.cd_buy[0] = self.data_low[0] - (4 * self.p.bardist)
                if self.td_seq.l.countdown_sell[0]:
                    self.l.cd_sell[0] = self.data_high[0] + (4 * self.p.bardist)
    
                # countdown_buy_defer/sell_defer
                if self.td_seq.l.countdown_buy_defer[0]:
                    self.l.cd_buy_defer[0] = self.data_low[0] - (3 * self.p.bardist)
                if self.td_seq.l.countdown_sell_defer[0]:
                    self.l.cd_sell_defer[0] = self.data_high[0] + (3 * self.p.bardist)
    
                # countdown_up_recycle/down_recycle
                if self.td_seq.l.countdown_count_up_recycle[0]:
                    self.l.cd_cnt_up_recycle[0] = self.data_low[0] + (3 * self.p.bardist)
                if self.td_seq.l.countdown_count_down_recycle[0]:
                    self.l.cd_cnt_down_recycle[0] = self.data_high[0] - (3 * self.p.bardist)
    
            # risk level
            if self.p.plot_risk_level:
                self.l.risk_level[0] = self.td_seq.risk_level[0]
    


  • strategy.py

    class StrategyTDSequential(bt.Strategy):
    
        def __init__(self):
            '''Initialization '''
            self.td_seq = TDSequentialInd()
            self._addobserver(
                False,
                TDSequentialObs,
                self.data,
                td_seq=self.td_seq,
                plot_setup_signals=True,
                plot_setup_count=True,
                plot_setup_tdst=True,
                plot_countdown_signals=True,
                plot_countdown_count=True,
                plot_risk_level=True)
        
    


  • I had to strip all comments from the indicator since it was too big to post, if anyone is interested in complete code, just send me a message.



  • Hello, I just started with backtrader recently and have a noob question. I'm not really converting pine script (that was the original plan though) but I have been programming in pine for about 2 years so i'm very used to it. I'm trying to program in a similar way using backtrader.

    This is probably super simple for you guys, but how does one make a variable in backtrader which acts like open/high/low/close whereas you can reference its' current candle value as var[0] and 10 candles back would be var[-10] and so on? I thought I could do it as a line, but when I try to set myLine[0] it doesn't let me. I need to be able to set its' current value and change it, and have that change apply to it when you reference it using myLine[-1] and myLine[-2] etc. while moving through the candles. Any insight would really be helpful because these docs are very extensive (which I very much appreciate) but the terminology gets confusing and i'm having trouble figuring this out. Once I solve this I can create most of my strategy very quickly because I can then program like i'm using pine. Thanks in advance for any help or advice anyone can offer.



  • @Wayne-Filkins check out the code above, there is an example how to do this.

    it would be this, what you want, I think:

    self.line = bt.LineNum(float('nan'))
    

    without the need to define the lines dict.

    Also check out the pinescript.py where I added some methods from pinescript.



  • Yes if I had scrolled further in your code blocks I probably would have figured that out. Hadn't had coffee yet haha. I saw some of the functions you made like nz() and barssince(). This is something I was wanting to do but would probably take me forever so really thankful you are sharing them!



  • @dasch Hey man if you have a minute, i'm trying to get bases (basically like looking 50 candles back and comparing that candle to the 50 before it and 50 after it to see if it's the lowest) and it's not quite working. I declared the base in init() with:

    self.base = bt.LineNum(float('1.0'))
    

    So basically in next() it starts out as 1, and I loop through the last 100 candles to try to compare to see if any are lower, and if any are lower it changes it to 0 (so it's not a pivot). This is the code in next():

            for i in range(-2 * 50, -1):
                if self.datalow[-2 * 50] in locals():
                    if self.datalow[i] < self.datalow[-1 * 50]:
                        self.base = 0.0
    
            if self.base == 1.0:
                print("we have a pivot!")
    

    So yeah basically i'm trying to compare the last 100 candles to close[-50], and do this on every iteration of next(). Any idea why this isn't working? self.base just stays as 1.0 the whole time. Thanks in advance for anyone who can help with this!



  • @dasch I did all that in strategy.py. Do I have to make an indicator.py and observer.py too to do stuff like this?



  • @dasch NM I got it figured out! base didn't have [0] on it, and the thing that was really messing it up was the "in locals()" thing wasn't working so I had to do a counter so that it didn't get the out of range error on the first 50 candles, since you know it can't look back 50 candles at the beginning if there aren't that many. Had to do that stuff a lot in pine script, always had a bunch of various counters lol. Do you know how I can plot these base/pivots with a dot on the low of the candle?



  • @dasch Can you send the complete code so I can see the comments? It's pretty confusing. Idk how to send a message on this goofy platform though



  • Sure, send me an email. Mine email is in my profile.


Log in to reply
 

});