📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

How a Simple Candles Strategy Achieved a Positive Win Rate With 7-Year Automated Backtest

CodeTrading14:40

Transcription

Hi, and welcome back. Today, we will test a very simple pattern that yielded around 90% win rate on the EUR/USD backtest. This is not a trick. I backtested this strategy on two different assets and seven years of data, and both equities showed positive profit and an aggregated win rate of 87%.

Actually, there's nothing new in this strategy. It was published by Larry Williams back in 1999, and it seems that it still works on a few currencies. I came across this strategy through one of your messages, as the strategy was recently tested by another YouTube channel. Simple patterns are always easy to test and implement, so thank you for keeping these ideas coming. They really bring a lot of interesting approaches to investigate.

As usual, the Python code I will be using for this video is available for download from the link in the description, so you can try it for yourself and maybe apply your own modifications and experiments.

For a long setup, we will wait for an external red candle, meaning the high is greater than the previous high and the low is lower than the previous low. So, the maxima and the minima of the current most recent candle are engulfing those of the previous candle. This is translated using these three conditions that we will see in detail in the coding part. We also need the recent candle to close below the low of the previous candle. We mark the closing price level and we enter the market on the next candle open. We exit the market in one of two ways: either we hit our 200 pips stop-loss, or in the next session, we close our winning trade. So, we will keep the trade open until either it hits the stop-loss or by the end of a daily session, it becomes a winning trade.

There are other variations of the same pattern on how to enter the market. For example, we can wait for the price to break above the recent high before entering the market. We will leave these for another video. I will just stick with what we have explained so far, but just to tell you that you might see similar strategies with small tweaks in order to improve the outcome.

For a short setup, we will just consider symmetrical conditions. So, whenever we have a green external candle with wide high and low levels and a closing price above the high of the previous candle, we set our entry level for the next session, and we use a 200 pips stop-loss, waiting for a bearish market to close our short trade with profit.

Okay, now let's code this in Python and backtest this strategy to see how it performs on historical data. This is our Jupiter notebook file. I'm defining the first function, which is `read_csv_data_frame`. So, to data frame, it's going to read a file and transform it into a data frame. It returns the data frame. It also applies some cleaning, basic cleaning lines.

Then we have `read_data_folder`. That's going to read a whole folder of CSV files. So, if you have more than one asset you want to test the strategy on, you just have to drop these in one of these folders and use the `read_data_from_folder`. It's going to return a list of data frames with the file names as well, so we can identify which data frame is coming from which file name.

Then, for the `total_signal`, this is where we are putting our conditions to generate signals, short and long signals. So, I'm going to define the current position, which is the current candle's index. Then we have four different conditions. The first one, C0, is just testing if the open price is above the closing price. So, if we have a red candle or a candle in the bearish direction. Then the second condition is checking if the high is greater than the high of the previous day. The C2 condition is checking if the low is less than the low of the previous day. So, now we have somehow a very wide external candle that's wider than the range of the previous candle with two conditions, C1 and C2. And C3 is going to test if the close of the outside bar is less than the low of the previous day. So, the reason I'm saying the previous day or the previous candle is because we're using this on the daily time frame. So, the CSV files that I'm using are the daily time frame data.

If all these conditions that we see here are verified, in this case, we have a bullish signal. This is the return two here. So, the function is going to return the value two. Then, in the opposite direction, if we have a bearish signal, we have different conditions. We're going to return one. In any other case, we return zero, which means we don't have any signal to enter the market.

Then we have `add_total_signal`, which is going to apply this function on the data frame and it's going to add a new column called `total_signal`. `add_point_position` column is going to add positions wherever we have a signal. If it's a bearish signal, the point is positioned above the candle. If it's a bullish signal, the point is positioned below the candle. And when I say a point, I'm just referring to these positions that we're going to plot here. So, these purple points are the signals, basically, that were generated using this algorithm.

Back to the code, we can also use the `plot_candlestick_with_signals`. It's going to plot the charts and the point positions that were generated in the previous function. So, now we can use these functions. For example, I'm using `read_data_folder`. It's going to read the folder by default, it's called `Data folder`. So, just running this is going to run over two CSVs because I'm using the EUR/USD and the GBP/USD. These are the two CSVs that I have in my data folder.

Then, just to compute the sum of the total signals that we have, to compute the number of the signals, we have 3155 bearish signals in total and 312 bullish signals. We can plot the signals. We can plot different slices because we have the data frame. We will choose one of two data frames because here we have just two data frames for the two assets. Then we start. We have the start index, which is 300. This is where we're starting our slicing up to a number of rows of 355, so up to 655 in terms of slice. So, this is the starting index where we're plotting and for how many rows are we going to include in our chart. So, if I put just 10 rows, it's going to be 10 candles, so 10 days, and it's going to look like this. So, it's not very clear. But if we put, let's say, 100 rows, we're going to see 100 candles and so on.

Now, for the entry and the exit, I just included two different strategies. One is called `strategy_one`, which I didn't explain in this video. It simply uses a stop-loss distance, which is a percentage of the current price, and a take-profit also as a percentage of the current price. And I'm just defining this strategy. I kept it there for the experiment, so you can use it, you can drop it. We didn't go through it. It's simply something that I've added to the strategy, but it works well. Actually, we will test it in a while, and we can also optimize these parameters, the percentage of the stop-loss distance and the take-profit distance as well.

