Equity Trend Entries with an S&P 500 Regime Filter
Summary
This long-only trend approach is applied separately to individual securities. It compares each security’s close with a simple moving average, optionally adjusted by a percentage band, and uses the S&P 500’s relationship to its own 200-day moving average as a market regime filter. The strategy buys when the security closes above its upper band while the regime is positive, then sells if the regime turns negative, the close falls below the lower band, or the data ends.
The included code describes the signals and data-processing workflow, including trade logs and portfolio statistics, but the document provides no actual backtest results or evidence of profitability. The default band width is zero, so the entry and price-based exit levels coincide with the security’s moving average unless changed. The source also sets adjusted prices off by default and relies on the quality and alignment of the security and index time series; those choices can affect results across instruments and periods.
Key ideas
- The strategy uses the S&P 500’s 200-day moving-average crossover as a broad market regime filter.
- A security is bought after its close rises above a configurable band around its moving average while the regime is positive.
- An open position is sold when the regime turns negative, the security closes below its lower band, or the series ends.
- The default percentage band is zero, making the moving average itself the entry and price exit threshold.
- The document provides implementation logic but no reported performance results.
Tags
Full text
# follow-trend
# follow-trend
A basic long term trend strategy applied separately to several
securities.
1. S&P 500 index closes above its 200 day moving average
2. The stock closes above its upper band, buy
3. S&P 500 index closes below its 200 day moving average
4. The stock closes below its lower band, sell your long position.
## Source (MIT)
```python
"""
A basic long term trend strategy applied separately to several
securities.
1. S&P 500 index closes above its 200 day moving average
2. The stock closes above its upper band, buy
3. S&P 500 index closes below its 200 day moving average
4. The stock closes below its lower band, sell your long position.
"""
import pinkfish as pf
default_options = {
'use_adj' : False,
'use_cache' : True,
'sma_period': 200,
'percent_band' : 0,
'use_regime_filter' : 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)
upper_band = row.sma * (1 + self.options['percent_band'] / 100)
lower_band = row.sma * (1 - self.options['percent_band'] / 100)
# Sell Logic
# First we check if an existing position in symbol
# should be sold
# - Sell if (use_regime_filter and regime < 0)
# - Sell if price closes below lower_band
# - Sell if end of data
if self.tlog.shares > 0:
if ((self.options['use_regime_filter'] and row.regime < 0)
or close < lower_band
or end_flag):
self.tlog.sell(date, close)
# Buy Logic
# First we check to see if there is an existing position,
# if so do nothing
# - Buy if (regime > 0 or not use_regime_filter)
# and price closes above upper_band
# and (use_regime_filter and regime > 0)
else:
if ((row.regime > 0 or not self.options['use_regime_filter'])
and close > upper_band):
self.tlog.buy(date, close)
# Record daily balance
self.dbal.append(date, close)
def run(self):
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
self.ts['sma'] = pf.SMA(self.ts, timeperiod=self.options['sma_period'])
# add S&P500 200 sma regime filter
ts = pf.fetch_timeseries('^GSPC')
ts = pf.select_tradeperiod(ts, self.start, self.end, use_adj=False)
self.ts['regime'] = \
pf.CROSSOVER(ts, timeperiod_fast=1, timeperiod_slow=200)
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.