The “BinHV45” algo trading strategy (Patreon)
Content
In this post I will show you the test of another strategy thats coming from the Freqtrade github repository.
It is called the BinHV45 strategy.
This time I am going to test the BinHV45. So let’s scan the code and roughly see which indicators it uses and how it determines buy and sell signals.
To get this strategy you can go to freqtrade’s github site and select the strategies repo. There you will find this strategy in the folder of berlinguyinca.
If I open this file I already see at the top that the bollinger band is created in this function.
# --- Do not remove these libs ---
from freqtrade.strategy import IStrategy
from freqtrade.strategy import IntParameter
from pandas import DataFrame
import numpy as np
# --------------------------------
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
def bollinger_bands(stock_price, window_size, num_of_std):
rolling_mean = stock_price.rolling(window=window_size).mean()
rolling_std = stock_price.rolling(window=window_size).std()
lower_band = rolling_mean - (rolling_std * num_of_std)
return rolling_mean, lower_band
This is the bollinger_bands() function that calculates the Bollinger Bands for a given pair. The function takes in three arguments: stock_price, which is a pandas DataFrame containing the stock price data; window_size, which is the number of periods used to calculate the rolling mean and standard deviation; and num_of_std, which is the number of standard deviations used to calculate the lower band.
The function first calculates the rolling mean and standard deviation of the crypto pair price using the rolling() method of the DataFrame object. It then calculates the lower band by subtracting the product of the rolling standard deviation and num_of_std from the rolling mean. Finally, the function returns the rolling mean and lower band as a tuple.
Then in the strategy class, the timeframe for this strategy is 1 minute. Unfortunately I only test the 1 day, 4 hour, 1 hour, 30, 15 and 5 minute timeframe. so hopefully these timeframes will also suffice. If the strategy performs well on these lower timeframes, perhaps I will download this 1 minute timeframe as well to see if it proves to have even higher changes of success.
The stoploss is set to 5% loss and the minimal roi is set to take profit at 1,25 percent profit. Which seems to me that this is a scalping strategy. Every time a signal is generated, a buy decision is made and after a small profit the trade is closed again.
class BinHV45(IStrategy):
INTERFACE_VERSION: int = 3
minimal_roi = {
"0": 0.0125
}
stoploss = -0.05
timeframe = '1m'
This small section here determines the spaces to use when optimizing the buy settings for this strategy. So apparently I can optimize this strategy even further in a later stage.
buy_bbdelta = IntParameter(low=1, high=15, default=30, space='buy', optimize=True)
buy_closedelta = IntParameter(low=15, high=20, default=30, space='buy', optimize=True)
buy_tail = IntParameter(low=20, high=30, default=30, space='buy', optimize=True)
# Hyperopt parameters
buy_params = {
"buy_bbdelta": 7,
"buy_closedelta": 17,
"buy_tail": 25,
}
In the populate indicators section the dataframe and its columns are defined. Here the bollinger indicator of the qtpylib is used and the upper, mid and lower bands are set in the dataframe.
Then there is a bollinger bands delta defined which is the absolute difference between the middle and lower Bollinger Bands.
Also a pricedelta is set that is the absolute difference between the open and close prices.
And a closedelta is set which is the absolute difference between the current and previous close prices.
Also there is a tail defined and this is the absolute difference between the close price and the low price.
So all kinds of comparisons in this section. Let’s find out how they are used to define the buy and sell signals.
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=40, stds=2)
dataframe['upper'] = bollinger['upper']
dataframe['mid'] = bollinger['mid']
dataframe['lower'] = bollinger['lower']
dataframe['bbdelta'] = (dataframe['mid'] - dataframe['lower']).abs()
dataframe['pricedelta'] = (dataframe['open'] - dataframe['close']).abs()
dataframe['closedelta'] = (dataframe['close'] - dataframe['close'].shift()).abs()
dataframe['tail'] = (dataframe['close'] - dataframe['low']).abs()
return dataframe
In the entry signal definition the signal is true if all these conditions are met. Let’s look at them line by line:
- the lower Bollinger Band of the previous row is greater than 0
- the absolute difference between the middle and lower Bollinger Bands is greater than the product of the close price and the delta value divided by 1000
- the absolute difference between the current and previous close prices is greater than the product of the close price and the closedelta value divided by 1000
- the absolute difference between the close price and the low price is less than the product of the middle and lower Bollinger Bands and the tail value divided by 1000
- the close price is less than the lower Bollinger Band of the previous row
- the close price is less than or equal to the previous close price
I do not know what the exact philosophy is behind these comparisons. But if all these are true, then a buy signal is generated.
And to keep things simple, there is no sell signal in this strategy. Only the sell signals from the ROI or stoploss.
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
dataframe['lower'].shift().gt(0) &
dataframe['bbdelta'].gt(dataframe['close'] * self.buy_bbdelta.value / 1000) &
dataframe['closedelta'].gt(dataframe['close'] * self.buy_closedelta.value / 1000) &
dataframe['tail'].lt(dataframe['bbdelta'] * self.buy_tail.value / 1000) &
dataframe['close'].lt(dataframe['lower'].shift()) &
dataframe['close'].le(dataframe['close'].shift())
),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
no sell signal
"""
dataframe.loc[:, 'exit_long'] = 0
return dataframe
Let me backtest this strategy and let’s find out what the results are. If it performs really well, then I’ll consider analyzing these buy settings more to understand why they do what they do.
Backtest results
Oke, so the initial backtest results are not that great. Over all the timeframes I normally test, this strategy performs pretty poorly. The 30 minute timeframe seems to perform the best, but with a winrate if 78% it still manages to make losses overall. The only reason I can think of is that the stoploss setting is set to wide in comparison to the small take profits points this strategy has.
If I take the output of this timeframe, I indeed see that the small amount of stoploss exits has a greater negative results than the large amount of roi take profits.
This is logical since the stoploss is slightly 5 times larger than takeprofit. So for each loss there has to be at least 5 wins to get break even again. Also the stoploss might be too tight in comparison with the volatility of crypto, so this point can be easily hit and causes an unnecessary losing exit.
In this case a parameter optimization session should hopefully fix this issue. And hopefully determine better settings for these two parameters so that it can perform better overall.
So let me optimize the buy, roi and stoploss settings and see if I can improve this strategy.
Parameter optimization
After optimizing the roi, stoploss and buy indicator settings, the strategy finally is making some profits. However it comes at a price and the winrate and drawdown get worse. However the other ratios get better, which is logical since it changed from a losing to a winning strategy.
Let’s see a snippet of the improved hyperopt results why this is so…
If you are familiar with the freqtrade bot, then you already might know that the hyperopt output always calculates three ROI takeprofit settings at three calculated timeperiods in minutes.
It also always returns one stoploss setting which in this case went from a 5 percent to over 13 percent stoploss setting.
If the ROI reaches almost 17% profit, then it sell the position.
So in this case the stoploss doesn’t get triggered quickly. And also at least one take profit point takes more profit than the stoploss losses.
You can see from this backtest output that there are still significant losses then the stoploss gets hit, which I find a little disturbing. The amount of ROI profit takings looks good but it is only being depending on the ROI to take profit. Which is also a little bit on the meager side is my personal opinion.
Charts
I will also compare the charts of the two backtest outputs to see where the differences are.
The first chart I show here is the initial backtest chart.
First of all I’d like to mention here that for the 30 minute timeframe I limited the backtesting period to the start of the last bull market to the 1 of januari 2023 because when I tested this timeframe over larger timespans, it errored out on me. That is why you see the flat line first.
But from the beginning of the backtest to the end the strategy was in the red and never really got out of it.
There was a period that there were more gains than losses and even almost a small break even point during the first run up of the last bull market. But then it started to loose again as you can see on the profit chart.
Also note that there were also coins that were in the top 50 of januari 2022 but they were consistently losing money with this strategy. That is why I also include the applicability score in my league. To see how well a strategy fits a certain crypto pair.
After adjusting the parameters with hyperoptimization you can see that the strategy performs totally different.
Now, from the same beginning of the backtesting period it steadily makes money. During the end of the last bull market there is a sudden drop in profits. and you can also see in the paralellism chart that a lot of trades were happening over there. Which probaby caused this crash in profits. But more analysis is needed to make the right claims about what happened there.
Nonetheless after this drop, the strategy again slowly climbed to higher profits. But at some stage less and less trades were made so this line flattens out at the end.
Also see how the profit per pair graph spreads out and how difficult it is to have consistend profits over all pairs this time. The majority of these pairs however is positive. But with some of these very negative exceptions on the total profitability the total result is lower than might be possible.
Knowing this, let’s take a look on how this specific strategy scores on my strategy league.
Strategy legaue
Comparing this strategy to the earlier tests I did, you can see that it is not really worth your time to seriously invest time investigating the code further.
My personal opinion is that, if a strategy does not perform well right away, then at max it is a good learning experience for coding this. But not much more.
My experience up until now is that scalping strategies only perform well under certain market conditions. If there is no general indicator to confirm if these specific market conditions are met, then the strategy trades under all circumstances. Also the ones that not benefit the strategy.
It might be so that the idea behind this algo has the correct assumtions but it should be tested against the specific market circumstances. And then try to create a measure to confirm these optimal circumstances. It could even be so that scalping both ways, and by that I mean going short with futures in negative trends as well, could also improve this strategies performance. But again these circumstances should be determined and also act as an additional confirmation indicator.
But I leave that challenge to someone who wants to put the effort and time into these improvements.
In the mean time I will be searching and testing another algo strategy.
All files are located in this earlier post, since this is a repost from my old site: https://www.patreon.com/posts/80423239
And therefore I will see you next time.
Goodbye!