Plotting text over bars
-
Hi @backtrader
I discovered Backtrader only recently, and it is great! It already makes impact in my work! Thanks!
Newbie question: how can one create indicator which plots indicator line as a text above bars?
For illustration see Sequential indicator:
https://www.mql5.com/en/market/product/25007
Thank you for your time and help
/Marek -
No it cannot be done.
backtrader
was not conceived as a charting/plotting platform and such things would require metadata and metaactions to indicate that it is text what you want to plot (text which obviously varies) -
I am also missing this functionality from Trading View. Is there anything out there that allows you to do this? I suppose we would need to write a generic charter based on d3. The backtrader_plotting package shows how we could do this.
-
You can do it this way:
# -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) # Import the backtrader platform import backtrader as bt import backtrader.indicators as btind from backtrader.observer import Observer as btobserver import datetime import time ''' TD Sequential-Style indicator // S = Sell Signal // B = Buy Signal // A = Sell (above bar) or Buy (below bar) Signal that is 'not ideal' - meaning the signal bar's or the previous bar's high or low is not higer or lower than one of the previous 2 bars. // - This could indicate that waiting another bar will deliver a 'true' signal. (Useful at the beginning of the trend reversal signal, not so much at the end) // // Indicator and Rules based on indicator as seen on CoinSheet.org Ein TD Sequential Buy Setup ist erfüllt, wenn 9 aufeinanderfolgende Bars tiefer Schliessen als 4 Bars zuvor. Für den Sell ist es umgekehrt, dort müssen 9 aufeinanderfolgende Bars höher Schliessen als 4 Bars zuvor. Das Ziel der Zählung ist es, mögliche Wendepunkte anzuzeigen und somit Low Risk Entrys zu finden. Falls die Bedingungen nicht bis zum 9er Count erfüllt wird, ist das Setup gecancelt. Sobald ein TD Setup erfüllt ist, würde normalerweise die Countdown resp. Intersection-Phase beginnen. Beim Countdown werden danach 13 Bars die einen tiefern Close haben, als das Low des zwei Bars zurückliegenden Stabes. Die Countdown Bars müssen jedoch nicht aufeinanderfolgen, die Zählung läuft weiter bis zu Bar 13, sofern nicht das Hoch des vorangehenden TD Setups (bei einem Buy Setup...sonst natürlich das Tief bei einem Sell) überschritten wird. Das ist jetzt wirklich nur ein ganz grober Überblick der Standartparameter.??? Beim angehängten Indi handelt es sich übrigens um den TD Combo und nicht den TD Sequential. Der Unterschied zwischen den beiden liegt einfach gesagt darin, dass die Countdown Zählung beim erreichen eines kompletten TD Setups, die Countdownzählung zürückrechnet und somit schon beim ersten Bar des Setups beginnen kann, anstatt erst nach der Beendigung des Setups.:P Grob gesagt kann beim erfüllen eines Setups mit einem kleinen Wendepunkt gerechnet werden und beim erfüllen eines Countdowns ein grosser Wendepunkt. Entry: There are three methods of entry: (a) Enter on the close of the day on which the countdown is completed; (b) Long Trades: Enter on the close if Close[i] > Close[i - 4]; Short Trades: Enter on the close if Close[i] < Close[i - 4]; (c) Long Trades: Enter on the close if Close[i] > High[i - 2]; Short Trades: Enter on the close if Close[i] < Low[i - 2]. Index: i ~ Current Bar. In this test we apply the 2nd method. Exit: Pattern Exit: The current position is liquidated once the new setup is complete and price fails to exceed the furthest price recorded by the recent inactive setup. Stop Loss Exit: Long Trades: The difference between the close at the entry bar and the low of the lowest day of the pattern is subtracted from the low of the lowest day. Short Trades: The difference between the high of the highest day of the pattern and the close at the entry bar is added to the high of the highest day. Note: In order to activate a stop loss, the close must exceed the calculated levels. ''' class strategy(bt.Strategy): params = dict( candles_past_to_compare = 4, stoploss_dist = 0.0, ) ''' Logging function for strategy ''' def log(self, txt, dt=None): dt = dt or self.dataprimary.datetime.datetime(0) print('%s, %s' % (dt.isoformat(), txt)) '''Initialization ''' def __init__(self): # Keep a reference to the primary data feed self.dataprimary = self.datas[0] # Keep a reference to the "close" line in the primary dataseries self.dataclose = self.dataprimary.close # Control vars self.live = True self.order = None self.bearishFlip = False self.bullishFlip = False self.tdsl = 0 # TD sequence long self.tdss = 0 # TD sequence short self.buySetup = False # TD buy setup self.sellSetup = False # TD sell setup self.buyCountdown = 0 # TD buy countdown self.sellCountdown = 0 # TD sell countdown self.buyVal = 0 # buy value at intersection self.sellVal = 0 # sell value at intersection self.buySig = False self.idealBuySig = False self.sellSig = False self.idealSellSig = False print('-' * 80) print('Strategy Created') print('-' * 80) ''' Data notification ''' def notify_data(self, data, status, *args, **kwargs): print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args) if status == data.LIVE: self.live = True elif status == data.DELAYED: self.live = False ''' Store notification ''' def notify_store(self, msg, *args, **kwargs): print('*' * 5, 'STORE NOTIF:', msg) ''' Order notification ''' def notify_order(self, order): if order.status in [order.Completed, order.Cancelled, order.Rejected]: self.order = None print('-' * 50, 'ORDER BEGIN', datetime.datetime.now()) print(order) print('-' * 50, 'ORDER END') ''' Trade notification ''' def notify_trade(self, trade): print('-' * 50, 'TRADE BEGIN', datetime.datetime.now()) print(trade) print('-' * 50, 'TRADE END') if not trade.isclosed: return self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) ''' Next ''' def next(self): self.buySig = False self.sellSig = False self.idealBuySig = False self.idealSellSig = False # Calculate td sequential values if enough bars if len(self.dataclose) > self.p.candles_past_to_compare: # begin of sequence, bullish or bearish flip if self.dataclose[0] < self.dataclose[-self.p.candles_past_to_compare] and self.dataclose[-1] > self.dataclose[-(self.p.candles_past_to_compare + 1)]: self.bearishFlip = True self.bullishFlip = False self.tdsl = 0 self.tdss = 0 self.sellSetup = False self.sellCountdown = 0 elif self.dataclose[0] > self.dataclose[-self.p.candles_past_to_compare] and self.dataclose[-1] < self.dataclose[-(self.p.candles_past_to_compare + 1)]: self.bullishFlip = True self.bearishFlip = False self.tdss = 0 self.tdsl = 0 self.buySetup = False self.buyCountdown = 0 # sequence start if self.dataclose[0] < self.dataclose[-self.p.candles_past_to_compare] and self.bearishFlip: self.tdsl += 1 elif self.dataclose[0] > self.dataclose[-self.p.candles_past_to_compare] and self.bullishFlip: self.tdss += 1 # sequence intersection if self.tdsl == 9: self.buySig = (self.dataprimary.low[0] < self.dataprimary.low[-3] and self.dataprimary.low[0] < self.dataprimary.low[-2]) or (self.dataprimary.low[-1] < self.dataprimary.low[-2] and self.dataprimary.low[-1] < self.dataprimary.low[-3]) self.bearishFlip = False self.tdsl = 0 self.buySetup = True self.buyCountdown = 0 elif self.tdss == 9: self.sellSig = (self.dataprimary.high[0] > self.dataprimary.high[-2] and self.dataprimary.high[0] > self.dataprimary.high[-3]) or (self.dataprimary.high[-1] > self.dataprimary.high[-3] and self.dataprimary.high[-1] > self.dataprimary.high[-2]) self.bullishFlip = False self.tdss = 0 self.sellSetup = True self.sellCountdown = 0 # sequence countdown if self.buySetup: if self.dataprimary.close[0] <= self.dataprimary.low[-2]: self.buyCountdown += 1 if self.buyCountdown == 8: self.buyVal = self.dataclose[0] elif self.buyCountdown == 13: if self.dataprimary.low[0] <= self.buyVal: self.idealBuySig = True self.buySetup = False self.buyCountdown = 0 elif self.sellSetup: if self.dataprimary.close[0] >= self.dataprimary.high[-2]: self.sellCountdown += 1 if self.sellCountdown == 8: self.sellVal = self.dataclose[0] elif self.sellCountdown == 13: if self.dataprimary.high[0] >= self.sellVal: self.idealSellSig = True self.sellSetup = False self.sellCountdown = 0 # Trade only if no pending order and live data if not self.order and self.live: if not self.position: if self.buySig: if self.p.stoploss_dist > 0: stoplossPrice = self.dataprimary.close[0] - self.p.stoploss_dist self.buy_bracket(stopprice=stoplossPrice, exectype=bt.Order.Market) else: self.buy() elif self.sellSig: if self.p.stoploss_dist > 0: stoplossPrice = self.dataprimary.close[0] + self.p.stoploss_dist self.sell_bracket(stopprice=stoplossPrice, exectype=bt.Order.Market) else: self.sell() else: if self.position.size > 0 and (self.sellSig or self.idealSellSig): self.close() elif self.position.size < 0 and (self.buySig or self.idealBuySig): self.close() # Log the closing price of the series from the reference self.log("Close {} TD SeqShort {} TD SeqLong {} TD CDBuy {} TD CDSell {}".format(self.dataclose[0], self.tdss, self.tdsl, self.buyCountdown, self.sellCountdown)) class observer(btobserver): plotinfo = dict(plot=True, subplot=False, plotlegend=False, plotlinelegend=False, plotlinevalues=False, plotabove=True, plotname="TD Sequential",) lines = ('buy', 'buy_ideal', 'sell', 'sell_ideal', 'seq_up_1', 'seq_up_2', 'seq_up_3', 'seq_up_4', 'seq_up_5', 'seq_up_6', 'seq_up_7', 'seq_up_8', 'seq_up_9', 'seq_up_1_b', 'seq_up_2_b', 'seq_up_3_b', 'seq_up_4_b', 'seq_up_5_b', 'seq_up_6_b', 'seq_up_7_b', 'seq_up_8_b', 'seq_up_9_b', 'seq_up_10_b', 'seq_up_11_b', 'seq_up_12_b', 'seq_down_1', 'seq_down_2', 'seq_down_3', 'seq_down_4', 'seq_down_5', 'seq_down_6', 'seq_down_7', 'seq_down_8', 'seq_down_9', 'seq_down_1_s', 'seq_down_2_s', 'seq_down_3_s', 'seq_down_4_s', 'seq_down_5_s', 'seq_down_6_s', 'seq_down_7_s', 'seq_down_8_s', 'seq_down_9_s', 'seq_down_10_s','seq_down_11_s', 'seq_down_12_s',) plotlines = dict( buy_ideal = dict(marker='^', markersize=10.0, color='green',), buy = dict(marker='2', markersize=10.0, color='green',), sell_ideal = dict(marker='v', markersize=10.0, color='maroon',), sell = dict(marker='1', markersize=10.0, color='maroon',), seq_up_1 = dict(marker='$1$', markersize=6.0, color='black', ls='',), seq_up_2 = dict(marker='$2$', markersize=6.0, color='black', ls='',), seq_up_3 = dict(marker='$3$', markersize=6.0, color='black', ls='',), seq_up_4 = dict(marker='$4$', markersize=6.0, color='black', ls='',), seq_up_5 = dict(marker='$5$', markersize=6.0, color='black', ls='',), seq_up_6 = dict(marker='$6$', markersize=6.0, color='black', ls='',), seq_up_7 = dict(marker='$7$', markersize=6.0, color='black', ls='',), seq_up_8 = dict(marker='$8$', markersize=6.0, color='black', ls='',), seq_up_9 = dict(marker='$9$', markersize=6.0, color='black', ls='',), seq_up_1_b = dict(marker='$1$', markersize=6.0, color='blue', ls='',), seq_up_2_b = dict(marker='$2$', markersize=6.0, color='blue', ls='',), seq_up_3_b = dict(marker='$3$', markersize=6.0, color='blue', ls='',), seq_up_4_b = dict(marker='$4$', markersize=6.0, color='blue', ls='',), seq_up_5_b = dict(marker='$5$', markersize=6.0, color='blue', ls='',), seq_up_6_b = dict(marker='$6$', markersize=6.0, color='blue', ls='',), seq_up_7_b = dict(marker='$7$', markersize=6.0, color='blue', ls='',), seq_up_8_b = dict(marker='$8$', markersize=6.0, color='blue', ls='',), seq_up_9_b = dict(marker='$9$', markersize=6.0, color='blue', ls='',), seq_up_10_b = dict(marker='$10$', markersize=6.0, color='blue', ls='',), seq_up_11_b = dict(marker='$11$', markersize=6.0, color='blue', ls='',), seq_up_12_b = dict(marker='$12$', markersize=6.0, color='blue', ls='',), seq_down_1 = dict(marker='$1$', markersize=6.0, color='black', ls='',), seq_down_2 = dict(marker='$2$', markersize=6.0, color='black', ls='',), seq_down_3 = dict(marker='$3$', markersize=6.0, color='black', ls='',), seq_down_4 = dict(marker='$4$', markersize=6.0, color='black', ls='',), seq_down_5 = dict(marker='$5$', markersize=6.0, color='black', ls='',), seq_down_6 = dict(marker='$6$', markersize=6.0, color='black', ls='',), seq_down_7 = dict(marker='$7$', markersize=6.0, color='black', ls='',), seq_down_8 = dict(marker='$8$', markersize=6.0, color='black', ls='',), seq_down_9 = dict(marker='$9$', markersize=6.0, color='black', ls='',), seq_down_1_s = dict(marker='$1$', markersize=6.0, color='blue', ls='',), seq_down_2_s = dict(marker='$2$', markersize=6.0, color='blue', ls='',), seq_down_3_s = dict(marker='$3$', markersize=6.0, color='blue', ls='',), seq_down_4_s = dict(marker='$4$', markersize=6.0, color='blue', ls='',), seq_down_5_s = dict(marker='$5$', markersize=6.0, color='blue', ls='',), seq_down_6_s = dict(marker='$6$', markersize=6.0, color='blue', ls='',), seq_down_7_s = dict(marker='$7$', markersize=6.0, color='blue', ls='',), seq_down_8_s = dict(marker='$8$', markersize=6.0, color='blue', ls='',), seq_down_9_s = dict(marker='$9$', markersize=6.0, color='blue', ls='',), seq_down_10_s = dict(marker='$10$', markersize=6.0, color='blue', ls='',), seq_down_11_s = dict(marker='$11$', markersize=6.0, color='blue', ls='',), seq_down_12_s = dict(marker='$12$', markersize=6.0, color='blue', ls='',), ) params = ( ('bardist', 0.00005), ) def __init__(self): pass def next(self): # begin of sequence for long position if self._owner.tdsl > 0 and self._owner.tdsl < 10: name = 'seq_up_%d' % self._owner.tdsl attr = getattr(self.lines, name, False) attr[0] = self.data_low[0] - self.p.bardist # begin of sequence of short position elif self._owner.tdss > 0 and self._owner.tdss < 10: name = 'seq_down_%d' % self._owner.tdss attr = getattr(self.lines, name, False) attr[0] = self.data_high[0] + self.p.bardist if self._owner.buySetup: if self._owner.buyCountdown > 0 and self._owner.buyCountdown < 13: name = 'seq_up_%d_b' % self._owner.buyCountdown attr = getattr(self.lines, name, False) attr[0] = self.data_low[0] - (2 * self.p.bardist) elif self._owner.sellSetup: if self._owner.sellCountdown > 0 and self._owner.sellCountdown < 13 and self._owner.sellSetup: name = 'seq_down_%d_s' % self._owner.sellCountdown attr = getattr(self.lines, name, False) attr[0] = self.data_high[0] + (2 * self.p.bardist) if self._owner.buySig: self.lines.buy[0] = self.data_low[0] - (2 * self.p.bardist) elif self._owner.sellSig: self.lines.sell[0] = self.data_high[0] + (2 * self.p.bardist) elif self._owner.idealBuySig: self.lines.buy_ideal[0] = self.data_low[0] - (3 * self.p.bardist) elif self._owner.idealSellSig: self.lines.sell_ideal[0] = self.data_high[0] + (3 * self.p.bardist)
To plot this, you need to add the observer
-
Not automated, but it can for sure do the trick
And good to see that things are getting more multilingual each day (at least I can read and understand this one)
-
Thanks @dasch & @backtrader
Impressive community!