The investment universe consists of stocks listed on NYSE, AMEX, or NASDAQ. The intraday Up/Down signal (UDS) measures the difference between stocks moving up or down by 9:45 am.

I. STRATEGY IN A NUTSHELL

The strategy monitors intraday market momentum for US stocks (share codes 10 or 11 on NYSE, AMEX, or NASDAQ) using the Up/Down Signal (UDS), calculated as the net number of stocks moving up minus down by 9:45 am, normalized by the total sample. This signal guides intraday trading based on short-term market sentiment.

II. ECONOMIC RATIONALE

Returns are driven by retail investor activity and intraday sentiment, not systemic risk. Individual investors’ reactions to recent market performance create predictable intraday price movements. These sentiment-based signals consistently forecast intraday returns in both mature and emerging markets, highlighting the economic relevance of short-term behavioral patterns.

III. SOURCE PAPER

 Intraday Market-Wide Ups/Downs and Returns [Click to Open PDF]

Wei Zhang, Tianjin University – College of Management and Economics; Shen Lin, PBC School of Finance (PBCSF), Tsinghua University; Yongjie Zhang, Tianjin University – College of Management and Economics

<Abstract>

Using 16-years Chinese and 3-years U.S. stock market data, this study explores the explanatory
power of early intraday market-wide up and down movements to the subsequent intraday returns
within the same trading days. Compared to the closing of the previous trading day, we introduce
two intraday market-wide up/down indicators in terms of the index return and the proportional
difference in the numbers of stocks moving upwards to downwards for each minute. Time series
analysis shows significantly positive, both economically and statistically, relation between the
intraday indicators and the subsequent intraday returns of the market indices. Intraday trading
strategies that exploit this intraday relationship lead to monthly returns of 4.1% in the Chinese
market and 2.8% in the U.S. market. In addition, the strategies are more profitable for the markets
with high activeness of individual investors (i.e., high trading value, low trading volume per
transaction, small-cap, high B/M ratio, low institutional ownership, low price, and high number of
shareholders). The results indicate that simple intraday market-wide up/down movements in the
earlier trading affect the sentiment of retail investors, resulting the market to move in the same
direction within the trading days.

IV. BACKTEST PERFORMANCE

Annualised Return23.75%
Volatility31.56%
Beta0.009
Sharpe Ratio 0.63
Sortino Ratio-0.176
Maximum DrawdownN/A
Win Rate50%

V. FULL PYTHON CODE

from AlgorithmImports import *
from typing import List, Dict
from dataclasses import dataclass
# endregion
class IntradayMarketWideUpsDowns(QCAlgorithm):
    def Initialize(self) -> None:
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        
        self.exchange_codes: List[str] = ['NYS', 'NAS', 'ASE']	
        data: Equity = self.AddEquity('SPY', Resolution.Minute)
        data.SetFeeModel(CustomFeeModel())
        self.symbol: Symbol = data.Symbol
        
        self.data: Dict[Symbol, SymbolData] = {} # Storing SymbolData for each stocks
        self.selected_stocks: List[Symbol] = []
        
        self.fundamental_count: int = 500
        self.fundamental_sorting_key = lambda x: x.DollarVolume
        
        self.selection_flag: bool = False
        self.settings.daily_precise_end_time = False
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.UniverseSettings.Resolution = Resolution.Minute
        self.AddUniverse(self.FundamentalSelectionFunction)
        self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
            
    def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
        # Rebalance monthly
        if not self.selection_flag:
            return Universe.Unchanged
        self.selection_flag = False
        
        # Filter universe
        selected: List[Fundamental] = [
            x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and x.SecurityReference.ExchangeId in self.exchange_codes
        ]
        if len(selected) > self.fundamental_count:
            selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]    
            
        # Add filtered stocks in self.data dictionary
        for stock in selected:
            symbol: Symbol = stock.Symbol
            self.data[symbol] = SymbolData()
        
        # Store selected stocks symbols in self.selected_stocks parameter
        self.selected_stocks = list(map(lambda x:x.Symbol, selected))
        
        return self.selected_stocks
        
    def OnData(self, data: Slice) -> None:
        # Trade stocks at this time
        if self.Time.hour == 9 and self.Time.minute == 45 and len(self.selected_stocks) > 0:
            # Diffrence between upwards and downwards
            difference: Union[None, float] = None
            
            # Make calculation for each stock in self.selected_stocks
            for symbol in self.selected_stocks:
                # Check if symbol is in data slices and stock with this symbol has close price
                if symbol in data and data[symbol] and self.data[symbol].close:
                    price: float = data[symbol].Value
                    
                    # Make sure, that trade is make based on difference
                    if difference == None:
                        difference = 0
                        
                    # If stock's current price is greater than it's close,
                    # then it is upwards stock, otherwise it is downwards stock
                    if price > self.data[symbol].close:
                        difference += 1
                    else:
                        difference -= 1
            
            # Trade only if it calculated difference
            if difference != None:
                # Calculate uds based od difference between upwards and downwards divided by total count of selected stocks            
                uds: float = difference / len(self.selected_stocks)
                trade_direction: int = 1 if uds >= 0 else -1
                
                self.SetHoldings(self.symbol, trade_direction)
            
        # Store close prices and liquidate portfolio
        if self.Time.hour == 15 and self.Time.minute == 59 and len(self.selected_stocks) != 0:
            
            # Update closes of stocks
            for symbol in self.selected_stocks:
                # Check if symbol is in data slices
                if symbol in data and data[symbol]:
                    self.data[symbol].close = data[symbol].Value
                
            # Liquidate portfolio
            self.Liquidate()
        
    def Selection(self) -> None:
        self.selection_flag = True
        
@dataclass
class SymbolData():
    close: Union[None, float] = None
    open: Union[None, float] = None
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
        fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading