• untoreh-light

Optimization rabbit hole in python

Speeding up backtesting for the freqtrade crypto trading bot.

Freqtrade Is a crypto trading bot in python. I used to play with a fork for a while, fixing some bugs and some behaviour that I didn't like. Listing the bugs here would be boring so I will instead talk about the things that I think are interesting.

How freqtrade (FQT) works, on a high level

FQT is not event based, it is loop based. What does this mean? We could be talking about the executor (live operation) or the backtester. In this case they are both loop based.

Short detour here. What do I prefer? Loops or Events?

...returning to FQT, in live mode, it processes orders on a loop. Every X seconds it queries the exchange for updates. It keeps tracks of:

Ruh-roh

Loop based live execution has an obvious problem. It is synchronous. If you process to many pairs execution will be less responsive, and iterate less often, lagging behind the unfolding of price action. So despite the fact that I prefer loops for backtesting, I prefer events for live execution.

The downside? If you use events for one thing and loop for the other, it is easier for models or parameters of the strategy to diverge and not match reality.

My defense argument against this is that constantly running self evaluation (backtesting) and live execution obviates this problem because they will eventually align.

FQT has many configuration parameters and many time the distinction between the bot and the strategy would turn greyish as the strategy can't control how the orders are executed by the bot. The bot handles "roi" which is a grid of take-profit price limits and "trailing stoploss" the same but on the downside. The strategy only provides the parameters and the bot does the rest. I was not a fan of this.

Callbacks for buy and sell signals. Trades would happen based on signals returned by callbacks that would compute signal on a dataframe. The problem with this is that signal are static; you could just dynamically compute a 1 row dataframe, and feed that to the bot, but it wouldn't be backtestable. This is fine if you don't value backtesting that much, to each their own.

Patchworks

What I modified in FQT to try to make it behave to my likining.

There are a bunch of other tweaks, like parallel signals evaluation that I eventually gutted out as I focused more on build fast signals, and tweaks for plotting and config options, but they are not noteworthy.

The quest for vectorized backtest

I tried many version of backtesting to speed it up. It was all, not at all, worth it. Computing logic between buy and sell operation using numpy arrays. It is like playing tetris with your brain. I mean where the blocks are made of your grey matter, and you try to put them together. Here we list them

We added also some additional features to the backtest, like spread and liquidity calculation, and the previously mentioned time weighted roi.

The quest for parallel optimization

Why did I want a fast backtester? Because I want to run many of them such that I can find the bestest of the best parameter config...of course, disregarding any concept of over-fitting, over-parametrization, lack-of-focus, etc... Let's list what I worked on:

Conclusions

We cranked FQT backtesting to 11. But we never really used it in production :).

Slightly after the numbification of the backtester, additional callbacks were added to the strategy that broke the separation between backtesting and strategy evaluation, which meant that to keep the thing fast, you had to also write your strategy in numba! But I got fed up with the shaky mix of python/numpy/numba gotchas and because I didn't like the live execution of FQT, I dropped the whole thing anyway, for greener (or shall I say pinker!) pastures.

Anyway...optimization is crack.

Post Tags: