Overall Statistics
Total Orders
1796
Average Win
0.11%
Average Loss
-0.14%
Compounding Annual Return
-1.669%
Drawdown
13.000%
Expectancy
-0.053
Start Equity
100000
End Equity
91926.48
Net Profit
-8.074%
Sharpe Ratio
-0.806
Sortino Ratio
-0.895
Probabilistic Sharpe Ratio
0.080%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
0.78
Alpha
-0.041
Beta
-0.092
Annual Standard Deviation
0.059
Annual Variance
0.003
Information Ratio
-0.704
Tracking Error
0.164
Treynor Ratio
0.516
Total Fees
$1878.53
Estimated Strategy Capacity
$1700000.00
Lowest Capacity Asset
IJK RWQR2INKP0TH
Portfolio Turnover
2.42%
Drawdown Recovery
722
# region imports
from AlgorithmImports import *
# endregion


class MomentumAndStyleRotationAlgorithm(QCAlgorithmFramework):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))  
        self.set_cash(100_000)
        self.settings.automatic_indicator_warm_up = True
        tickers = [ 
            "IJJ", # iShares S&P MidCap 400 Value Index ETF
            "IJS", # iShares S&P SmallCap 600 Value ETF 
            "IVE", # iShares S&P 500 Value Index ETF 
            "IVW", # iShares S&P 500 Growth ETF   
            "IJK", # iShares S&P Mid-Cap 400 Growth ETF
            "IJT", # iShares S&P Small-Cap 600 Growth ETF
        ]
        symbols = [Symbol.create(t, SecurityType.EQUITY, Market.USA) for t in tickers]
        self.universe_settings.resolution = Resolution.DAILY
        self.set_universe_selection(ManualUniverseSelectionModel(symbols))
        self.set_alpha(MomentumAndStyleRotationAlphaModel(self))
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel()) 
        self.set_execution(ImmediateExecutionModel())
        self.set_risk_management(NullRiskManagementModel())
        # Add a warm-up period so the algorithm trades on deployment.
        self.set_warm_up(timedelta(45))
    

class MomentumAndStyleRotationAlphaModel(AlphaModel):
    '''Alpha model that uses the Momentum Indicator to create insights'''

    def __init__(self, algorithm, period=12*20, resolution=Resolution.DAILY):
        '''
        Initializes a new instance of the MomentumAndStyleRotationAlphaModel class
        Args:
            period: The Momentum lookback period
            resolution: The data resolution
        '''
        self._period = period
        self._resolution = resolution
        self._securities = []
        self._insights = []
        # Add a Scheduled Event to create new insights each month.
        algorithm.schedule.on(
            algorithm.date_rules.month_start('SPY'),
            algorithm.time_rules.at(8, 0),
            self._create_insights
        )

    def on_securities_changed(self, algorithm, changes):
        # As assets enter the universe, create momentum indicators for them.
        for security in changes.added_securities:
            security.momentum = algorithm.mom(security, self._period, self._resolution)
            self._securities.append(security)

    def _create_insights(self):
        # Sort the assets by their momentum.
        sorted_by_momentum = sorted(
            [s for s in self._securities if s.momentum.is_ready], 
            key=lambda security: security.momentum
        )
        self._insights = [
            # Buy the stock with the greatest momentum.
            Insight.price(sorted_by_momentum[-1], Expiry.END_OF_MONTH, InsightDirection.UP),
            # Short the stock with the lowest momentum.
            Insight.price(sorted_by_momentum[0], Expiry.END_OF_MONTH, InsightDirection.DOWN),
        ]

    def update(self, algorithm, data):
        # Return the insights for the Portfolio Construction model.
        insights = self._insights.copy()
        self._insights.clear()
        return insights