I'm going to run this cell, and we can skip this part for now to test `strategy_one`. And actually, to optimize it, I'm going to uncomment this part. Going to optimize it on all the data frames, all the assets. I'm going to comment this line, and I'm going to show you the potential that this indicator has. Now, why am I saying the potential? Because we're optimizing the strategy on two assets, and for each of these assets, we have the optimal stop-loss percentage and take-profit percentage providing the maximum return. So, what can we expect from such an indicator in the best possible conditions? This is what we're going to compute now.

And if I run the aggregated results, we can see that we have an aggregated returns of 181%, which is an amazing number. Number of trades is 56, and the maximum drawdown is just minus 12%, and an average drawdown of minus 1.22%. The win rate is around 75%, and the best trade is 7%, worst trade minus 5%, and the average trade is 2.3%. So, these are excellent results. Just don't forget that we're talking about optimized results. So, we optimized for each asset what would be the best possible stop-loss and take-profit percentage to have the maximum returns.

I'll not spend a lot of time on this part of the strategy because this is not what we focused our video on. I just, I'll just leave it here for you. You can experiment on it if you're interested. But the strategy that we explained is `strategy_two`. The size of the lot is 10% of the equity. And I define this function called `calculate_stop_loss`. It takes the entry price of the current candle, via pips distance of the stop loss. It's 200 by default. The pip value is 10 to the minus four, and we need the direction as well, because we need to know if we're putting the stop-loss level below or above the entry price in case we have a long or a short position.

Then, this is the part where the trading happens and backtesting happens. So, first of all, if we have any open trades, we're going to check the profit loss. If it's positive, if we have a winning trade, we're going to close it. This happens at the beginning of each session because we need to close any trades, any winning trades that were opened in the previous session. And whenever we have a bullish signal and we don't have any currently open trades, the current close price is equal to `self.data.close[-1]`, so it's the last candle's closing price. The stop-loss is equal to whatever the function is going to provide. `calculate_stop_loss`, we provide the entry price, which is the current closing price. I'm changing the pips distance to 250. You can change it to 100 or 150 and 200. Default values were 200 and 250. That's what I saw in the literature and in the YouTube video describing this strategy. Pip value is 10 to the minus four, and the direction is long in this case. Then we apply the `buy` function with the size and the stop-loss. Notice that we don't need a take-profit value because anyway, we're going to close the trade whenever it becomes a winning trade in the next session or in the future sessions.

Then, when we have a bearish signal and we don't have any open position, we apply the same approach and we use the `sell` function to open a selling position or a short position. So, I'm going to run this cell and I'm going to comment this part, which is basically optimizing the stop-loss percentage and take-profit percentage. So, we don't have these parameters in this function, in this new class or new strategy. These were for the previous one, and I'm just going to run it. So, that's not an optimized strategy. We're going to run it as is, and we need to change this from `strategy_one` to `strategy_two`.

So, we have a win rate of 87.5%. We have an aggregated returns of 26%, which is not extremely high for such a high win rate. And I'm going to explain to you why we're getting a high win rate, but our aggregated returns are still relatively low. But the maximum drawdown is minus 11%, and an average drawdown of minus 1.2%. This is acceptable, it's good.

So, we can actually plot the equity curves of the assets we are trading, and we can see that at the beginning, we had a really good time. So, if you aggregate these two curves until this point here, the index 1,500, it's going to provide you an excellent set of results. But then, we have one of these two, I think it's the EUR/USD, went down. Actually, it wasn't the strategy wasn't working anymore on this pair, and it was actually covered up by a spike with the GBP/USD. So, it kind of compensates.

So, all in all, we have 12% in returns and 14% in returns between the EUR/USD and GBP. Then we have access to the results for each of these two assets. So, if you go to `results[0]`, for example, the first element, we can get the results for EUR/USD, and that's 12%. The buy and hold returns is minus 5%, the maximum drawdown minus 11%, average drawdown minus 1%, and so on. So, we have a win rate of 90% that's on the EUR/USD. And if we try it for the GBP, let's run this. So, we have a win rate of 84.5% and the returns of around 14% and so on.

Now, the most important thing I want to highlight in this strategy is that we have a high win rate. We have an extremely high win rate, but we're not very impressed by the returns. Remember that these are results on the daily time frame, and we are closing any winning trade immediately on the next day, at the beginning of any day, we're closing those trades. And it could be that at some point, these trades didn't have the chance to be opened long enough to provide greater returns. So, at the same time, this is where the high win rate is coming from because if every time you have a winning trade, you're going to close it, that's a very high win rate. However, how much you are winning per trade is not going to be very impressive, and this is what we're seeing here.

So, in brief, the 200 pips stop-loss distance that we have introduced in this strategy is way larger than the take-profit distance, practically that we're using. So, we're not really using a take-profit distance, but we're actually closing the trades very quickly, and this is as if we were using a very narrow take-profit distance. This is why we have a very high win rate, but nothing impressive related to the aggregated returns. Remember that this is the daily time frame, and we're using data from 2017 up to 2024, so that's 7 years of data. So, whatever you are seeing here as returns, it's over 7 years of data. It's not much. The win rate is impressive, the aggregated returns are not very impressive, though. But I would still give some credit to the simplicity of the strategy, and honestly, it's still achieving relatively safe results with very limited drawdown percentage, even though I did add a small commission part in the backtest to account for minimal trading fees. And that's all I had to tell you for this one. Thank you for staying that long. Until our next one, trade safe, and see you next time.