Sector/ETF momentum rotation (fork)
byVineet Sadananda @vineetsadanandav1forked from Sector/ETF momentum rotation by @michelle
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
Performance
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