Buy and hold investing on the S&P 500 index with a low cost mutual fund is a top performing system; it can be straightforward relative to many other prevailing techniques employed by many top HF's.
In the below analysis, we'll use a simple moving average strategy and ultimately determine whether it outperforms a strategy investing over the course of 20 years. We'll use the The 200 day simple moving average of the the S&P 500 using the and hold if the month-end price is over the 200 day simple moving average (SMA) thereshold. Conversely, we'll sell if the SPY price was under the 200 day SMA on the last day of the month. The end of month signal will avoid all the intra-month false signals when price chops under and over the threshold. This is not the pinnacle of trend trading strategies, however it can provide a fundamental & solid approach to trading equities.
A backtest is a historical simulation of how a strategy would have performed should it have been run over a past period of time.To test our above hypothesis, we'll backtest this SMA Strategy on the SPY over the past 20 years.
As indicated above, since we're testing it on the S&P500, we'll use the SPDR S&P 500 Trust ETF which is represented by the ticker SPY. We'll use the yfinance module to get some simple data and feed it to our startegy to get going.
import yfinance
data = yfinance.download("SPY", start="2000-01-01", end="2021-06-16")
data.to_csv("spy.csv")
We initally had a default value of $1million of Cash. We can observe that the amount of cash we initially dispensed was $986,838 . (1028 * 95.92...) thereby leaving us some remaining cash. In the aftermath of the Buy-hold strategy, our portfolio value is $2,916,764 representing a total increase of 191.68% which is commendable.
Moving-Average Backtesting
We'll now implement the 200 Day SMA average in order to contrast the results with the above and determine which of these method can generate a greater alpha.
import pandas
import pandas_market_calendars as market_calendar
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed
from pyalgotrade.technical import ma
from pyalgotrade import plotter
from pyalgotrade.stratanalyzer import returns, drawdown, trades
Note that one of the rules that we have to take into account is the end of the month rule. In the above Buy-Hold strategy, we were looping bar by bar and continually making a decision at each bar iteration. Using this approach, we only want to make a decision if and only it is the last day of the month. Determining the last trading day of the month can actually be a non-trivial as it may potential fall January 31st, February 28th, February 29th on a leap year, market holidays etc. We'll use the import pandas_market_calendars coupled with the below 5 lines of code to take care of these specificities.
# get last days of month
nyse = market_calendar.get_calendar('NYSE')
df = nyse.schedule(start_date='2000-01-01', end_date='2021-12-31')
df = df.groupby(df.index.strftime('%Y-%m')).tail(1)
df['date'] = pandas.to_datetime(df['market_open']).dt.date
last_days_of_month = [date.isoformat() for date in df['date'].tolist()]
In the below, note how we're only using 98% of our cash. We're only using 98% to ensure that we have sufficent cash to fill our order. Additionally, in a scenario that we would want to keep hold of half of our "cash", we would pass in 0.50.
Overall, quite interesting to observe how splendid our simple startegy performed (No machine learning needed). It'd be very interesting to backtest some learning algorithms and see how they fare against this simple yet solid strategy.
We can see that by employing this strategy, our final portfolio value equates $6.5mil with an overall increase of 554.94% from our initial cash value thereby surpassing the 191.68% performance of the S&P500 <-> Buy&Hold Strategy over the last 20years. From 2003 to 2007, we were able to capture a huge upswing in the markets(output 1.1) and adjacently we were also able to avoid some of the major drawdowns in the market. As illustrated above (output 1.3), our maximum drawdown was 22.44% sparing us the 50% drawdown of the since we had sold our position on 12-31-2007. Similarly, with the coronavirus pandemic which sent equity markets reeling as the S&P500 plummeted 51%, our trading algorithm was able to evade the eye of this crisis as we exited on 02-28-2020.