“该策略分析55种期货,使用12个月的回报概率和0.4的阈值来发出头寸信号,按波动率缩放回报,并每月重新平衡以优化绩效。”

I. 策略概要

投资策略涉及55种流动性交易所交易的期货。分析12个月的回溯期内的月度回报。如果正回报的概率达到或超过0.4的阈值,则触发多头头寸的“买入”信号;否则,进入空头头寸。使用年化事前波动率缩放回报,年波动率的临界值为40%。投资组合每月重新平衡,以调整变化。这种系统性方法使用基于概率的信号和波动率管理来优化投资组合的回报。

II. 策略合理性

RSM(回报符号动量)的合理性源于短期反应不足和延迟过度反应。回报符号可预测性基于以下理论原则:当回报的条件均值非零时,符号依赖性存在。由于大多数金融资产在长期内表现出正回报,因此检测符号依赖性是可行的。这强调了回报行为和条件均值之间的关系,支持了回报符号中的可预测模式与更广泛的市场趋势相关联的观点。

III. 来源论文

 Returns Signal Momentum [点击查看论文]

<摘要>

引入了一种基于过去回报符号的新型动量。这种动量主要由符号依赖性驱动,符号依赖性与平均回报呈正相关,与回报波动率呈负相关。使用商品和金融期货的投资范围进行的实证应用为这种动量的存在提供了支持证据。基于回报信号动量的投资策略相对于时间序列动量和其他基准策略,产生了更高的回报和夏普比率,以及更低的下跌幅度。总体而言,回报信号动量可以作为投机和对冲的有效策略,使投资者受益。

IV. 回测表现

年化回报11.9%
波动率12.3%
β值-0.084
夏普比率0.97
索提诺比率-0.215
最大回撤-19.5%
胜率52%

V. 完整的 Python 代码

