S&P 500 Golden Cross and Death Cross with 50- and 200-Day SMAs
Summary
This document describes a simple long-only moving-average crossover strategy for the S&P 500 index. It enters when the 50-day simple moving average crosses above the 200-day average, and exits when the shorter average falls below the longer one. The accompanying Python implementation identifies regime changes and trades on the crossover day, rather than buying repeatedly while the bullish regime persists. It also closes any open position at the end of the selected test period.
The source code shows how historical prices are selected, crossover regimes are calculated, and trade and balance logs are produced. However, the document includes no backtest dates, performance statistics, or comparison with a benchmark, so it provides a rule set rather than evidence of profitability. The approach can remain out of the market during bearish regimes, but moving-average signals are lagging and may whipsaw in sideways markets. The example does not describe position sizing beyond using the strategy’s available capital.
Key ideas
- The strategy buys when the 50-day SMA crosses above the 200-day SMA.
- It sells an open position when the shorter average moves below the longer average.
- Entries depend on a change in regime, not simply the ongoing bullish state.
- The code closes a remaining position at the end of the selected period.
- No performance results are provided, and sideways markets can produce repeated reversals.
Tags
Full text
# golden-cross
# golden-cross
Golden Cross / Death Cross S&P 500 index (^GSPC)
1. sma50>sma200, buy
2. sma50<sma200, sell your long position.
## Source (MIT)
```python
"""
Golden Cross / Death Cross S&P 500 index (^GSPC)
1. sma50>sma200, buy
2. sma50<sma200, sell your long position.
"""
import pinkfish as pf
default_options = {
'use_adj' : False,
'use_cache' : True
}
class Strategy:
def __init__(self, symbol, capital, start, end, options=default_options):
self.symbol = symbol
self.capital = capital
self.start = start
self.end = end
self.options = options.copy()
self.ts = None
self.rlog = None
self.tlog = None
self.dbal = None
self.stats = None
def _algo(self):
pf.TradeLog.cash = self.capital
for i, row in enumerate(self.ts.itertuples()):
date = row.Index.to_pydatetime()
close = row.close
end_flag = pf.is_last_row(self.ts, i)
# Buy
# Note ts['regime'][i-1] is regime for previous day
# We want to buy only on the day of a moving average crossover
# i.e. yesteraday regime is negative, today it is positive
if self.tlog.shares == 0:
if row.regime > 0 and self.ts['regime'].iloc[i-1] < 0:
self.tlog.buy(date, close)
# Sell
else:
if row.regime < 0 or end_flag:
self.tlog.sell(date, close)
# Record daily balance
self.dbal.append(date, close)
def run(self):
# Fetch and selct timeseries
self.ts = pf.fetch_timeseries(self.symbol, use_cache=self.options['use_cache'])
self.ts = pf.select_tradeperiod(self.ts, self.start, self.end,
self.options['use_adj'])
# Add technical indicator: day sma regime filter.
self.ts['regime'] = \
pf.CROSSOVER(self.ts, timeperiod_fast=50, timeperiod_slow=200)
# Finalize timeseries
self.ts, self.start = pf.finalize_timeseries(self.ts, self.start,
dropna=True, drop_columns=['open', 'high', 'low'])
self.tlog = pf.TradeLog(self.symbol)
self.dbal = pf.DailyBal()
self._algo()
self._get_logs()
self._get_stats()
def _get_logs(self):
self.rlog = self.tlog.get_log_raw()
self.tlog = self.tlog.get_log()
self.dbal = self.dbal.get_log(self.tlog)
def _get_stats(self):
self.stats = pf.stats(self.ts, self.tlog, self.dbal, self.capital)
```Shown in full with attribution under the source's licence. Licence: MIT
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.