“该策略通过负日间反转和市值对股票进行排序,做多AB_NR最高五分位的股票,做空最低五分位的股票,投资组合等权重,每月重新平衡。”

I. 策略概要

投资范围包括在纽约证券交易所、美国证券交易所和纳斯达克上市的普通股,不包括金融和公用事业公司。该策略将每日股票回报分解为隔夜和日间部分。日间回报(RETOC)计算为市场开盘价和收盘价之间的相对价格变化。隔夜回报(RETCO)根据日间回报和标准日回报推算。当正的隔夜回报之后是负的日间回报时,即识别出负日间反转。股票根据负日间反转的频率(AB_NR)和市值(SIZE)进行排序。对大盘股中AB_NR最高的五分位建立多头头寸,对AB_NR最低的五分位建立空头头寸。该策略每月重新平衡。

II. 策略合理性

该论文研究了美国股票中负日间反转现象,即正的隔夜回报之后是负的日间回报,这表明噪音交易者和日间投资者之间存在拉锯战。这种模式反映了噪音交易者在隔夜期间的价格压力,被日间交易的投资者逆转。负日间反转的频率被证明可以预测更高的未来回报,以补偿与噪音交易者相关的风险。这种关系是不对称的,因为高开盘价之后是负日间反转可以预测未来的股票回报,反之则不然。该论文将此归因于噪音交易者在隔夜期间的影响,零售投资者在这些月份的交易中占更大比例。它得出结论,噪音交易者风险解释了回报溢价,因为这些反转的更高频率需要更高的溢价,因为与噪音交易者交易存在风险。由于市场中这种持续存在的模式,该策略仍然有利可图。

III. 来源论文

Overnight Returns, Daytime Reversals, and Future Stock Returns: The Risk of Investing in a Tug of War With Noise Traders [点击查看论文]

<摘要>

一个月内正隔夜回报之后是负交易日反转的频率越高,表明对立投资者群体之间每日拉锯战越激烈,这些群体很可能由隔夜的噪音交易者和日间的套利者组成。我们表明,更激烈的每日拉锯战预示着横截面中未来更高的回报。额外的测试支持以下结论:在更激烈的拉锯战中,日间套利者更有可能低估隔夜出现利好消息的可能性,从而过度纠正持续的隔夜上涨价格压力。

IV. 回测表现

年化回报5.28%
波动率6.43%
β值0.002
夏普比率0.82
索提诺比率-0.447
最大回撤N/A
胜率49%

V. 完整的 Python 代码

import numpy as np
from AlgorithmImports import *
import pandas as pd
from pandas.core.frame import dataframe
class ImpactOfOvernightReturnsDaytimeReversals(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        
        market:Symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.fundamental_count:int = 1000
        self.fundamental_sorting_key = lambda x: x.DollarVolume
        self.period:int = 13 * 21
        self.quantile:int = 10
        self.leverage:int = 5
        self.min_share_price:float = 5.
        self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
        self.long:List[Symbol] = []
        self.short:List[Symbol] = []
        
        self.selection_flag = False
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.FundamentalSelectionFunction)
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.Schedule.On(self.DateRules.MonthEnd(market), self.TimeRules.AfterMarketOpen(market), self.Selection)
        self.settings.daily_precise_end_time = False
    def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
        for security in changes.AddedSecurities:
            security.SetFeeModel(CustomFeeModel())
            security.SetLeverage(self.leverage)
                
    def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
        if not self.selection_flag:
            return Universe.Unchanged
        selected:List[Fundamental] = [
            x for x in fundamental if x.HasFundamentalData and x.Price >= self.min_share_price and \
            x.Market == 'usa' and x.MarketCap != 0
        ]
        if len(selected) > self.fundamental_count:
            selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
        
        AB_NR:Dict[Fundamental, float] = {}
        
        for stock in selected:
            symbol:Symbol = stock.Symbol
            hist:dataframe = self.History([symbol], self.period, Resolution.Daily)
            if 'close' in hist.columns and 'open' in hist.columns:
                closes:pd.Series = hist['close']
                opens:pd.Series = hist['open']
                if len(closes) == self.period and len(opens) == self.period:
                    # Calculate overnight and daily returns                    
                    RET_OC:pd.Series = pd.Series(closes / opens - 1)         # Open to close return
                    RET:pd.Series = pd.Series(closes).pct_change()        # Close to close return
                    RET_CO:pd.Series = ((1 + RET) / (1 + RET_OC)) - 1
                    
                    # Negative daytime reversal signal for last year                    
                    reversal_vector:List = [1 if co > 0 and oc < 0 else 0 for co, oc in zip(RET_CO, RET_OC)]
                    
                    # Slice it for every month
                    reversal_separate_months:List = [reversal_vector[x:x+21] for x in range(0, len(reversal_vector),21)]
                    NRIT:List = [month.count(1) / len(month) for month in reversal_separate_months]
                    NRIT_current_month:float = NRIT[-1]
                    NRTI_avg:float = np.average(NRIT[:-2])
                    
                    # AB_NR calc
                    AB_NR[stock] = NRIT_current_month / NRTI_avg
        
        if len(AB_NR) != 0:
            # Sort by market cap and AB_NR
            market_cap_values:List[float] = [x.MarketCap for x in AB_NR.keys()]
            high_by_market_cap:List[Fundamental] = [x[0] for x in AB_NR.items() if x[0].MarketCap >= np.percentile(market_cap_values, 66)]
    
            abnr_values:List[float] = list(AB_NR.values())
            high_by_abnr:List[Fundamental] = [x[0] for x in AB_NR.items() if x[1] >= np.percentile(abnr_values, 80)]
            low_by_abnr:List[Fundamental] = [x[0] for x in AB_NR.items() if x[1] <= np.percentile(abnr_values, 20)]
    
            self.long = [x.Symbol for x in high_by_market_cap if x in high_by_abnr]
            self.short = [x.Symbol for x in high_by_market_cap if x in low_by_abnr]
        
        return self.long + self.short
    
    def OnData(self, data: Slice) -> None:
        if not self.selection_flag:
            return
        self.selection_flag = False
        # order execution
        targets:List[PortfolioTarget] = []
        for i, portfolio in enumerate([self.long, self.short]):
            for symbol in portfolio:
                if symbol in data and data[symbol]:
                    targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
		
        self.SetHoldings(targets, True)
        self.long.clear()
        self.short.clear()
            
    def Selection(self) -> None:
        self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读