投资于跟踪16个国家指数的ETF。根据36个月的回报率,做空表现最好的4只ETF,做多表现最差的4只ETF。每三年重新平衡。

策略概述

该策略包含16只代表不同国家股指的ETF。基于36个月的回报率,对回报率最低的四只ETF进行做多,对回报率最高的四只ETF进行做空。每三年调整一次投资组合,以应对市场表现的变化。该策略旨在利用表现较差和较好国家之间的潜在反转效应,借助长期周期性市场估值变化来获得收益。

策略合理性

该研究探讨了国家股市为什么会在多年内经历反转,指出由于数据样本较短、国家数量有限、市场整合问题以及缺乏普遍接受的资产定价模型,难以确定一个明确的原因。这种复杂性类似于美国市场中价格异常现象的持续争论。重要的是,研究发现没有证据表明这些反转与风险差异相关,否定了先前表现不佳的国家因其波动性或与全球市场回报及其他风险因素的相关性较高而更具风险的观点。然而,反转在较小市场中更加明显,表明可能存在“小国效应”或市场不完善。此外,跨境股权流动对修正错误定价的影响有限,可能是由于对征收或资本管制的担忧。研究还探讨了由于股票估值不确定、回报波动性大以及市场修正所需时间较长,套利行为可能无法完全消除价格差异的可能性。有趣的是,增加的跨境资金流动可能通过吸引更多的动量投资者加剧错误定价,进一步复杂化市场动态。

论文来源

Winner-Loser Reversals in National Stock Market Indices: Can They be Explained? [点击浏览原文]

<摘要>

本文探讨了16个国家股市指数中的“赢家-输家反转”现象的可能解释。没有证据表明输家国家比赢家国家在标准差、与世界市场的协方差或其他风险因素,或在世界不利经济状态中的表现方面更具风险。虽然证据表明小市场比大市场反转幅度更大,可能是由于某种市场不完善,但反转现象并不仅限于小市场。因此,国家市场指数中的赢家-输家反转这一明显异常现象仍未得到解决。

回测表现

年化收益率6.4%
波动率N/A
Beta-0.07
夏普比率-0.28
索提诺比率-0.3
最大回撤80.3%
胜率41%

完整python代码

from AlgoLib import *

class InternationalEquityReversals(XXX):
    '''
    This class implements an international equity reversal strategy using the XXX framework.
    It focuses on identifying and trading based on momentum reversals in international ETFs.
    '''

    def Initialize(self):
        '''
        Initializes the algorithm settings, including start date, initial cash, 
        the list of ETF symbols to trade, momentum score dictionary, analysis period, 
        and rebalance interval.
        '''
        
        self.SetStartDate(2000, 1, 1)  # Set the start date of the algorithm
        self.SetCash(100000)  # Set the initial cash balance

        self.momentum_scores = {}  # Initialize a dictionary to store momentum scores

        self.analysis_period = 36 * 21  # Define the analysis period in trading days (approx. 36 months)
        self.SetWarmUp(self.analysis_period, Resolution.Daily)  # Warm-up period for indicators

        # Define a list of ETF symbols representing various international markets
        self.etf_symbols = [
            "EWA",  # Australia
            "EWO",  # Austria
            "EWK",  # Belgium
            "EWZ",  # Brazil
            "EWC",  # Canada
            "FXI",  # China
            "EWQ",  # France
            "EWG",  # Germany
            "EWH",  # Hong Kong
            "EWI",  # Italy
            "EWJ",  # Japan
            "EWM",  # Malaysia
            "EWW",  # Mexico
            "EWN",  # Netherlands
            "EWS",  # Singapore
            "EZA",  # South Africa
            "EWY",  # South Korea
            "EWP",  # Spain
            "EWD",  # Sweden
            "EWL",  # Switzerland
            "EWT",  # Taiwan
            "THD",  # Thailand
            "EWU",  # UK
            "SPY",  # USA
        ]

        # Add each ETF symbol to the algorithm, set fee model and leverage, and initialize momentum scores
        for symbol in self.etf_symbols:
            equity = self.AddEquity(symbol, Resolution.Daily)
            equity.SetFeeModel(EnhancedFeeModel())  # Set a custom fee model
            equity.SetLeverage(15)  # Set leverage for each equity

            # Calculate the Rate of Change (ROC) indicator for each symbol for the analysis period
            self.momentum_scores[symbol] = self.ROC(symbol, self.analysis_period, Resolution.Daily)
        
        self.rebalance_interval = 36  # Set rebalance interval in months
        self.last_rebalance_month = -1  # Initialize the last rebalance month

    def OnData(self, data: Slice):
        '''
        This method is called whenever new data is received. It checks if the warm-up period is complete, 
        then rebalances the portfolio based on momentum scores if the rebalance interval has been reached.
        '''
        
        if self.IsWarmingUp:
            return  # Do nothing if the algorithm is still warming up

        current_month = self.Time.month  # Get the current month

        # Check if the current month is the same as the last rebalance month to avoid multiple rebalances within the same month
        if current_month == self.last_rebalance_month:
            return

        self.last_rebalance_month = current_month  # Update the last rebalance month
        self.rebalance_interval += 1  # Increment the rebalance interval counter

        # Check if the rebalance interval is less than or equal to 36 months
        if self.rebalance_interval <= 36:
            return
        self.rebalance_interval = 1  # Reset the rebalance interval

        # Sort ETFs by momentum scores and select the top and bottom 4 for long and short positions
        momentum_sorted = sorted([x for x in self.momentum_scores.items() if x[1].IsReady and x[0] in data], key=lambda x: x[1].Current.Value, reverse=True)
        to_long = [x[0] for x in momentum_sorted[-4:]]  # Symbols to take long positions in
        to_short = [x[0] for x in momentum_sorted[:4]]  # Symbols to take short positions in

        # Adjust portfolio by liquidating positions not in the to_long or to_short lists and setting holdings for new positions
        for symbol in self.Portfolio:
            if symbol.Value not in to_long + to_short:
                self.Liquidate(symbol.Value)  # Liquidate positions not being held

        for symbol in to_long:
            self.SetHoldings(symbol, 1 / len(to_long))  # Set long positions equally weighted
        for symbol in to_short:
            self.SetHoldings(symbol, -1 / len(to_short))  # Set short positions equally weighted

class EnhancedFeeModel(FeeModel):
    '''
    This class defines a custom fee model for the algorithm, calculating fees based on a specified rate.
    '''
    
    def GetOrderFee(self, parameters: OrderFeeParameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005  # Custom fee rate
        return OrderFee(CashAmount(fee, "USD"))
        

Leave a Reply

Discover more from Quant Buffet

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

Continue reading