Overall Statistics
Total Trades
586
Average Win
2.74%
Average Loss
-2.49%
Compounding Annual Return
-0.932%
Drawdown
49.200%
Expectancy
0.018
Net Profit
-18.245%
Sharpe Ratio
-0.021
Probabilistic Sharpe Ratio
0.000%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
1.10
Alpha
-0.002
Beta
-0.001
Annual Standard Deviation
0.105
Annual Variance
0.011
Information Ratio
-0.374
Tracking Error
0.205
Treynor Ratio
1.698
Total Fees
$1170.17
Estimated Strategy Capacity
$900000.00
Lowest Capacity Asset
IWO RWQR2INKP0TH
# https://quantpedia.com/strategies/momentum-factor-and-style-rotation-effect/
# 
# Russell’s ETFs for six equity styles are used
# (small-cap value, mid-cap value, large-cap value, small-cap growth, mid-cap growth, large-cap growth).
# Each month, the investor calculates 12-month momentum for each style and goes long on the winner and short on the loser.
# The portfolio is rebalanced each month.
#
# QC Implementation:
#   - Trading IVW in 10/2020 is skipped due to data error.

class MomentumFactorAndStyleRotationEffect(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.tickers = [
            'IWS', # iShares Russell Midcap Value ETF
            'IWP', # iShares Russell Midcap Growth ETF
            'IWN', # iShares Russell 2000 Value ETF
            'IWO', # iShares Russell 2000 Growth ETF
            'IVE', # iShares S&P 500 Value ETF
            'IVW'  # iShares S&P 500 Growth ETF       
        ]
        
        self.mom = {}
        
        self.period = 12 * 21
        self.SetWarmUp(self.period)
        
        for ticker in self.tickers:
            security = self.AddEquity(ticker, Resolution.Daily)
            security.SetFeeModel(CustomFeeModel(self))
            security.SetLeverage(10)
            
            self.mom[security.Symbol] = self.MOM(security.Symbol, self.period)
        
        self.Schedule.On(self.DateRules.MonthStart(self.tickers[0]), self.TimeRules.AfterMarketOpen(self.tickers[0]), self.Rebalance)
        
    def Rebalance(self):
        mom_ready = [s for s in self.mom if self.mom[s].IsReady]
        if mom_ready:
            sorted_mom = sorted(mom_ready, key = lambda x: self.mom[x].Current.Value, reverse=True)
            
            for symbol in sorted_mom[1:-1]:
                if self.Portfolio[symbol].Invested:
                    self.Liquidate(symbol)
            
            winner = sorted_mom[0]
            loser = sorted_mom[-1]
            
            if self.Securities[winner].Price != 0 and self.Securities[winner].IsTradable:
                if (self.Time.month == 10 and self.Time.year == 2020) and winner.Value == 'IVW':    # prevent data error
                    self.Liquidate(winner)
                else:
                    self.SetHoldings(winner, 1)
            
            if self.Securities[loser].Price != 0 and self.Securities[loser].IsTradable:
                if (self.Time.month == 10 and self.Time.year == 2020) and loser.Value == 'IVW':     # prevent data error
                    self.Liquidate(loser)
                else:
                    self.SetHoldings(loser, -1)
        
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))