Hi there, welcome back to another strategy breakdown and testing post.
This time, we’re taking a look at the SMAOffsetOptV1—a strategy that’s built around simple yet effective moving average techniques. This isn’t one of those overcomplicated algorithms, but don't let its simplicity fool you. With a mix of SMA and EMA signals, it packs a punch when it comes to optimizing short-term trades.
So, what makes this strategy interesting? The SMAOffsetOptV1 uses customizable offsets to fine-tune when it enters and exits trades. You’ve got your typical buy/sell triggers based on price movement around the SMA and EMA lines, but with the added flexibility of adjusting these parameters to fit market conditions. Whether you're aiming for quick profits or testing a longer hold, this strategy has some built-in options for both.
In this post, we’ll break down the logic behind how this strategy works, look at the settings that make it tick, and of course, go over the results from the backtests. Spoiler alert: while the strategy has solid foundations, there’s always room for tweaks depending on your risk tolerance and trading goals. Let's dive in and see if SMAOffsetOptV1 fits your trading style!
The SMAOffsetOptV1 strategy is a short-term trading algorithm that leverages Simple Moving Averages (SMA) and Exponential Moving Averages (EMA) to time entries and exits. By applying customizable offsets to these moving averages, the strategy aims to capture price momentum while optimizing risk with built-in ROI targets and a dynamic stoploss system. It's designed for flexibility and fine-tuning, making it suitable for both quick trades and more conservative holds.
You can find the strategy on Github if you search for it on it’s name.
I’ll include the link to that search too so that you can download this algo directly from github.
https://github.com/search?q=SMAOffsetOptV1.py&type=code
Let’s click on the first one here to see the contents of the file and check out how the algorithm works:
The raw file link
The first thing that I’d like to mention is that the author of this code is @tirail
Therefore all the kudos and compliments go to this person. I’m just testing it out and present you the analysis on my backtesting environment. Hopefully this strategy has a high probability to be profitable.
The first thing there is to see is this ma_types dictionary. Here there are two moving averages configured. Later in this code we’ll see why this part is here.
The strategy is ready for hyperparameter optimization of the SMA on the buy side and EMA on the sell side. This other section below also has information about the ranges in which the optimal parameters have to be found.
Furthermore the strategy has a stop loss of 50% set. Which is quite large and will give trades a lot of breathing room to eventually, and hopefully, go higher again.
Then there is a ROI table configured that has time based take profits set at multiple points in time. In this case at the 60, 90 and 220 minute mark.
Furthermore there is a trailing stop loss set that might be of influence on the backtest results you see later. So keep this in mind and always test on your own setup to see how well the algo performs over there.
The strategy is supposed to be working best on the 5 minute timeframe and we’ll see later if this is really the case here.
Also there is a custom stop loss configured but it’s also disabled with this line: use_custom_stoploss = False
In the populate indicators method the indicators are configured on which the buy and sell signals will work.
As you can see there are only two indicators configured to be entered into the dataframe.
An MA offset buy indicator and a MA offset sell indicator. Let’s see what is happening over here because this is quite interesting:
dataframe['ma_offset_buy'] = ma_types[self.buy_trigger.value](dataframe, int(self.base_nb_candles_buy.value)) * self.low_offset.value
dataframe['ma_offset_sell'] = ma_types[self.sell_trigger.value](dataframe, int(self.base_nb_candles_sell.value)) * self.high_offset.value
The buy offset first uses the ma type. And the types of MA are in the dictionary we found at the beginning of this strategy.
The actual use of which kind of MA is determined in the hyperspace parameter settings next to the base_nb_candles_buy value and the offset for the calculation. The base nb candles is just a fancy description for the length of these moving averages.
These values are also written down in the section for hyperoptimizing. And in that part of the code the ranges are also determined in which the optimal parameters has to be found if you want to do hyperparameter optimization for this strategy.
Then there is the “* self.low_offset.value” and this is the offset boundary where the moving average is multiplied by.
In the creation of the ma_offsett_sell column in the dataframe is the same calculation. The MA type, its base candles buy value and the high offset candle are all determined by the initial hyperparameter setting and also set for optimizing in the opzimization settings section.
So initially the settings for these ma offse buy and sell calculations are in the buy and sell hyperspace settings. With hyperparameter optimization optimized values can be found for your strategy, but you’ll then have to be aware that you are introducing curve fitting bias in your strategy that might cause suboptimal future behavior for the strategy.
As for the final buy and sell signals these are determined in this final section:
The main condition for triggering a buy is when the price crosses above the ma_offset_buy (an adjusted moving average). This is evaluated using qtpylib.crossed_above, which detects if the price has crossed from below to above the adjusted moving average (i.e., from a dip or low point). The buy condition also checks if the trading volume is greater than zero to ensure the market is active.
The sell condition is triggered when the price crosses below the ma_offset_sell. This condition is evaluated using qtpylib.crossed_below.
Similar to the buy logic, this checks for a cross but in the opposite direction (indicating the price has peaked and is now starting to fall).
It also ensures that there is trading activity by checking if the volume is greater than zero. So in other words, the strategy looks to sell when the price, which has been above the adjusted moving average, starts to drop back below it. This is often a sign that the upward momentum is ending and a potential price drop might occur.
Let’s test this supersimple strategy to see of this has potential for future trading opportunities.
As it stands, the originally configured 5 minute strategy has indeed the best cards in hand for profitability.
Out of the box this algorithm scored 338 points and got a hypothetical 341 percent return over the backtest period. It also has a respectable 75 percent win rate. And with an average of 5 consecutive wins over 2 consecutive losses it is also easy on the nerves.
If we take a look at the equity curve then we see more of the characteristics of this trading strategy. On a 5 minute timeframe this is not the most active trading strategy, which we can see at these flat lines over several weeks. But when it comes to detecting a profitable trade, then it certainly catches this.
This also has a positive impact on the drawdown curve. Which remains flat most of the times with here and there a spike upwards. The maximum drawdown detected at backtesting was 9 percent at most.
Lets take a closer look at the winrate and profit distribution.
The boxplot of the winrate distribution on the left shows a very high winrate, close to 1.0 (100%) for the majority of trades, indicating that most trades are profitable. However, the presence of a small number of lower winrate results suggests occasional losses or less frequent success in certain weeks.
The profit distribution on the right reveals that while the majority of weekly profits are small and clustered close to the median, there are a few significant outliers (dots above the box) representing large profitable trades. This indicates that although the strategy is generally consistent, it occasionally captures much larger profits. However, the strategy's profitability seems to rely on these few outliers to boost overall returns.
So overall this strategy might not be the highest scoring type in comparison with other, more advanced trading algorithms. But I think this will make a good foundation for building other trading rules on.
At the moment the amount of trading signals it gets on the 5 minute timeframe is way too low. Optimizing this with Hyperopt might seem to be a good idea. But keep remember that you’ll introduce biases here too. Additional trading rules might prevent this and help to get a higher quantity and quality of trading signals.
As for now I think this is enough information about this trading algorithm you can freely download from github. Thanks for watching and I’ll see you in the next post.
Goodbye!