Warning: this post is just me bitching about poor python libraries and does not contain any useful technical knowledge. I didn’t really plan to interrupt this long break from writing with a text about python backtesting libraries. Actually I wasn’t planning to publish at all. I was fairly busy lately and didn’t have any obvious topic to write about. Since I believe not writing at all is preferable over writing about nothing just to keep up some made-up tempo I was just waiting until I have something interesting to say. Note that this article is strongly subjective.
I’m interested in investing for some time now. Recently I wanted to backtest some strategies for stock trading. Of course I could use tradingview to write some backtests, but I thought using python would give me more flexibility – not only it’s one of the languages I know, but also it’s general purpose and has plethora of other libraries that might come in handy at some point, especially for data analysis and machine learning.
At first I was amazed at how many resources and different libraries I have found. Here are just some examples of awesome lists on github:
Why do we need tens of awesome lists about a single non-technical topic? What’s keeping all these people from contributing to an existing list instead of creating their own one? My guess is that they thought their list will be better, larger, more organized. Of course such a list deserves its own repository. Curating an existing list would make them disappear in the sea of OS contributors. And now, thanks to their brilliance, they stand out among all the repositories. Similarly thought ~10 people.
Furthermore – every list suggests tens of projects, many of which solving the same problem. This is a positive feedback loop – there is no proper tool for my job (either because all of them are flawed or because there’s just too many), so I’ll create my own copy and enrich the list of useless libraries.
It took me a couple of hours to just go through some of the lists and pick up candidate libraries I could use. After >5y of programming profesionally I find this proliferation of information even more disturbing than lack of any. Here are two notable examples of frustrating libraries I tried to use.
Everyone trying to trade loves charts, right? If so, this is the main reason to stay away from this library:
Pretty unclear, especially the drawdown. Of course you cannot set logarithmic scale in the charts, increase the height etc unless you’re willing to dabble in the dependency files. But writing all that from scratch is what I essentially tried to avoid from the beginning.
Oh – one thing I forgot to mention. To even run the plot I had to either downgrade matplotlib from 3.5.1 to 3.2.2 or install some develop version of backtesting.py.
One more thing that cought my attention about this library is awkward naming. Suppose you’d like to backtest a crossover strategy on the spot market, this is probably not going to do what you want:
class SmaCross(Strategy): n_fast = 3 n_slow = 23 def init(self): price = self.data.Close self.ma_fast = self.I(SMA, price, self.n_fast) self.ma_slow = self.I(SMA, price, self.n_slow) def next(self): if crossover(self.ma_fast, self.ma_slow): self.buy() elif crossover(self.ma_slow, self.ma_fast): self.sell()
self.buy() indeed buys the asset, but
self.sell() doesn’t sell it (and go into cash), but rather opens a short position. The strategy will work if we replace
Having to struggle with more of such inconveniences, I moved on to even more disappointing tool.
Let’s try to work with a similar example. The following code implements the SMA strategy.
import backtrader as bt class SmaCross(bt.Strategy): def __init__(self): self.dataclose = self.datas.close self.sma = bt.indicators.SimpleMovingAverage(self.datas, period=30) def next(self): if not self.position: if (self.dataclose > self.sma) and (self.dataclose[-1] < self.sma[-1]): self.buy() else: if (self.dataclose < self.sma) and (self.dataclose[-1] > self.sma[-1]): self.sell()
I wanted to test the all-in / all-out strategy, so I’ve added a proper sizer:
cerebro.addsizer(bt.sizers.AllInSizer), but no asset was ever bought (I’ve reused the previous data). After finding that this sizer has some issues, I’ve changed it to
Then I’ve noticed the most disturbing thing – the results are highly dependent on the initial cash position! It’s not only the problem of not being able to buy fractional units. After some debugging I’ve noticed, some positions are not closed when changing the initial cash from 100_000 to 1_000_000.
There are multiple other bugs and inconveniences – the plots are even less configurable than in the previous library, there are issues using yahoo data feeds and feeding external data is very confusing – you need to pass it as
dataname parameter of
I know what you might say – “If you don’t like the lib, just don’t use it”. But as we speak of investments, we are probably all aware, our most important one is time. That’s probably the reason I’m so frustrated having to dig through all those libraries until I find something useful (and I have, it’s bt, unless that’s what it looks like after two days of using it – that’s a topic for another post). And it’s not that I’m going through some niche projects, we’re talking about thousands of stars on github. I expect more from that, don’t you?
I’m sorry for such a salty post, but my intention is not to bash any project or its developers. If there was one thing I’d like you to get out of it is the question to ask yourself – “Is it worth taking part in this race of projects proliferation or would you rather improve an existing product and provide more and more value?”.