“投资范围包括在纽约证券交易所、美国证券交易所或纳斯达克上市的股票。盘中涨跌信号(UDS)衡量的是上午9:45前上涨或下跌股票之间的差异。”

I. 策略概要

投资范围包括CRSP中股票代码为10或11,并在纽约证券交易所、美国证券交易所或纳斯达克上市的股票。对于这些股票,盘中涨跌信号(UDS)通过衡量在t日早上9:45之前上涨或下跌的股票数量来计算。UDS的计算方法是:自前一天收盘以来上涨股票数量与下跌股票数量之差,除以样本中股票总数。该信号用于衡量当天整体市场动量。

II. 策略合理性

该研究表明,盘中策略的回报不能用系统性风险或贝塔来解释,而是由个体投资者活动和盘中情绪驱动的。散户投资者受近期市场表现和短期情绪影响,在整个交易日内推动市场向同一方向发展。这两个盘中信号反映了这些情绪来源,并对回报产生显著的积极影响。研究结果表明,这种模式不仅存在于中国等新兴市场,也存在于美国等成熟市场,尽管交易限制和投资者行为存在差异。结果表明,盘中情绪信号可以预测这两个市场随后的盘中回报。

III. 来源论文

Intraday Market-Wide Ups/Downs and Returns [点击查看论文]

<摘要>

本研究利用16年的中国股市数据和3年的美国股市数据,探讨了盘中早期全市场涨跌幅对同一交易日后续盘中回报的解释力。与前一交易日收盘价相比,我们引入了两个盘中全市场涨跌指标,即指数回报和每分钟上涨与下跌股票数量的比例差异。时间序列分析表明,盘中指标与市场指数的后续盘中回报之间存在显著的(无论是经济上还是统计上)正相关关系。利用这种盘中关系的盘中交易策略在中国市场产生了4.1%的月回报,在美国市场产生了2.8%的月回报。此外,对于散户投资者活跃度高(即交易价值高、每笔交易量低、小盘股、高市净率、低机构持股比例、低股价和股东数量多)的市场,这些策略的盈利能力更强。结果表明,早期交易中简单的盘中全市场涨跌幅会影响散户投资者的情绪,导致市场在交易日内朝同一方向波动。

IV. 回测表现

年化回报23.75%
波动率31.56%
β值0.009
夏普比率0.63
索提诺比率-0.176
最大回撤N/A
胜率50%

V. 完整的 Python 代码

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"))

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读