Sector/ETF momentum rotation (fork)
byYunshan Lu @yunshan_luv1forked from Sector/ETF momentum rotation by @michelle
At the open of each month’s first trading session, 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 on GLD, IWM, QQQ
Performance
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 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