Overall Statistics
Total Orders
8418
Average Win
0.07%
Average Loss
-0.07%
Compounding Annual Return
-3.685%
Drawdown
22.400%
Expectancy
-0.076
Start Equity
50000
End Equity
40482.67
Net Profit
-19.035%
Sharpe Ratio
-1.045
Sortino Ratio
-1.217
Probabilistic Sharpe Ratio
0.001%
Loss Rate
54%
Win Rate
46%
Profit-Loss Ratio
1.02
Alpha
-0.042
Beta
0.018
Annual Standard Deviation
0.039
Annual Variance
0.002
Information Ratio
-0.738
Tracking Error
0.155
Treynor Ratio
-2.251
Total Fees
$8958.69
Estimated Strategy Capacity
$2900000.00
Lowest Capacity Asset
IYR RVLEALAHHC2T
Portfolio Turnover
136.87%
#region imports
from AlgorithmImports import *
#endregion


class IntradayMomentumAlphaModel(AlphaModel):
    """
    This class emits insights to take positions for the last `return_bar_count` minutes 
    of the day in the direction of the return for the first `return_bar_count` minutes of the day.
    """
    _intraday_momentum_by_symbol = {}
    sign = lambda _, x: int(x and (1, -1)[x < 0])
    
    def __init__(self, algorithm, return_bar_count = 30):
        """
        Input:
         - return_bar_count
            Number of minutes to calculate the morning return over and the number of minutes
            to hold before the close (0 < return_bar_count < 195)
        """
        if return_bar_count <= 0 or return_bar_count >= 195:
            algorithm.quit(f"Requirement violated:  0 < return_bar_count < 195")
        self._return_bar_count = return_bar_count
    
    def update(self, algorithm, slice):
        """
        Called each time our alpha model receives a new data slice.
        
        Input:
         - algorithm
            Algorithm instance running the backtest
         - data
            A data structure for all of an algorithm's data at a single time step
        
        Returns a list of Insights to the portfolio construction model
        """
        insights = []
        
        for symbol, intraday_momentum in self._intraday_momentum_by_symbol.items():
            if slice.contains_key(symbol) and slice[symbol] is not None:
                intraday_momentum.bars_seen_today += 1
        
                # End of the morning return
                if intraday_momentum.bars_seen_today == self._return_bar_count:
                    intraday_momentum.morning_return = (slice[symbol].close - intraday_momentum.yesterdays_close) / intraday_momentum.yesterdays_close
                
                ## Beginning of the close
                next_close_time = intraday_momentum.exchange.hours.get_next_market_close(slice.time, False)
                mins_to_close = int((next_close_time - slice.time).total_seconds() / 60)
                
                if mins_to_close == self._return_bar_count + 1:
                    insight = Insight.price(intraday_momentum.symbol, 
                                            next_close_time, 
                                            self.sign(intraday_momentum.morning_return))
                    insights.append(insight)
                    continue
                
                # End of the day
                if not intraday_momentum.exchange.date_time_is_open(slice.time):
                    intraday_momentum.yesterdays_close = slice[symbol].close
                    intraday_momentum.bars_seen_today = 0
        
        return insights
        
    def on_securities_changed(self, algorithm, changes):
        """
        Called each time our universe has changed.
        
        Input:
         - algorithm
            Algorithm instance running the backtest
         - changes
            The additions and subtractions to the algorithm's security subscriptions
        """
        for security in changes.added_securities:
            self._intraday_momentum_by_symbol[security.symbol] = IntradayMomentum(security, algorithm)
            
        for security in changes.removed_securities:
            self._intraday_momentum_by_symbol.pop(security.symbol, None)
            

class IntradayMomentum:
    """
    This class manages the data used for calculating the morning return of a security.
    """
    def __init__(self, security, algorithm):
        """
        Input:
         - security
            The security to trade
         - algorithm
            Algorithm instance running the backtest
        """
        self.symbol = security.symbol
        self.exchange = security.exchange
        
        self.bars_seen_today = 0
        self.yesterdays_close = algorithm.history(self.symbol, 1, Resolution.DAILY).loc[self.symbol].close[0]
        self.morning_return = 0
        
#region imports
from AlgorithmImports import *
#endregion


class CloseOnCloseExecutionModel(ExecutionModel):
    """
    Provides an implementation of IExecutionModel that immediately submits a market order to achieve 
    the desired portfolio targets and an associated market on close order.
    """

    def __init__(self):
        self._targets_collection = PortfolioTargetCollection()
        self._invested_symbols = []

    def execute(self, algorithm, targets):
        """
        Immediately submits orders for the specified portfolio targets.
        Input:
         - algorithm
            Algorithm instance running the backtest
         - targets
            The portfolio targets to be ordered
        """
        # for performance we check count value, OrderByMarginImpact and ClearFulfilled are expensive to call
        self._targets_collection.add_range(targets)
        if self._targets_collection.count > 0:
            for target in self._targets_collection.order_by_margin_impact(algorithm):
                # calculate remaining quantity to be ordered
                quantity = OrderSizing.get_unordered_quantity(algorithm, target)
                if quantity == 0:
                    continue
                
                algorithm.market_order(target.symbol, quantity)
                algorithm.market_on_close_order(target.symbol, -quantity)
                
            self._targets_collection.clear_fulfilled(algorithm)
        
#region imports
from AlgorithmImports import *

from alpha import IntradayMomentumAlphaModel
from execution import CloseOnCloseExecutionModel
#endregion


class IntradayETFMomentumAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2015, 1, 1)
        self.set_end_date(2020, 8, 16)
        self.set_cash(50000)
        
        tickers = ['SPY',  # S&P 500
                   'IWM',  # Russell 2000
                   'IYR'   # Real Estate ETF
        ]
        symbols = [ Symbol.create(ticker, SecurityType.EQUITY, Market.USA) for ticker in tickers ]
        self.set_universe_selection(ManualUniverseSelectionModel(symbols))
        self.universe_settings.resolution = Resolution.MINUTE
        
        self.set_alpha(IntradayMomentumAlphaModel(self))
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel(lambda _: None))
        self.set_execution(CloseOnCloseExecutionModel())