Skip to content
All library documents

Scaling Into and Out of SPY With Double-Seven Signals

Article Strategy library · Author: Farrell Aultman (pinkfish)

Summary

This strategy scales exposure to SPY using rolling price extremes, with a 200-day moving average as a market-regime filter. When SPY is above that average, a close at a period low increases the target share allocation in increments. A close at a period high reduces it in increments, and the final bar also triggers a reduction. The default period is seven days, with up to four increments when scaling is enabled.

The document provides implementation logic and configurable settings, but no performance results or empirical comparison. The increment size is tied to the maximum number of open trades; disabling scaling changes the increment behavior. The code tracks allocation by adjusting the share percentage, rounds weights, and records balances and trade statistics. This describes a long-only equity strategy framework, not evidence that the signals are profitable. It does not specify transaction-cost assumptions, slippage, or a tested sample period, so those factors and the effects of parameter choices remain unassessed.

Key ideas

  • The strategy only adds exposure when SPY is above its 200-day moving average.
  • A close at a rolling period low increases the share allocation by an increment.
  • A close at a rolling period high reduces exposure, and the final data bar also triggers a reduction.
  • The default settings use a seven-day period and allow up to four allocation increments.
  • The document gives implementation details but reports no backtest evidence or trading costs.

Tags

Full text
# scaling-in-out


# scaling-in-out









Scaling in and out of using the double-7s strategy.

1. The SPY is above its 200-day moving average.
2. The SPY closes at a X-day low, buy some shares.  If it sets further
   lows, buy some more.
3. If the SPY closes at a X-day high, sell some.  If it sets further
   highs, sell some more, etc...

## Source (MIT)

```python
"""
Scaling in and out of using the double-7s strategy.

1. The SPY is above its 200-day moving average.
2. The SPY closes at a X-day low, buy some shares.  If it sets further
   lows, buy some more.
3. If the SPY closes at a X-day high, sell some.  If it sets further
   highs, sell some more, etc...
"""

import pandas as pd

import pinkfish as pf


default_options = {
    'use_adj' : False,
    'use_cache' : False,
    'margin' : 1,
    'period' : 7,
    'max_open_trades' : 4,
    'enable_scale_in' : True,
    'enable_scale_out' : True
}

def _round_weight(weight):
    if    weight > 99/100: weight = 1
    elif  weight < 5/100: weight = 0
    else: weight = round(int(weight*100)) / 100
    return weight

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
        pf.TradeLog.margin = self.options['margin']

        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)

            max_open_trades = self.options['max_open_trades']
            enable_scale_in = self.options['enable_scale_in']
            enable_scale_out = self.options['enable_scale_out']
            max_open_trades_buy  = max_open_trades if enable_scale_in  else 1
            max_open_trades_sell  = max_open_trades if enable_scale_out  else 1

            # Buy Logic
            #  - Buy if still open trades slots left
            #       and bull regime
            #       and price closes at period low
            #       and not end end_flag

            if (row.regime > 0 and close == row.period_low and not end_flag):
                # Get current, then set new weight
                weight = self.tlog.share_percent(close)
                weight += 1 / max_open_trades_buy
                weight = _round_weight(weight)
                self.tlog.adjust_percent(date, close, weight)

            # Sell Logic
            # First we check if we have any open trades, then
            #  - Sell if price closes at X day high.
            #  - Sell if end of data.

            elif (self.tlog.shares > 0 
                  and (close == row.period_high or end_flag)):
                # Get current, then set new weight
                weight = self.tlog.share_percent(close)
                weight -= 1 / max_open_trades_sell
                weight = _round_weight(weight)
                self.tlog.adjust_percent(date, close, weight)

            # Record daily balance.
            self.dbal.append(date, close)

    def run(self):

        # Fetch and select 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,
                                        use_adj=self.options['use_adj'])

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

        # Add technical indicators: X day high, and X day low.
        self.ts['period_high'] = pd.Series(self.ts.close).rolling(self.options['period']).max()
        self.ts['period_low'] =  pd.Series(self.ts.close).rolling(self.options['period']).min()

        # Finalize timeseries.
        self.ts, self.start = pf.finalize_timeseries(self.ts, self.start,
                                dropna=True, drop_columns=['open', 'high', 'low'])

        # Create tlog and dbal objects.
        self.tlog = pf.TradeLog(self.symbol)
        self.dbal = pf.DailyBal()

        # Run algo, get logs, and get stats.
        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.