Skip to content
Lokin
Paper trading
Sign in

Sector/ETF momentum rotation (fork)

byVineet Sadananda @vineetsadanandav1forked from Sector/ETF momentum rotation by @michelle

MomentumRebalanceETFETFs

At each month’s first open, hold the four ETFs with the lowest returns over the previous calendar month and exit the rest on GLD, IWM, QQQ

1 net votes (1 up, 0 down)

The creator hasn't run this agent in paper yet.

The backtest result, the creator's paper run, and your own paper runs are separate sources and are never combined into one return.

About this agent

Trades GLD, IWM, QQQ, SPY and 5 more with one rule: at each month’s first open, hold the four ETFs with the lowest returns over the previous calendar month and exit the rest.. Positions are checked every month, with equal money in up to 4 names. Over the test period from 2020 to 2026 it returned +96%, against +145% for the benchmark, with a worst drop of -20%. Simulated results with trading costs included; not a prediction.

The plan

Version 1 always runs exactly this plan. A change of any kind is published as a new version.

Buy rule
At each month’s first open, hold the four ETFs with the lowest returns over the previous calendar month and exit the rest.
Show the exact rule

The calculation below is saved with this version and used when it runs.

Settings: hold_count = 4 · uses 42 days of history

def signal(bars, params):
    if bars.phase != "open" or bars.date != bars.anchor("month_start"):
        return None
    end_date = bars.anchor("previous_month_end")
    if end_date is None:
        return None
    month = end_date[:7]
    scores = {}
    for ticker in bars.symbols:
        history = bars.history[ticker]
        dates = list(history.dates)
        closes = list(history.close)
        month_indices = [i for i in range(len(dates)) if dates[i][:7] == month and closes[i] is not None]
        prior_indices = [i for i in range(len(dates)) if dates[i][:7] < month and closes[i] is not None]
        if month_indices and prior_indices:
            first_month = min(month_indices)
            last_month = max(month_indices)
            earlier = [i for i in prior_indices if i < first_month]
            if earlier:
                prior = max(earlier)
                scores[ticker] = closes[last_month] / closes[prior] - 1
    count = params["hold_count"]
    if len(scores) < count:
        return None
    ranked = sorted(scores, key=lambda ticker: scores[ticker])
    selected = ranked[:count]
    weight = 1 / count
    return {ticker: (weight if ticker in selected else 0) for ticker in bars.symbols}
Stocks and ETFs
GLD, IWM, QQQ, SPY, TLT, XLE, XLF, XLK, XLV
Position sizes
Buying only · equal money in each · up to 4 at a time · at most 25% in one · checked every month
Orders and costs
Orders fill at the next day's open · costs 0.05% of each trade
Test period
Jul 27, 2020 to Oct 13, 2026 · compared with SPY

Backtest at a glance

Return
+95.8%
SPY, same period
+145.4%
Worst drop
-20.4%
Closed trades
142

See the full backtest result