|
| 1 | +### AI generated from Faber, Meb, Relative Strength Strategies for Investing (April 1, 2010). |
| 2 | +### Available at SSRN: https://ssrn.com/abstract=1585517 or http://dx.doi.org/10.2139/ssrn.1585517 |
| 3 | + |
| 4 | +from AlgorithmImports import * |
| 5 | + |
| 6 | +class MyTradingAlgorithm(QCAlgorithm): |
| 7 | + def Initialize(self): |
| 8 | + # Set the start and end date for backtesting |
| 9 | + self.SetStartDate(2024, 1, 1) |
| 10 | + self.SetEndDate(2024, 12, 1) |
| 11 | + |
| 12 | + # Set the initial cash balance |
| 13 | + self.SetCash(100000) |
| 14 | + |
| 15 | + # Add the assets to trade |
| 16 | + self.symbols = [self.AddEquity("SPY", Resolution.Daily).Symbol, |
| 17 | + self.AddEquity("SLV", Resolution.Daily).Symbol, |
| 18 | + self.AddEquity("GLD", Resolution.Daily).Symbol] |
| 19 | + |
| 20 | + # Set the benchmark |
| 21 | + self.SetBenchmark(self.symbols[0]) |
| 22 | + |
| 23 | + # Initialize risk management parameters |
| 24 | + self.stopLossPercent = 0.05 |
| 25 | + self.trailingStopPercent = 0.02 |
| 26 | + |
| 27 | + # Initialize portfolio construction parameters |
| 28 | + self.rebalancePeriod = timedelta(30) |
| 29 | + self.nextRebalanceTime = self.Time + self.rebalancePeriod |
| 30 | + |
| 31 | + # Initialize alpha model parameters |
| 32 | + self.momentumPeriod = 20 |
| 33 | + self.momentumThreshold = 0.01 |
| 34 | + |
| 35 | + # Schedule the rebalance function |
| 36 | + self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("SPY", 30), self.Rebalance) |
| 37 | + |
| 38 | + # Initialize a dictionary to store stop prices |
| 39 | + self.stopPrices = {} |
| 40 | + |
| 41 | + def OnData(self, data): |
| 42 | + # Check if it's time to rebalance the portfolio |
| 43 | + if self.Time >= self.nextRebalanceTime: |
| 44 | + self.Rebalance() |
| 45 | + self.nextRebalanceTime = self.Time + self.rebalancePeriod |
| 46 | + |
| 47 | + # Implement trailing stop loss |
| 48 | + for symbol in self.Portfolio.Keys: |
| 49 | + if symbol in self.stopPrices and symbol in data and data[symbol]: |
| 50 | + if data[symbol].Close < self.stopPrices[symbol]: |
| 51 | + self.Liquidate(symbol) |
| 52 | + else: |
| 53 | + self.stopPrices[symbol] = max(self.stopPrices[symbol], data[symbol].Close * (1 - self.trailingStopPercent)) |
| 54 | + |
| 55 | + def Rebalance(self): |
| 56 | + # Calculate momentum for each symbol |
| 57 | + momentumScores = {} |
| 58 | + for symbol in self.symbols: |
| 59 | + history = self.History(symbol, self.momentumPeriod, Resolution.Daily) |
| 60 | + if not history.empty: |
| 61 | + momentum = (history['close'][-1] - history['close'][0]) / history['close'][0] |
| 62 | + momentumScores[symbol] = momentum |
| 63 | + |
| 64 | + # Rank symbols by momentum |
| 65 | + rankedSymbols = sorted(momentumScores, key=momentumScores.get, reverse=True) |
| 66 | + |
| 67 | + # Determine the number of positions to hold |
| 68 | + numPositions = min(3, len(rankedSymbols)) |
| 69 | + |
| 70 | + # Calculate the target weight for each position |
| 71 | + targetWeight = 1.0 / numPositions if numPositions > 0 else 0 |
| 72 | + |
| 73 | + # Liquidate positions not in the top ranked symbols |
| 74 | + for symbol in self.Portfolio.Keys: |
| 75 | + if symbol not in rankedSymbols[:numPositions]: |
| 76 | + self.Liquidate(symbol) |
| 77 | + |
| 78 | + # Set target weights for the top ranked symbols |
| 79 | + for symbol in rankedSymbols[:numPositions]: |
| 80 | + self.SetHoldings(symbol, targetWeight) |
| 81 | + self.stopPrices[symbol] = self.Securities[symbol].Price * (1 - self.stopLossPercent) |
| 82 | + |
| 83 | + def OnOrderEvent(self, orderEvent): |
| 84 | + # Log order events for debugging |
| 85 | + self.Debug(f"Order event: {orderEvent}") |
| 86 | + |
| 87 | + def OnEndOfAlgorithm(self): |
| 88 | + # Log final portfolio value |
| 89 | + self.Debug(f"Final Portfolio Value: {self.Portfolio.TotalPortfolioValue}") |
| 90 | + |
| 91 | +# Helper functions can be added here if needed |
0 commit comments