If you trade precious metals and want a low-maintenance, rules-based, long-only algorithmic strategy for Gold (XAUUSD) and Silver (XAGUSD), this post is for you. Today I share a complete weekly momentum rotation strategy built in Python for the ALGOGENE platform. It runs on daily data, rebalances once per week, only takes long positions, and is easy to understand, modify, and backtest.
What Is This Strategy?
This is a relative momentum rotation strategy focused on the two most liquid precious metals: Gold and Silver. The core idea is simple:
- Calculate the 20-day price momentum for both XAUUSD and XAGUSD
- Each week, select and hold the metal with stronger positive momentum
- If both have negative momentum, stay in cash (no positions)
- Fully automated: no short selling, weekly rebalance only, minimal monitoring
It is designed for trend-following in precious metals, reduces whipsaws by using weekly frequency, and avoids directional bias by picking the stronger asset each week.
Strategy Logic & Rules
This strategy runs on daily data (1D interval) and uses position netting = False for clean execution. Here are the clear, step-by-step rules:
Core Parameters
- Instruments: XAUUSD (Gold), XAGUSD (Silver)
- Trading mode: Long only (short selling = False)
- Rebalance frequency: Once per calendar week
- Momentum lookback: 20 trading days (from 25-day historical bars)
- Position size: Fixed 0.01 lot per trade
- Data interval: Daily (1D) bars
Trading Rules
- Weekly Check: The strategy runs only once per week to avoid over-trading.
- Momentum Calculation: Compute momentum as (Current Close − Close 20 days ago) / Close 20 days ago.
- Asset Selection:
- If both Gold and Silver have negative momentum: hold cash, close all positions.
- If Gold momentum > Silver momentum: go long XAUUSD.
- If Silver momentum > Gold momentum: go long XAGUSD.
- Rebalance Mechanism:
- First close all positions in the non-target metal.
- After closing completes, open a long position in the target metal.
- If already holding the correct asset, do nothing.
This structure keeps turnover low, transaction costs minimal, and logic transparent for debugging and optimization
How It Works (Step-by-Step)
Step 1: Initialize Strategy & Settings
We define the target instruments, fixed trading volume, and weekly rebalance tracking variables. The strategy uses ALGOGENE’s native API for order management and historical data.
Step 2: Compute 20-Day Price Momentum
The strategy pulls 25 daily historical bars and calculates the 20-day price momentum—a standard trend indicator that measures the strength and direction of the recent price move.
Step 3: Weekly Rebalance Logic
Each new calendar week, it compares momentum scores between Gold and Silver. It then updates the target instrument and triggers the rebalance function.
Step 4: Automated Position Rebalancing
The rebalance() method first closes unwanted positions. After a close order fills, it continues rebalancing to open the target metal. This ensures the portfolio always matches the weekly signal.
Full Python Source Code for ALGOGENE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | from AlgoAPI import AlgoAPIUtil, AlgoAPI_Backtest class AlgoEvent: def __init__(self): self.gold = "XAUUSD" self.silver = "XAGUSD" self.volume = 0.01 self.last_week = None self.target = None def start(self, mEvt): self.evt = AlgoAPI_Backtest.AlgoEvtHandler(self, mEvt) self.evt.start() def on_bulkdatafeed(self, isSync, bd, ab): if not isSync: return timestamp = bd[self.gold]["timestamp"] week = timestamp.isocalendar()[:2] # Run once every calendar week if week == self.last_week: return gold_momentum = self.momentum(self.gold, timestamp) silver_momentum = self.momentum(self.silver, timestamp) if gold_momentum is None or silver_momentum is None: return self.last_week = week self.evt.consoleLog( "Weekly rebalance:", timestamp, "XAUUSD momentum=", round(gold_momentum, 6), "XAGUSD momentum=", round(silver_momentum, 6) ) # Both weak: target is cash if gold_momentum <= 0 and silver_momentum <= 0: self.target = None # Hold the stronger metal elif gold_momentum > silver_momentum: self.target = self.gold elif silver_momentum > gold_momentum: self.target = self.silver else: return self.rebalance() def momentum(self, instrument, timestamp): bars = self.evt.getHistoricalBar( contract={"instrument": instrument}, numOfBar=25, interval="D", timestamp=timestamp ) if bars is None or len(bars) < 25: return None close = [bars[t]["c"] for t in bars] if close[-20] == 0: return None return (close[-1] - close[-20]) / close[-20] def rebalance(self): """ Requires Position Netting = False. Close all unwanted trades first. Once each close is completed, on_orderfeed() calls rebalance() again until the portfolio matches self.target. """ pos, orders, pending = self.evt.getSystemOrders() # Close positions when target is cash, # or close positions in the non-target metal. for tradeID in orders: instrument = orders[tradeID]["instrument"] if self.target is None or instrument != self.target: self.close_order(tradeID) return # No target means all positions have been closed. if self.target is None: self.evt.consoleLog("Portfolio is in cash.") return # Do nothing if already holding the target instrument. for tradeID in orders: if orders[tradeID]["instrument"] == self.target: self.evt.consoleLog("Already holding:", self.target) return # Portfolio is flat: open the selected metal. self.open_order(self.target) def open_order(self, instrument): order = AlgoAPIUtil.OrderObject( instrument=instrument, orderRef="momentum", openclose="open", buysell=1, ordertype=0, volume=self.volume ) self.evt.consoleLog("Opening long position:", instrument) self.evt.sendOrder(order) def close_order(self, tradeID): order = AlgoAPIUtil.OrderObject( tradeID=tradeID, openclose="close" ) self.evt.consoleLog("Closing tradeID:", tradeID) self.evt.sendOrder(order) def on_orderfeed(self, of): self.evt.consoleLog( "Order update:", of.status, of.openclose, of.instrument, of.tradeID ) # After an old trade is closed, continue the rotation. if of.status == "success" and of.openclose == "close": self.rebalance() def on_marketdatafeed(self, md, ab): pass def on_openPositionfeed(self, op, oo, uo): pass def on_dailyPLfeed(self, pl): pass |
Backtest Setup
To test this strategy on ALGOGENE, use these settings:
- Instruments: XAUUSD, XAGUSD
- Data Interval: 1 day (Daily)
- Short Selling: Disabled (False)
- Position Netting: Disabled (False)
- Initial Capital: Set based on your risk management
Key Advantages of This Strategy
- Low Effort: Weekly rebalance only, no daily monitoring
- Long Only: No short exposure, suitable for conservative precious metals traders
- Relative Strength: Avoids single-asset drawdowns by rotating to the stronger metal
- Transparent: Simple momentum calculation, easy to modify and optimize
How to Use & Improve
- Adjust the momentum lookback period (try 14, 20, 25 days)
- Modify the position size based on your risk tolerance
- Add a volatility filter (e.g., ATR) to skip high-volatility weeks
- Extend to more precious metals (e.g., Platinum XPTUSD)
Wrap Up
This weekly momentum rotation strategy provides a robust, rule-based way to trade Gold and Silver without emotional decisions or short-selling. It is beginner-friendly, algorithmically clean, and optimized for ALGOGENE’s backtesting and live trading environment.
Try backtesting it with your preferred historical period, tweak the parameters, and share your results in the comments below.
Happy backtesting & profitable trading!
