Skip to content
All library documents

Applying the Double-7 Mean-Reversion Strategy Across a Portfolio

Article Strategy library · Author: Farrell Aultman (pinkfish)

Summary

This Python module adapts the Double-7 mean-reversion approach, described as originally designed for ETFs, to a portfolio-like comparison of lookback periods. Each period is represented as a separate symbol, allowing the user to examine several versions of the strategy as if they were portfolio constituents. The default configuration uses a seven-day period, a 200-period simple moving average, and a regime filter.

For each version, the strategy buys when the close reaches the period low, provided the regime condition passes, and assigns an equal share of the portfolio to that position. It exits when the close reaches the period high or when the data ends. The implementation fetches historical time series, constructs rolling highs and lows, applies indicators, and records trade logs and statistics. It supplies no empirical results, transaction-cost assumptions, or detailed treatment of overlapping positions. The code is a framework for testing alternative periods; it does not establish that the approach is profitable or that its settings generalize.

Key ideas

  • The module applies a Double-7 dip-buying and strength-selling rule to multiple lookback periods.
  • Each lookback period is represented as a distinct symbol for portfolio comparison.
  • Entries occur at a period low when the regime filter allows them.
  • Positions are equally weighted and exit at the period high or at the end of the data.
  • The source defines a testing framework but reports no performance evidence.

Tags

Full text
# double-7s-ave-portfolio


# double-7s-ave-portfolio









The double-7s-ave-portfolio stategy.

This is double-7s strategy applied to a portfolio.
The simple double 7's strategy was revealed in the book
'Short Term Strategies that Work: A Quantified Guide to Trading Stocks
and ETFs', by Larry Connors and Cesar Alvarez. It's a mean reversion
strategy looking to buy dips and sell on strength and was initially
designed for ETFs.

This module allows us to examine this strategy and try different
number of periods.  Each period is represented using a different
symbol, for example SPY_7 for 7 day high/low.  This allows us to
compare the periods as if we were comparing different stocks in a
portfolio.

## Source (MIT)

```python
"""
The double-7s-ave-portfolio stategy.

This is double-7s strategy applied to a portfolio.
The simple double 7's strategy was revealed in the book
'Short Term Strategies that Work: A Quantified Guide to Trading Stocks
and ETFs', by Larry Connors and Cesar Alvarez. It's a mean reversion
strategy looking to buy dips and sell on strength and was initially
designed for ETFs.

This module allows us to examine this strategy and try different
number of periods.  Each period is represented using a different
symbol, for example SPY_7 for 7 day high/low.  This allows us to
compare the periods as if we were comparing different stocks in a
portfolio.
"""

import pandas as pd

import pinkfish as pf


default_options = {
    'use_adj' : False,
    'use_cache' : True,
    'margin' : 1.0,
    'periods' : [7],
    'sma' : 200,
    '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.symbols = None
        self.ts = None
        self.rlog = None
        self.tlog = None
        self.dbal = None
        self.stats = None

    def _algo(self):

        pf.TradeLog.cash = self.capital
        pf.TradeLog.margin = self.options['margin']

        # Loop though timeseries.
        for i, row in enumerate(self.ts.itertuples()):

            date = row.Index.to_pydatetime()
            end_flag = pf.is_last_row(self.ts, i)
            
            # Get the prices for this row, put in dict p.
            p = self.portfolio.get_prices(row, fields=['close'])

            # Loop though each symbol in portfolio.
            for symbol in self.portfolio.symbols:

                period_high_field = 'period_high' + str(symbol.split('_')[1])
                period_low_field  = 'period_low'  + str(symbol.split('_')[1])
                period_high = getattr(row, period_high_field)
                period_low = getattr(row, period_low_field)
                
                # Use variables to make code cleaner.
                close = p[symbol]['close']
                
                # Sell Logic
                # First we check if an existing position in symbol should be sold
                #  - sell if price closes at X day high
                #  - sell if end of data by adjusted the percent to zero

                if symbol in self.portfolio.positions:
                    if close == period_high or end_flag:
                        self.portfolio.adjust_percent(row, 0, symbol)

                # 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 at X day low

                else:
                    if (((row.regime > 0 or close > row.sma) or not self.options['use_regime_filter'])
                          and close == period_low):
                        # Use equal weight.
                        weight = 1 / len(self.portfolio.symbols)
                        self.portfolio.adjust_percent(row, weight, symbol)

            # record daily balance
            self.portfolio.record_daily_balance(row)

    def run(self):
        # Build the list of symbols.
        periods = self.options['periods']
        self.symbols = []
        for period in periods:
            symbol = self.symbol + '_' + str(period)
            self.symbols.append(symbol)

        self.portfolio = pf.Portfolio()
        self.ts = self.portfolio.fetch_timeseries(self.symbols, self.start, self.end,
                    fields=['close'],
                    use_cache=self.options['use_cache'],
                    use_adj=self.options['use_adj'])

        # Fetch symbol time series.
        ts = pf.fetch_timeseries(self.symbol, use_cache=self.options['use_cache'])
        ts = pf.select_tradeperiod(ts, self.start, self.end, use_adj=self.options['use_adj']) 

        # Add technical indicator: 200 sma regime filter.
        self.ts['regime'] = pf.CROSSOVER(ts, timeperiod_fast=1, timeperiod_slow=200)

        # Add technical indicator: X day sma.
        self.ts['sma'] = pf.SMA(ts, timeperiod=self.options['sma'])

        # Add technical indicator: X day high, and X day low.
        for period in periods:
            self.ts['period_high'+str(period)] = pd.Series(ts.close).rolling(period).max()
            self.ts['period_low'+str(period)]  = pd.Series(ts.close).rolling(period).min()

        # Finalize timeseries.
        self.ts, self.start = pf.finalize_timeseries(self.ts, self.start, dropna=True)

        # Init trade log objects.
        self.portfolio.init_trade_logs(self.ts)

        self._algo()
        self._get_logs()
        self._get_stats()

    def _get_logs(self):
        self.rlog, self.tlog, self.dbal = self.portfolio.get_logs()

    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.