How To Build A Profitable ROC-Based Trading Strategy Using Python
Introduction
This tutorial will implement a ROC trading strategy using Python. The first part will briefly explain the indicator and its calculation. The second part will calculate the Python example. We show you how to build a profitable ROC-based trading strategy using Python. However, the main purpose of the article to show how it can be done using Python. The strategy itself is of minor importance in this tutorial.
Indicator
The Rate of Change (ROC) measures the momentum of price movement between two periods of time. This indicator identifies the securities that outperform or underperform the market. When companies have solid financial situations, the expectation is steady growth in their stock price; the ROC will pick up some of these companies because they have positive returns. It can be used for sideways markets to find overbought or oversold securities.
The formula is:
\text{ROC} = \frac{\text{Price} - \text{Price}_n}{\text{Price}_n}
Price=current preriod
Pricen = Price n periods back.
ROC = Rate of Change
Download Data and Indicator
This article will use the Invesco QQQ Trust Series 1 (QQQ) ETF. As in the previous tutorial, the first step is to import the libraries and download the historical data:
The following image illustrates the rate_of_change function, which calculates the Rate of Change (ROC). The panda’s method .pct_change(period=n) calculates the ROC between the current value and the value from 15 periods back. When n=15 means the percentual variation between the period analyzed and 15 days back.
To plot the closing price and the ROC in the same chart is necessary to implement the plot_data function. This function has two charts, at the top, the closing price, and at the button, the indicator.
In the previous chart, the closing price tends to increase over time. The total return in 15 years is around 50%. Although it does not behave sideways, a mean reversion strategy can be implemented with minor modifications.
The following function estimates the RSI with the values from the Rate of Change. The rsi_function function uses the RSIIndicator class from the ta Python library to calculate the RSI. It has 14 periods (window=14) as a default look-back window.
The following image shows the rsi_function function implementation:
After adding some modifications to the plot_data function, the following image shows the plot_data1 function. It plots at the top the closing price and the RSI below with 30% and 70% bands.
the implementation is:
To implement a mean-reverting strategy, the price should fluctuate around a horizontal line. After running plot_data1, it is evident how QQQ follows an upward trend; therefore, having 30% and 70% as thresholds will not work well.
In the long term, more profits will come from long positions than short positions. A solution is to increase the upper band from 70% to 75% to curb the number of short positions.
The 30% and 75% band selection is based on a visual analysis. You can increase or reduce those values at your will.
Trading Strategy – rules
This article will implement two strategies with the following trading rules:
- Place a long position if the RSI < 30, and hold the position until the RSI crosses from below 75%.
- Place a short position when the RSI > 70 and hold the position until the RSI crosses from above 30%.
To calculate the total return for each strategy is necessary to make two functions that will indicate when there is or is not an open trade. A long position makes a profit when the price goes up. Meanwhile, a short position makes a profit when the price goes down.
The get_signals_long function will generate a Python lit with 0 or 1 signals_list. Whenever the RSI’s values are under lower_threshold, signal_list will append 1, which indicates a long position. Otherwise, signal_list will append 0, which indicates without a position. Finally, get_signals_long will transform signal_list into a data frame df_result.
In case you are interested in backtesting a short trading strategy. The get_signals_short function. It is necessary to indicate with minus one signal_value = -1 when the RSI is higher than the upper threshold val_indicator > upper_threshold. Otherwise val_indicator < lower_threshold, the signal will be zero.
The signal for a short position is minus one signal_value = -1 for one reason. The minus one indicates that the strategy profits in the opposite direction. For example, a short position has negative results if the price moves from $20 to $23.
After implementing get_signals_long, get_signals_short, and adding their values to df:
The implementation of the previous function yields:
Equity Curve
The following image shows the equity_curve function. This function plots the performance of the strategy and the buy-and-hold strategy.
The long strategy equity curve:
While the short strategy equity curve:
Before moving to the next section, it is convenient to check some trading metrics. The financial_metrics function calculates the CARG, total trades, total open positions, time spent in the market, and maximum drawdown.
The result for both long and short positions are:
Metrics for the long strategy:
- CARG: 8.994%
- N Operations: 58
- Total positions: 29
- Time spent in the market: 78.82%
- Max Drawdown: 60.91%
Metrics for the short strategy:
- CARG: -3.32%
- N Operations: 123
- Total Open Positions: 61
- Time spent in the market: 51.17
- Maximum Drawdown: 75%
Strategy Optimization
The previous sections implemented an RSI with a 14-day window. This section will calculate the performance optimization. It will loop RSIs ranging from 3 to 100 days period, and calculate the profit and loss associated with each strategy.
The function optimization_roc_rsi calculates the total return for each strategy nominated in the previous paragraph. The function body has the code already discussed in this article. The variable last_benchmark_return appends the buy-and-hold strategy total profit and loss.
The results are:
The following image shows the code to plot the returns of each strategy. The term colors[-1] = ‘red’ changes the color from the last bar, which is the buy-and-hold strategy.
The preceding image illustrates the 97 strategies in blue and the buy-and-hold strategy in red. The first blue bar represents an RSI(3) strategy, while the last blue bar represents an RSI(99) strategy. The strategies show similar results from RSI(15) to RSI(99).
The first ten strategies, ranging from RSI(3) to RSI(12), generate returns of at least 65%. Three strategies yield returns of over 100%.
A pertinent question arises: why does the RSI(3) strategy generate a return of almost 160%, while the RSI(4) strategy yields around 80%?
This disparity suggests that the RSI(3) strategy may overfit the data. Similarly, the RSI(5) and RSI(8) strategies appear to overfit the data. The underlying reason lies in the concept of robustness. Two similar algorithms should produce comparable results. Therefore, the first ten strategies should exhibit similar return values; their returns should be in the range of 65% to 85%.
RSI(20) and ROC 15
The other important topic is to see what happens when the RSI window is higher or equal to 20. The following images show that situation:
The previous image shows the RSI and the closing price. The bands are 30% and 75%, respectively. Using an RSI(20) does not generate enough trades (see the values outside the bands).
Finally, the equity curve is:
The equity equity curve is very similar because the strategy generates long positions and tracks its historical price.