This repository stores the codes to backtest several momentum-based trading strategies.
From the point of view of an asset-management entity, the goal is to maximise the returns through holding positive positions of stocks while adhering to the agreed-upon trading strategy and universe of assets.
- The stocks under consideration are what are famously-known as the "Magnificent 7" companies today: AAPL, MSFT, META, TSLA, NVDA, AMZN and GOOG.
- Only long positions are allowed.
- Rebalancing is allowed only when a trading signal is triggered.
- Backtest for the years 1981 to 2023 inclusive.
- Daily level price data.
- Evaluate signal post-market, react on market-open.
All parameters are contained within the ./config.py
file.
Parameter | Default Value | Description |
---|---|---|
CAPITAL_0 | 1,000,000 | Initial capital for investment. |
COMMISSION_RATE | 0.0010 | Commission rate per transaction. |
SLIPPAGE_RATE | 0.0002 | Estimated slippage per transaction. |
MIN_TRANSC | 10 | Minimum transaction size (in number of stocks). |
MAX_PROP | 0.30 | Maximum proportion of asset under management (AUM) to be risked per instrument. |
RISK_FREE_RATE | 0.02 | Annual risk-free interest rate. |
BUSINESS_DAYS | 252 | Number of business days in a year. |
No. | Strategy Name | Notebook Name | Description |
---|---|---|---|
1 | RSI | 01 rsi-only.ipynb | The idea is to buy an instrument when it is "oversold" i.e. having a low RSI value. |
2 | MACD+RSI | 02 macd-and-rsi.ipynb | Consider buying an instrument when its MACD Difference crosses zero from below. RSI must be relatively oversold to enter this position. |
3 | HRP | 03 hrp-alloc.ipynb | An extension of the MACD+RSI strategy. Portfolio allocation is done through Hierarchical Risk Parity. |
4 | VolAdj | 04 voladj-alloc.ipynb | An extension of the MACD+RSI strategy. Portfolio allocation is done through proportionally allocating funds based on each instrument's rolling returns per standard deviation. |
File Name | Function | Description |
---|---|---|
backtest.py | run_backtest() | Given the positions signal and relevant price data, the function in this module runs the backtest to produce two tables - portfolio (in number of shares) and portfolio_value (in $) for each trading day. |
calculate_macd.py | calculate_macd_signal() | Computes the MACD (Moving Average Convergence Divergence) difference indicator signal. |
calculate_rsi.py | calculate_rsi() | Computes the RSI (Relative Strength Index) indicator signal. |
generate_positions_macd.py | generate_positions_macd() | Generates trading positions based on MACD signals. "1" represents a long position is active while "0" represents no active positions. |
generate_positions_rsi.py | generate_positions_rsi() | Generates trading positions based on RSI signals. "1" represents a long position is active while "0" represents no active positions. |
hrp.py | calculate_hrp_weights() | Calculates the Hierarchical Risk Parity (HRP) portfolio allocation weights. |
metrics.py | calculate_portfolio_metrics() | Defines performance metrics for evaluating strategies. |
read_data.py | read_data() | Handles reading and processing data from source. |
voladj.py | calculate_voladj_weights() | Calculates the volatility-adjusted portfolio allocation weights. |
Metric | RSI | MACD+RSI | HRP | VolAdj | S&P500 |
---|---|---|---|---|---|
Total Return | 1.8662 | 56.8983 | 0.2976 | 2.2947 | 33.9848 |
Annualised Return | 0.0486 | 0.1251 | 0.0062 | 0.0317 | 0.1039 |
Annualised Volatility | 0.2143 | 0.2163 | 0.0142 | 0.0827 | 0.1797 |
Maximum Drawdown | 0.6484 | 0.4885 | 0.0631 | 0.2757 | 0.5678 |
Sharpe Ratio | 0.1280 | 0.4527 | -0.9762 | 0.1354 | 0.4391 |
Sortino Ratio | 0.2012 | 0.7331 | -1.4788 | 0.2158 | 0.6841 |
Total Number of Trades | 1,504 | 1,437 | 833 | 754 | |
Average Return per Trade | 0.0012 | 0.0396 | 0.0004 | 0.0030 | |
Win Rate | 0.6918 | 0.7519 | 0.7970 | 0.6305 | |
Expectancy | 6,009 | 211,358 | 2,237 | 9,215 |
-
RSI is considered a leading indicator. Using RSI alone to indicate mean-reversion timings produced a return far lower than the S&P500 index.
-
Adding MACD as a lagging indicator for momentum, the results significantly outperformed the first model. The model has also outperformed the S&P500 index in total returns over the long run.
-
While it is tempting to minimise the drawdown and volatility of the trading strategy in step 2, both approaches that were experimented with resulted in significantly worse metrics.
(a) Both HRP and Volatility-adjusted portfolio optimisation approaches aim to reduce volatility in the portfolio by setting a lower weight to the higher volatility instruments.
(b) Given that the magnificent 7 companies are high-performing in hindsight, they are in a long-term uptrend. In such an uptrend, the returns are usually driven by the more volatile stocks. The de-prooritisation of these stocks resulted in the strategies missing out on the upside.
(c) Volatility-adjusted portfolio optimisation is unsuitable to be used together with our mean-reverting RSI strategy. By definition, an instrument that is oversold has experienced a recent downtrend. Adjusting the portfolio by looking at its recent volatility-adjusted returns are not helpful, given that the returns are likely poor.