This tutorial presents a comprehensive approach to backtesting a trend-following trading strategy on S&P 500 energy sector stocks using daily OHLC (Open, High, Low, Close) data. Our strategy is based on a two-trend system that identifies and capitalizes on short-term and long-term market movements. The backtesting period extends from January 2022 through December 2023, with a starting investment capital of $100,000. This strategy permits both long and short positions, enabling flexibility in response to market trends.
Performance evaluation of the backtested strategy includes measures such as total return, Sharpe ratio, maximum drawdown, and win-loss ratio, providing a holistic view of the strategy’s effectiveness and risk-adjusted returns. Additionally, the tutorial details the optimization process for selecting the best parameters for the moving averages and other parameters ensuring the strategy is finely tuned for the specific characteristics of the energy sector.
Following the initial backtesting, the strategy is applied to another industrial sector, Health Care to assess its versatility and performance in different market conditions. Furthermore, a retrospective analysis is conducted for the period January 2019 through December 2020 to compare the strategy’s performance during different market phases. This comparison reveals insights into the strategy’s adaptability and potential areas for adjustment in response to varying market environments.
The tutorial concludes with key takeaways, emphasizing the importance of trend analysis, risk management, and the adaptability of trading strategies across different sectors and time periods.
Understanding of Financial Market Data Manipulation: Learners will gain hands-on experience in manipulating and analyzing financial market data, focusing on OHLC (Open, High, Low, Close) data and moving averages, which are crucial for trend analysis.
Application of Technical Analysis in Trading: The script applies fundamental concepts of technical analysis, such as moving averages, to generate buy or sell signals. This provides practical insights into how traders can use historical price data to make informed decisions.
Development of Trading Strategies: By following this script, learners will understand the steps involved in developing and testing a trading strategy. This includes setting initial capital, defining trading signals, managing positions, and implementing risk management rules.
Backtesting and Optimization: The script demonstrates how to backtest a trading strategy against historical data to evaluate its performance. This is vital for understanding the strategy’s potential profitability and risk before applying it in live trading.
Risk Management: Through the implementation of maximum trade value and maximum daily trades, learners will appreciate the importance of risk management in trading. This includes how to size positions and when to exit trades to minimize losses.
Performance Evaluation: The script calculates key performance metrics such as total return, Sharpe ratio, and maximum drawdown. Learning how to evaluate these metrics is crucial for assessing the strategy’s effectiveness and comparing it to other strategies or benchmarks.
This tutorial breaks down the process of creating and testing a trading strategy into five clear steps, making it easier to follow and implement.
1. Trading Strategy Definition: We start by clearly laying out our trading strategy. This means clearly stating what we want to achieve, the signals that will tell us when to buy or sell, and the rules for starting and ending trades.
2. Setting Parameters: Next, we detail our strategy’s goals, risk levels, and other important benchmarks like maximum value per trade and maximum trades per day.
3. Generating Indicators : We generate technical indicators, Fast Moving Average (FMA) and Slow Moving Average (SMA), enabling the identification of trend-based trading signals.
4. Generating Signals : We then create a system to find buy or sell signals from past market data. This involves analyzing historical trends that match our strategy’s criteria to identify the best times to trade.
5. Applying Strategy Rules: Here, we turn the theoretical aspects of our strategy into actual trading actions. This means translating when and how we’ll enter and exit trades into concrete steps, ensuring our trades stick to our original plan.
6. Managing the Portfolio: This step focuses on keeping a close eye on our investments, making sure everything is accounted for and managed properly.
7. Reviewing Performance: The last step involves regularly checking how our trades are doing, and evaluating if our strategy meets our goals.
Further Exploration:
Conclusion: By simplifying this process into manageable steps, this tutorial offers a straightforward path to developing, implementing, and refining a trading strategy.
The trend strategy is designed for trading within the S&P 500 energy sector stocks. It utilizes moving averages as indicators to capture market trends and guide trading decisions.
The use of two moving averages helps to identify the momentum and direction of stock prices by comparing short-term price movements to longer-term trends.
We now see how positions are opened, managed, and closed:
The above R script snippet is setting up an environment for financial data analysis by loading libraries for technical analysis and visualization. It also includes steps to define the working directory, and clear the workspace to start with a clean slate.
We then load a 5-year financial dataset and sector information, specifically focusing on the Energy sector’s stocks from 2021 to 2023. It sets the trading strategy’s initial capital at $100,000, limits trades to $5,000 each, and restricts to 8 trades per day.
In lines (40-56), the genIndicators function prepares
technical indicators for a specific stock symbol. It selects the stock’s
data (42), converts it for time series calculations (43), and computes
the fast and slow moving averages (46-47). It then filters the data to
start from January 1, 2022 (53) for backtesting, resets row names (54),
and then returns the data frame with added indicators (55-56).
Below are snapshots displaying the indicators via dataframe and graph (APA and MPC stock):
We select the data filtering by stock , converting it to a time series format for easier analysis (66), and creates two signals: “cross.upper” when the closing price is above both the fast and slow moving averages, indicating a bullish trend (68-70), and “cross.lower” when below, signaling a bearish trend (71-73).
In lines (75-90), We define trading signals for a stock symbol. It creates a “cross.trendup” signal for when the closing price is above either moving average, suggesting a potential upward trend (75-77), and a “cross.trenddn” signal for when the closing price is below at least one, indicating a possible downward trend (78-80).
Below is a snapshot displaying the signals:
Lines (199-212) applyRules function, performs two main tasks: it first evaluates and closes any existing positions that meet the strategy’s sell criteria, then it identifies and opens new positions based on the buy criteria. Later, we update the total equity after accounting for the profits or losses from closed positions and returns the current state of the portfolio, including open positions and available cash.
The R function closePositions in lines (101-116) manages
the closure of trading positions. If there are any open positions, it
determines which ones to close based on the day’s trading signals. It
handles both long and short positions separately, closing long positions
when a downtrend signal is detected and short positions when an uptrend
signal is present.
The R code in lines (117-127) processes the final steps in closing trading positions. If there are any positions that have been closed, it calculates the cash realized from each, the profit or loss, and updates the total cash balance accordingly. If no positions have been closed, it sets the list of closed positions to NULL. Finally, the function returns a list with the details of the closed positions and the updated cash balance.
In openPositions function:
openshort to NULL if no new short
positions are identified.In lines (168-191), the openPositions function finalizes
the process of opening new trading positions. It starts by assigning buy
or sell prices to the newly opened positions, depending on whether they
are long or short. The function then limits the number of trades to the
maximum daily allowed, ensuring it does not exceed the set parameter
maxdaytrades. It calculates the trade amount, distributing
the available equity evenly across all trades, and adjusts the position
sizes based on this calculation. The cash spent on opening the positions
is accumulated to update the total cash balance. If no positions are
opened, it sets the opened variable to NULL. Finally, the
function returns a list that includes the opened positions and the
remaining cash after these new positions have been taken.
We manage the daily portfolio through a loop that iterates over each
trading day. It calls applyRules (line 299) to decide on
opening or closing positions and adjusts the cash balance based on these
trades (line 302). On days with open positions, it calculates the
current market value of these holdings (lines 304-308) and updates the
portfolio’s value accordingly. The portfolio value for each day is
determined by combining the value of open positions with the current
cash balance (line 312). If no positions are open, the portfolio value
for that day is recorded as zero (line 310).
The portfolioStats function, calculates various
statistics to evaluate trading performance. It determines the number of
unique trading days, total trades, and distinguishes between long and
short trades. It computes the percentage of winning trades, average
returns for long and short positions separately, and the overall
percentage of profitable trades. Additionally, the function calculates
cumulative returns, maximum returns, and maximum drawdown percentages to
assess the risk and return profile of the trading strategy. It also
calculates the longest drawdown period and the Sharpe ratio, which is a
measure of risk-adjusted return.
Here’s a glimpse of our trading strategy’s outcomes for HAL stock, showcasing a successful example of both a long and a short trade.
The provided chart illustrates the application of our dual moving average crossover strategy on the stock price of HAL. In this strategy:
A Long Position is initiated when the stock’s price (represented by the black line) crosses above both the Fast Moving Average (FMA, in green) and the Slow Moving Average (SMA, in blue), indicating an uptrend. For instance, we see a “Buy” signal where the stock is bought at $23.14 when the price crosses above both moving averages.
The position is then Closed (sold) when the stock price drops below either moving average, suggesting the uptrend may be weakening. This is observed in the “Sell” transaction at $31.46.
Conversely, a Short Position is entered when the stock price falls below both the FMA and SMA, signaling a downtrend. This is marked by “Short” at $38.65.
The short position is Covered (bought back) when the stock price rises above either moving average, implying the downtrend might be reversing, shown by “Cover” at $38.08.
The chart shows three different metrics: Cumulative Return, Max Return, and Portfolio Return. The black line representing Cumulative Return likely shows the compounded total return of the portfolio over the observed period. The red line for Max Return indicates the highest value the portfolio has reached, a useful measure for understanding the portfolio’s peak performance. The blue line, showing Portfolio Return, might represent the actual return of the portfolio at the end of the period.
The performance summary suggests that the trading strategy resulted in a total of 1275 trades. Out of these, 679 were long trades with a win rate of approximately 54.63%, and 596 were short trades with a slightly lower win rate of around 54.19%. The average return on long trades was about 2.49%, while for short trades, it was slightly lower at around 1.94%. Overall, the strategy won 54.43% of the time.
The cumulative return of 2.09 indicates that the portfolio more than doubled over the period tested. The mean daily return was very low, suggesting that while the strategy was profitable, the profits on any given day were quite small, which is typical for high-frequency trading strategies that don’t hold positions for long.
The Sharpe ratio of 2.33 is quite high, which often indicates a good risk-adjusted return. However, the maximum drawdown of -6.004 suggests that at one point, the portfolio lost more than 6% of its value from the previous high, which might be acceptable given the overall returns but still represents a significant risk. The maximum drawdown period of 36 days shows that the longest losing streak lasted just over a month, which should be manageable in the context of the overall performance.
Considering the initial equity was set at $100,000 with a maximum trade value of $5,000 and a limit of 8 trades per day, the strategy seems to have managed risk well, not committing more than 5% of the total capital to any single trade and keeping the number of daily trades to a conservative level. The positive Sharpe ratio and overall profitability indicate that the strategy could be effective for managing a similar-sized portfolio within the constraints set.
We experimented with various settings to see how changes might improve our strategy. Below are the results for the hyper parameters we tried:
| Max Val per Trade | Max Trades per day | FMA size | SMA size | Cum. Return | Sharpe Ratio |
|---|---|---|---|---|---|
| 5000 | 8 | 15 | 30 | 2.09 | 2.33 |
| 5000 | 8 | 15 | 45 | 1.94 | 2.06 |
| 9000 | 20 | 15 | 30 | 2.80 | 2.43 |
| 5000 | 8 | 20 | 40 | 1.74 | 1.66 |
| 5000 | 10 | 20 | 40 | 1.68 | 1.57 |
Key Observations:
Moving Averages: Shorter moving averages (FMA size 5, SMA size 10) tend to result in hi~gher returns in your experiments. This could indicate that the strategy benefits from being more sensitive to recent price changes, capturing profits from shorter-term trends or market movements.
Trade Value and Frequency: A higher number of trades per day (16 vs. 8) and larger “Max Val per Trade” amounts ($10,000 vs. $5,000) generally led to higher returns, suggesting the strategy scales well with increased trading activity and capital allocation per trade.
Sharpe Ratio: A higher Sharpe Ratio is typically better as it indicates higher risk-adjusted returns. In our experiments, the highest Sharpe Ratio was achieved with the most frequent trading and the shortest moving averages, implying an optimal balance of risk and reward in this configuration.
We have applied the best settings from our previous experiments with trading parameters to a new sector (Utilties) and here are the performance results of the trading strategy.
The results suggests a promising strategy for the health care sector, making our strategy robust.