Quant Buffet放轻松,别过度思虑

盘中全市场上涨/下跌与收益

登录后收藏

学术论文

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

策略概要

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

II. 策略合理性

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

回测表现

波动率31.56%
夏普比率0.63
索提诺比率-0.176
胜率50%

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