Improving Random Python Internet Study Notes
Every now and then, samples with backtrader code pop up in the Internet. There are several in what it looks to me to be Chinese. The latest one is here:
The title is: backtrader-学习笔记2, which apparently (thanks Google) translates to backtrader- study notes 2. If those are study notes, let’s try to improve the code there where it can really be improved and in my personal opinion there where backtrader shines the most.
In the __init__
method of the strategy inside the study notes we find the
following
def __init__(self): ... self.ma1 = bt.indicators.SMA(self.datas[0], period=self.p.period ) self.ma2 = bt.indicators.SMA(self.datas[1], period=self.p.period )
Nothing to argue here (style is something very personal, I won’t touch that)
And in the next
method of the strategy, the following are the logic
decisions for buying and selling.
... # Not yet ... we MIGHT BUY if ... if (self.ma1[0]-self.ma1[-1])/self.ma1[-1]>(self.ma2[0]-self.ma2[-1])/self.ma2[-1]: ...
and
... # Already in the market ... we might sell if (self.ma1[0]-self.ma1[-1])/self.ma1[-1]<=(self.ma2[0]-self.ma2[-1])/self.ma2[-1]: ...
These two logic blocks is what one can actually make a lot more better, which will also add to readability, maintainability and tweaking (if needed be)
Instead of having those comparison of moving averages (current point 0
and
previous point -1
) followed by some divisions, let’s look at how to have it
precalculated for us.
Let’s tweak __init__
def __init__(self): ... # Let's create the moving averages as before ma1 = bt.ind.SMA(self.data0, period=self.p.period) ma2 = bt.ind.SMA(self.data1, period=self.p.period) # Use line delay notation (-x) to get a ref to the -1 point ma1_pct = ma1 / ma1(-1) - 1.0 # The ma1 percentage part ma2_pct = ma2 / ma2(-1) - 1.0 # The ma2 percentage part self.buy_sig = ma1_pct > ma2_pct # buy signal self.sell_sig = ma1_pct <= ma2_pct # sell signal
And we can now take that to the next
method and do the following:
def next(self): ... # Not yet ... we MIGHT BUY if ... if self.buy_sig: ... ... # Already in the market ... we might sell if self.sell_sig: ...
Notice that we don’t even have to use self.buy_sig[0]
, because the boolean
test make with if self.buy_sig
is already translated by the backtrader
machinery to a check for [0]
Imho, a much cleaner approach in which defining the logic in __init__
with
standard arithmetic and logical operations (and using the line delay notation
(-x)
) makes the code much better.
In any case and for closing note, one could have also tried to use the built-in
PercentChange
indicator (aka PctChange
)
See: backtrader documentation - Indicator Reference
As the name suggests it does already calculate the percentage change over a
given period of bars. The code in __init__
would now look like this
def __init__(self): ... # Let's create the moving averages as before ma1 = bt.ind.SMA(self.data0, period=self.p.period) ma2 = bt.ind.SMA(self.data1, period=self.p.period) ma1_pct = bt.ind.PctChange(ma1, period=1) # The ma1 percentage part ma2_pct = bt.ind.PctChange(ma2, period=1) # The ma2 percentage part self.buy_sig = ma1_pct > ma2_pct # buy signal self.sell_sig = ma1_pct <= ma2_pct # sell signal
It doesn’t make much of a difference in this case, but it may for sure save you from lots of trouble if the calculations are larger and more complex.
Happy backtrading!