import numpy as np
from AlgorithmImports import *
class ReturnsSignalMomentum(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2005, 1, 1)
        self.SetCash(100000)
        self.symbols = ["CME_S1",   # Soybean Futures, Continuous Contract
                        "CME_W1",   # Wheat Futures, Continuous Contract
                        "CME_SM1",  # Soybean Meal Futures, Continuous Contract
                        "CME_BO1",  # Soybean Oil Futures, Continuous Contract
                        "CME_C1",   # Corn Futures, Continuous Contract
                        "CME_O1",   # Oats Futures, Continuous Contract
                        "CME_LC1",  # Live Cattle Futures, Continuous Contract
                        "CME_FC1",  # Feeder Cattle Futures, Continuous Contract
                        "CME_LN1",  # Lean Hog Futures, Continuous Contract
                        "CME_GC1",  # Gold Futures, Continuous Contract
                        "CME_SI1",  # Silver Futures, Continuous Contract
                        "CME_PL1",  # Platinum Futures, Continuous Contract
                        "CME_CL1",  # Crude Oil Futures, Continuous Contract
                        "CME_HG1",  # Copper Futures, Continuous Contract
                        "CME_LB1",  # Random Length Lumber Futures, Continuous Contract
                        # "CME_NG1",  # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
                        "CME_PA1",  # Palladium Futures, Continuous Contract 
                        "CME_RR1",  # Rough Rice Futures, Continuous Contract
                        "ICE_RS1",  # Canola Futures, Continuous Contract
                        "ICE_GO1",  # Gas Oil Futures, Continuous Contract
                        "CME_RB2",  # Gasoline Futures, Continuous Contract
                        "CME_KW2",  # Wheat Kansas, Continuous Contract
                        "ICE_WT1",  # WTI Crude Futures, Continuous Contract
                        
                        "ICE_CC1",  # Cocoa Futures, Continuous Contract 
                        "ICE_CT1",  # Cotton No. 2 Futures, Continuous Contract
                        "ICE_KC1",  # Coffee C Futures, Continuous Contract
                        "ICE_O1",   # Heating Oil Futures, Continuous Contract
                        "ICE_OJ1",  # Orange Juice Futures, Continuous Contract
                        "ICE_SB1",   # Sugar No. 11 Futures, Continuous Contract
                        
                        "CME_AD1", # Australian Dollar Futures, Continuous Contract #1
                        "CME_BP1", # British Pound Futures, Continuous Contract #1
                        "CME_CD1", # Canadian Dollar Futures, Continuous Contract #1
                        "CME_EC1", # Euro FX Futures, Continuous Contract #1
                        "CME_JY1", # Japanese Yen Futures, Continuous Contract #1
                        "CME_MP1", # Mexican Peso Futures, Continuous Contract #1
                        #"CME_NE1",# New Zealand Dollar Futures, Continuous Contract #1    # Short history ~2007
                        "CME_SF1", # Swiss Franc Futures, Continuous Contract #1
                    
                        "ICE_DX1",      # US Dollar Index Futures, Continuous Contract #1
                        "CME_NQ1",      # E-mini NASDAQ 100 Futures, Continuous Contract #1
                        "EUREX_FDAX1",  # DAX Futures, Continuous Contract #1
                        "CME_ES1",      # E-mini S&P 500 Futures, Continuous Contract #1
                        "EUREX_FSMI1",  # SMI Futures, Continuous Contract #1
                        "EUREX_FSTX1",  # STOXX Europe 50 Index Futures, Continuous Contract #1
                        "LIFFE_FCE1",   # CAC40 Index Futures, Continuous Contract #1
                        "LIFFE_Z1",     # FTSE 100 Index Futures, Continuous Contract #1
                        "SGX_NK1",      # SGX Nikkei 225 Index Futures, Continuous Contract #1
                    
                        "CME_TY1",      # 10 Yr Note Futures, Continuous Contract #1
                        "CME_FV1",      # 5 Yr Note Futures, Continuous Contract #1
                        "CME_TU1",      # 2 Yr Note Futures, Continuous Contract #1
                        #"ASX_XT1",     # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1   # 'Settlement price' instead of 'settle' on quandl. 
                        #"ASX_YT1",     # 3 Year Commonwealth Treasury Bond Futures, Continuous Contract #1    # 'Settlement price' instead of 'settle' on quandl.
                        "EUREX_FGBL1",  # Euro-Bund (10Y) Futures, Continuous Contract #1
                        #"EUREX_FBTP1", # Long-Term Euro-BTP Futures, Continuous Contract #1   # Short history
                        "EUREX_FGBM1",  # Euro-Bobl Futures, Continuous Contract #1
                        "EUREX_FGBS1",  # Euro-Schatz Futures, Continuous Contract #1 
                        "SGX_JB1",      # SGX 10-Year Mini Japanese Government Bond Futures
                        "LIFFE_R1"      # Long Gilt Futures, Continuous Contract #1
                        #"MX_CGB1",     # Ten-Year Government of Canada Bond Futures, Continuous Contract #1    # 'Settlement price' instead of 'settle' on quandl.
                        ]
        
        self.data = {}
        self.return_history = {}
        
        # lookup_period = 60
        lookup_period = 21
        self.return_months_count = 12
        
        self.SetWarmUp(lookup_period)
        
        for symbol in self.symbols:
            data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(CustomFeeModel())
            data.SetLeverage(5)
            
            self.data[symbol] = RollingWindow[float](lookup_period)
            self.return_history[symbol] = RollingWindow[float](self.return_months_count)
            
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.At(0, 0), self.Rebalance)
    
    def OnData(self, data):
        for symbol in self.symbols:
            if symbol in data and data[symbol]:
                price = data[symbol].Value
                if price != 0:
                    self.data[symbol].Add(price)
    def Rebalance(self):
        if self.IsWarmingUp: return
        volatility = {}
        for symbol in self.symbols:
            if self.data[symbol].IsReady:
                if self.Securities[symbol].GetLastData() and self.Time.date() < QuantpediaFutures.get_last_update_date()[symbol]:
                    prices = [x for x in self.data[symbol]]
                    volatility[symbol] = self.Volatility(prices)
            
                    prices = prices[:21] # Last month of daily prices
                    self.return_history[symbol].Add(self.Return(prices))
        
        if len(volatility) == 0:
            self.Liquidate()    
            return
        long = []
        short = []
        threshold = int(0.4*self.return_months_count)
        
        # Create long and short portfolio
        for symbol, roll_window in self.return_history.items():
            # Check if monthly returns are ready
            if not roll_window.IsReady:
                continue
            if symbol not in volatility:
                continue
            
            # Select only positive returns
            temp = [x for x in roll_window if x > 0]
            
            if len(temp) >= threshold:
                long.append(symbol)
            else:
                short.append(symbol)
        
        if len(long + short) == 0:
            self.Liquidate()
            return
    
        # Volatility weighting
        total_vol_long = sum([1 / volatility[x] for x in long if volatility[x] != 0])
        total_vol_short = sum([1 / volatility[x] for x in short if volatility[x] != 0])
        
        weight = {}
        
        if total_vol_long != 0:
            # Calculate long stocks weights
            for symbol in long:
                vol = volatility[symbol]
                if vol != 0:
                    weight[symbol] = (1 / vol) / total_vol_long
    
        if total_vol_short != 0:
            # Calculate short stocks weights
            for symbol in short:
                vol = volatility[symbol]
                if vol != 0:
                    weight[symbol] = -(1 / vol) / total_vol_short
        # Trade execution
        invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in weight:
                self.Liquidate(symbol)
        
        for symbol, w in weight.items():
            self.SetHoldings(symbol, w)
        
    def Return(self, history):
        return (history[0] - history[-1]) / history[-1]
        
    def Volatility(self, history):
        values = np.array(history)
        returns = (values[:-1] - values[1:]) / values[1:]
        return np.std(returns)  
        
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))
# Quantpedia data
class QuantpediaFutures(PythonData):
    _last_update_date:Dict[Symbol, datetime.date] = {}
    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return QuantpediaFutures._last_update_date
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("http://data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        try:
            if not line[0].isdigit(): return None
            split = line.split(';')
            
            data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
            data['settle'] = float(split[1])
            data.Value = float(split[1])
        except:
            return None
            
        if config.Symbol.Value not in QuantpediaFutures._last_update_date:
            QuantpediaFutures._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
        if data.Time.date() > QuantpediaFutures._last_update_date[config.Symbol.Value]:
            QuantpediaFutures._last_update_date[config.Symbol.Value] = data.Time.date()
        return data

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读