Overall Statistics
Total Trades
398
Average Win
5.56%
Average Loss
-3.45%
Compounding Annual Return
36.775%
Drawdown
28.300%
Expectancy
0.509
Net Profit
2083.645%
Sharpe Ratio
1.132
Probabilistic Sharpe Ratio
60.007%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.61
Alpha
0.228
Beta
0.414
Annual Standard Deviation
0.227
Annual Variance
0.052
Information Ratio
0.794
Tracking Error
0.235
Treynor Ratio
0.622
Total Fees
$13766.77
Estimated Strategy Capacity
$71000000.00
Lowest Capacity Asset
UUP TQBX2PUC67OL
Portfolio Turnover
10.45%
# region imports
from AlgorithmImports import *

class ROCMomentumAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2014,1,1)  # Set Start Date
        self.SetCash(100000)  # Set Strategy Cash
        self.lastSellTime = datetime.min

        self.AddRiskManagement(TrailingStopRiskManagementModel(0.05))
        self.Settings.FreePortfolioValuePercentage = 0.05
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
        
        # Dictionary to store buy prices
        self.buy_prices = {}

        # Dictionary to store purchase dates
        self.purchase_dates = {}

        # Add Equity individually
        self.SPY = self.AddEquity("SPY", Resolution.Daily).Symbol  # SPY
        self.equity1 = self.AddEquity("AAPL", Resolution.Daily).Symbol  # Apple
        self.equity2 = self.AddEquity("MSFT", Resolution.Daily).Symbol  # Microsoft
        self.equity3 = self.AddEquity("AMZN", Resolution.Daily).Symbol  # Amazon
        self.equity4 = self.AddEquity("NVDA", Resolution.Daily).Symbol  # NVIDIA
        self.equity5 = self.AddEquity("TSLA", Resolution.Daily).Symbol  # Tesla
        self.equity6 = self.AddEquity("GOOGL", Resolution.Daily).Symbol  # Alphabet Class A
        self.equity7 = self.AddEquity("META", Resolution.Daily).Symbol  # Meta
        self.equity8 = self.AddEquity("GOOG", Resolution.Daily).Symbol  # Alphabet Class C
        self.equity9 = self.AddEquity("AVGO", Resolution.Daily).Symbol  # Broadcom
        self.equity10 = self.AddEquity("ORCL", Resolution.Daily).Symbol  # Oracle
        
        self.equities = [self.equity1, self.equity2, self.equity3, self.equity4, self.equity5,
                        self.equity6, self.equity7, self.equity8, self.equity9, self.equity10]

        self.uup = self.AddEquity("UUP", Resolution.Daily).Symbol  # PowerShares DB US Dollar Index Bullish Fund
        self.tlt = self.AddEquity("TLT", Resolution.Daily).Symbol  # iShares 20+ Year Treasury Bond ETF
        self.gld = self.AddEquity("GLD", Resolution.Daily).Symbol  # SPDR Gold Trust ETF
        
        # Define Bollinger Band for each symbol with 13 periods and 2 standard deviation
        self.bbands_equities = {symbol: self.BB(symbol, self.GetParameter("BB"), self.GetParameter("stdBB"), MovingAverageType.Simple, Resolution.Daily) for symbol in self.equities}

        # define our daily roc(37) indicators for each symbol
        self.roc_equities = {symbol: self.ROC(symbol, self.GetParameter("ROC"), Resolution.Daily) for symbol in self.equities}
        self.roc_uup = self.ROC(self.uup, 40, Resolution.Daily)
        self.roc_tlt = self.ROC(self.tlt, 40, Resolution.Daily)
        self.roc_gld = self.ROC(self.gld, 40, Resolution.Daily)

        # define a rolling window for the ROC for each symbol
        self.window_equities = {symbol: RollingWindow[IndicatorDataPoint](50) for symbol in self.equities}
        self.window_uup = RollingWindow[IndicatorDataPoint](40)
        self.window_tlt = RollingWindow[IndicatorDataPoint](40)
        self.window_gld = RollingWindow[IndicatorDataPoint](40)

        # Set warm-up period for 60 bars
        self.SetWarmUp(60)

        self.SetBenchmark(self.SPY)

        # initialize flag for stop loss triggered
        self.stop_loss_triggered = False

        self.previous_closes = {symbol: RollingWindow[float](2) for symbol in self.equities}

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            order = self.Transactions.GetOrderById(orderEvent.OrderId)
            if order.Type == OrderType.StopMarket or order.Direction == OrderDirection.Sell:
                self.stop_loss_triggered = True
                self.lastSellTime = self.Time  # Record the time of the last sell

                # Remove the equity from purchase_dates dictionary if it's sold
                if orderEvent.Symbol in self.purchase_dates:
                    del self.purchase_dates[orderEvent.Symbol]

            # Update buy price for the purchased asset and record its purchase date
            if order.Direction == OrderDirection.Buy:
                self.buy_prices[order.Symbol] = orderEvent.FillPrice
                self.purchase_dates[order.Symbol] = self.Time

    def OnData(self, data):
        # Check if we are still warming up
        if self.IsWarmingUp:
            return

        if self.Time - self.lastSellTime < timedelta(days=1):
            return

        if not (all(roc.IsReady for roc in self.roc_equities.values()) and 
                all(data.ContainsKey(symbol) for symbol in self.equities) and
                self.roc_uup.IsReady and self.roc_tlt.IsReady and self.roc_gld.IsReady and 
                data.ContainsKey(self.uup) and data.ContainsKey(self.tlt) and data.ContainsKey(self.gld)):
            return 

        # Check the Bollinger Bands and ROC conditions and buy logic for equity
        for symbol in self.equities:
            current_price = self.Securities[symbol].Close
            lower_band = self.bbands_equities[symbol].LowerBand.Current.Value
            middle_band = self.bbands_equities[symbol].MiddleBand.Current.Value
            upper_band = self.bbands_equities[symbol].UpperBand.Current.Value
            current_roc = self.roc_equities[symbol].Current.Value

            # Store the current price for the symbol
            self.previous_closes[symbol].Add(current_price)
            if self.previous_closes[symbol].Count < 2:
                continue

            prev_close = self.previous_closes[symbol][1]
            max_roc_symbol = max(self.roc_equities, key=lambda s: self.roc_equities[s].Current.Value)

            tag_message = f"Prev Close: {prev_close}, Current Price: {current_price}, Lower Band: {lower_band}, Middle Band: {middle_band}, Upper Band: {upper_band}, ROC: {current_roc}"
            
            # Now, run the 20-day check
            purchase_date = self.purchase_dates.get(symbol)
            if purchase_date and (self.Time - purchase_date).days >= 10:
                self.Liquidate(symbol, "Held for >= 10 days")
                del self.purchase_dates[symbol]

            # Check if the current equity is the one with max ROC
            if symbol == max_roc_symbol:
                # Now check if the current price of the equity with max ROC is above its middle band
                if current_price > middle_band and current_price < upper_band:
                    if self.Portfolio.Invested:
                        return
                    if self.Portfolio[max_roc_symbol].Invested and not self.stop_loss_triggered:
                        return
                    if not self.Portfolio[max_roc_symbol].Invested:
                        orderTickets = self.Liquidate()
                        for ticket in orderTickets:
                            ticket.UpdateTag(f"Liquidate for new max ROC")
                        quantity = self.CalculateOrderQuantity(max_roc_symbol, 1)
                        orderTicket = self.MarketOrder(max_roc_symbol, quantity)
                        orderTicket.UpdateTag(f"Buy with max ROC, {tag_message}")

                    # Explicit profit-taking mechanism
                    if self.Portfolio[max_roc_symbol].Invested:
                        if current_price >= 1.05 * self.buy_prices.get(max_roc_symbol, 0):
                            tag_message1 = f"Prev Close: {prev_close}, Current Price: {current_price}, Lower Band: {lower_band}, Middle Band: {middle_band}, Upper Band: {upper_band}, ROC: {current_roc}"
                            self.Liquidate(max_roc_symbol, f"Take profit >= 1.05, {tag_message1}")

                    self.stop_loss_triggered = False

            elif all(roc.Current.Value < 0 for roc in self.roc_equities.values()):
                orderTickets = self.Liquidate()
                for ticket in orderTickets:
                    if isinstance(ticket, OrderTicket):
                        ticket.UpdateTag(f"Negative ROC for all equities")

                if not self.Portfolio.Invested:
                    target_symbol, tag_message = None, ""
                    if self.roc_uup.Current.Value > 0 and self.roc_tlt.Current.Value > 0:
                        target_symbol = self.uup if self.roc_uup.Current.Value > self.roc_tlt.Current.Value else self.tlt
                        tag_message = f"Buy UUP, ROC: {self.roc_uup.Current.Value}" if self.roc_uup.Current.Value > self.roc_tlt.Current.Value else f"Buy TLT, ROC: {self.roc_tlt.Current.Value}"
                    elif self.roc_uup.Current.Value < 0 and self.roc_tlt.Current.Value > 0:
                        target_symbol = self.tlt
                        tag_message = f"Buy TLT, ROC: {self.roc_tlt.Current.Value}"
                    elif self.roc_uup.Current.Value > 0 and self.roc_tlt.Current.Value < 0:
                        target_symbol = self.uup
                        tag_message = f"Buy UUP, ROC: {self.roc_uup.Current.Value}"
                    else:
                        target_symbol = self.gld
                        tag_message = "Buy GLD"

                    quantity = self.CalculateOrderQuantity(target_symbol, 0.95)
                    orderTicket = self.MarketOrder(target_symbol, quantity)
                    orderTicket.UpdateTag(tag_message)

                if any(roc.Current.Value > 0 for roc in self.roc_equities.values()):
                    orderTicket = self.MarketOrder(self.uup, -self.Portfolio[self.uup].Quantity)
                    orderTicket.UpdateTag(f"Liquidate UUP, ROC: {self.roc_uup.Current.Value}")
                    orderTicket = self.MarketOrder(self.tlt, -self.Portfolio[self.tlt].Quantity)
                    orderTicket.UpdateTag(f"Liquidate TLT, ROC: {self.roc_tlt.Current.Value}")
                    orderTicket = self.MarketOrder(self.gld, -self.Portfolio[self.gld].Quantity)
                    orderTicket.UpdateTag("Liquidate GLD")