Skip to content
Lokin
Paper trading
Sign in

Sector/ETF momentum rotation

byMichelle @michellev1

MomentumETFs

Each month, compare SPY, QQQ, IWM, XLK, XLF, XLE, XLV, GLD, and TLT based on their trailing 1-month returns. Invest equally in the top 3 ETFs.

2 net votes (2 up, 0 down)
Backtest· v1
+185.1%
Jan 2020 – May 2026 · worst drop −24.0%
Growth of $100, Jan 2020 – May 2026. Simulated, with trading costs included.

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 the open of each month’s first trading day, rank the ETFs by adjusted close-to-close return over the previous calendar month, target equal one-third weights in the top three, and exit the others.. Positions are checked every month, with equal money in up to 3 names. Over the test period from 2020 to 2026 it returned +185%, against +140% for the benchmark, with a worst drop of -24%. 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 the open of each month’s first trading day, rank the ETFs by adjusted close-to-close return over the previous calendar month, target equal one-third weights in the top three, and exit the others.
Show the exact rule

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

def signal(bars, params):
    if bars.phase != 'open' or bars.date != bars.anchor('month_start'):
        return {}

    previous_month_end = bars.anchor('previous_month_end')
    previous_month_dates = [d for d in bars.dates if d[:7] == previous_month_end[:7]]
    if not previous_month_dates:
        return {}
    first_month_index = bars.dates.index(previous_month_dates[0])
    if first_month_index == 0:
        return {}
    start_date = bars.dates[first_month_index - 1]

    ranked = []
    for ticker in bars.symbols:
        history = bars.history[ticker]
        if start_date not in history.dates or previous_month_end not in history.dates:
            continue
        start_price = history.close[history.dates.index(start_date)]
        end_price = history.close[history.dates.index(previous_month_end)]
        if start_price is None or end_price is None or start_price <= 0:
            continue
        ranked.append((float(end_price) / float(start_price) - 1.0, ticker))

    if len(ranked) < 3:
        return {}
    ranked.sort(key=lambda item: (-item[0], item[1]))
    selected = [ticker for score, ticker in ranked[:3]]
    return {ticker: (1.0 / 3.0 if ticker in selected else 0.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 3 at a time · at most 34% in one · checked every month
Orders and costs
Orders fill at the next day's open · costs 0.05% of each trade
Test period
Jan 1, 2020 to May 25, 2026 · compared with SPY

Backtest at a glance

Return
+185.1%
SPY, same period
+140.2%
Worst drop
-24.0%
Closed trades
121

See the full backtest result