
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.
ASSET CLASS: CFDs, ETFs, futures | REGION: United States | FREQUENCY:
Intraday | MARKET: equities | KEYWORD: Intraday
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 Return | 23.75% |
| Volatility | 31.56% |
| Beta | 0.009 |
| Sharpe Ratio | 0.63 |
| Sortino Ratio | -0.176 |
| Maximum Drawdown | N/A |
| Win Rate | 50% |
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