This is a code snippet for trend line indicator. As the name suggests, it calculates the price value at different points of a trend line and generates buy and sell signals accordingly. One thing I like to able to do is take a semi-automated approach to algorithmic trading. I might spot a nice trend line on Tradingview but want to execute trades in a forward testing environment using Backtrader when price reaches the trend line. As such, I consider this a short term indicator that can be used for as long as the trend line holds up.
The trend line indicator written in this post is intended to be used in the backtrader framework but the calculations used to calculate the trend line should be portable to other frameworks.
Background
If like me, you are not a maths wizard, a bit of background reading might be required so that you can understand the equations contained in the code. In the world of maths we need to study “linear equations” to develop this indicator.
I found this tutorial to be well written and quite helpful:
The main takeaway is that to be able to calculate the price of the trend line at any point in time, we need to calculate how fast the slope changes between two points in time. These points in time are the two prices you input into the indicator. How you identify those two points is up to you. As mentioned above I personally identify and draw a trend line on Tradingview and then make a note of the start and end points to use with this indicator.
The equation we need to arrive at is:
y=mx+b
Where:
y = price
m = slope
x = date / time
b = y-intercept
Now one thing I found difficult to wrap my brain around is that most tutorials assume you can visually see the y-intercept. It is the value of y when it crosses the y-axis at 0. Like this:
As we know price charts look a little different and we are unlikely to go back in time (Where we would cross the y-axis). We cannot see what the y-intercepts is. So we need to calculate it! To do this you need to flip the equation around a bit.
y=mx+b
Becomes
b = y – m*x
When solving this, you can use the y and x values you already have from the start and end points of your trend line. You will see how I have implemented it in the code below.
Indicator Rules
The trend line indicator will generate a buy signal if price crosses down into the trend line from above. It will generate a sell signal if price crosses up into it. I reasoned that because the trend line will either act as support or resistance, it will always be below price when you are looking for buy signals and above price when you are looking for sell signals.
The Trend Line Indicator Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class TrendLine(bt.Indicator): ''' This indicator shall produce a signal when price reaches a calculated trend line. The indicator requires two price points and date points to serve as X and Y values in calcuating the slope and the future expected price trend x1 = Date/Time, String in the following format "YYYY-MM-DD HH:MM:SS" of the start of the trend y1 = Float, the price (Y value) of the start of the trend. x2 = Date/Time, String in the following format "YYYY-MM-DD HH:MM:SS" of the end of the trend y2 = Float, the price (Y value) of the end of the trend. ''' lines = ('signal','trend') params = ( ('x1', None), ('y1', None), ('x2', None), ('y2', None) ) def __init__(self): self.p.x1 = datetime.datetime.strptime(self.p.x1, "%Y-%m-%d %H:%M:%S") self.p.x2 = datetime.datetime.strptime(self.p.x2, "%Y-%m-%d %H:%M:%S") x1_time_stamp = time.mktime(self.p.x1.timetuple()) x2_time_stamp = time.mktime(self.p.x2.timetuple()) self.m = self.get_slope(x1_time_stamp,x2_time_stamp,self.p.y1,self.p.y2) self.B = self.get_y_intercept(self.m, x1_time_stamp, self.p.y1) self.plotlines.trend._plotskip = True def next(self): date = self.data0.datetime.datetime() date_timestamp = time.mktime(date.timetuple()) Y = self.get_y(date_timestamp) self.lines.trend[0] = Y #Check if price has crossed up / down into it. if self.data0.high[-1] < Y and self.data0.high[0] > Y: self.lines.signal[0] = -1 return #Check for cross downs (Into support) elif self.data0.low[-1] > Y and self.data0.low[0] < Y: self.lines.signal[0] = 1 return else: self.lines.signal[0] = 0 def get_slope(self, x1,x2,y1,y2): m = (y2-y1)/(x2-x1) return m def get_y_intercept(self, m, x1, y1): b=y1-m*x1 return b def get_y(self,ts): Y = self.m * ts + self.B return Y |
Commentary
The doc string (long comment under the class declaration), hopefully gives a good enough explanation of the input parameters and format they are required in.
There are two lines being generated for the indicator. One is the trend line and the other is the signal line. Note that the trend line is not plotted. This is because in backtrader we cannot plot one line on the master plot and another line on the subplot. We need to choose one or the other. We could technically plot both on the subplot but that would look a little strange since the trend line can be any number (ie. the value depends on the price of the instrument) but the signal line will only oscillate between -1 and 1. A workaround for this is provided in the results section.
Another thing to note is that the dates must be converted to a timestamp so that calculations can made against a number and not a date object.
Finally, you might be wondering why I am building the trendline and calling get_y() in the next() method instead of __init__()? This is because backtrader will throw an IndexError if I try and obtain the date information during __init__(). If anyone knows how to workaround this, let me know! I probably missed something in the docs.
The key methods
If you want to implement this code in another frame work the three key methods that are needed are below:
1 2 3 4 5 6 7 8 9 10 11 |
def get_slope(self, x1,x2,y1,y2): m = (y2-y1)/(x2-x1) return m def get_y_intercept(self, m, x1, y1): b=y1-m*x1 return b def get_y(self,ts): Y = self.m * ts + self.B return Y |
As above, just make sure you pass time stamps for the x parameters and call the functions in the following order:
- get_slope()
- get_y_intercept(), because it needs the value of m from get_slope()
- get_y, to get the final price from any given timestamp.
Result
As mentioned above, if you run this code as is, the indicator will not plot a trend line on the resulting graph automatically. Since the indicator is designed to produce buy and sell signals, I prioritize the plotting of the signal line.
As a workaround and in order to build some visual confidence that the indicator is working correctly, you can initialize a second indicator in the strategy that plots a simple moving average of the trend line with a period of 1.
1 |
self.sma = bt.indicators.MovingAverageSimple(self.ind1.lines.trend, period=1, plotmaster=self.data0) |
Note that in the code above, you should place the line inside the __init__() method of the strategy. Assume I have initialized the indicator with the name ind1 already (self.ind1.lines.trend).
This will produce a final chart which looks like this:
As you can see, we have a trend line running through the chart and signals being generated according to our rules in the code, shown in the bottom subplot.
Thanks, but it will be a lot better if you could include a small, fully-working example of how this custom indicator can be used in Backtrader’s in relation to Strategy, cerebro, and cerebro.run().
Yes! I would love to implement, is there a complete example anywhere?
Hi can you please provide complete example?
Thanks