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 likebarssince
,valuewhen
from pinescript, which allows conditions like:setupCountUp == self.p.setup_bars
the easiest method I found is to use numpy for this.- 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))
- 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:
- look for vars, params, signals used in pinescript
- define vars, params, signal in indicator
- strip all plot related code from pinescript
- 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/ -
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
-
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
-
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]
-
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.
-
@dasch
sorry to write in an old post. I need to convert an indicator that I use to python. but I'm having difficulty in the valuewhen function. I saw that you have a lot of experience in this. Would you be able to help me or else do the service for me. Sorry for my english, I'm from Brazil -
@jcgeraldi His email is in his profile.