“该策略使用八种流动性外汇期货,结合利率套利、动量和均值回归等标准化指标,进行风险预算分配,等权重配置,并每月重新平衡。”

I. 策略概要

该策略投资于芝加哥商品交易所(CME)八种流动性最强的当月外汇期货,涉及多种货币(澳元、英镑、加元、欧元、日元、墨西哥比索、新西兰元和瑞士法郎)。使用多种指标来创建单独的策略,包括利率套利、动量、均值回归、股票动量和商品动量。这些指标通过计算相对于历史值的百分位数得分,并进行-0.5的偏差调整,标准化到-0.5到+0.5的范围。采用风险预算,根据每个指标标准化分数的绝对值进行分配,所有指标的年化总风险目标为10%。这些策略以等权重组合,并每月重新平衡,允许在优化过程中出现负权重。论文中还提供了用于优化的Python代码。

II. 策略合理性

该策略的功能源于使用一套从各种策略中提取的标准化指标,这些指标成功预测了外汇期货回报。套利策略是一种行之有效的方法,涉及在低利率经济体借款并在高利率经济体放贷。这在股票市场回报和外汇回报之间建立了联系,解释了股票动量的使用。此外,商品价格,特别是农产品、石油和金属,与外汇回报密切相关。发达货币之间的交叉汇率趋于均值回归,进一步支持了该策略的有效性。多策略方法被证明更优越,与表现最佳的单一策略相比,提供了更稳定的一致回报并提高了夏普比率,展示了系统化、多元化方法的优势。

III. 来源论文

Realized Semibetas: Signs of Things to Come [点击查看论文]

<摘要>

在本文中,我们提出了一种系统化的多策略方法,用于管理期货投资组合中的外汇期货交易。我们的核心发现是,与手工设计每个指标相比,结合不同的指标可以获得更多的阿尔法。我们表明,将动量和均值回归等技术指标与外汇套利指标相结合,可以显著优于单个指标。通过端到端的系统化投资组合构建方法,包括指标构建、标准化和组合,我们在无偏的前向回测中评估时,能够将所得投资组合的夏普比率比表现最佳的单一指标提高60%。

IV. 回测表现

年化回报9.76%
波动率9.3%
β值0.033
夏普比率0.5
索提诺比率-0.965
最大回撤N/A
胜率48%

V. 完整的 Python 代码

from AlgorithmImports import *
import numpy as np
from typing import List, Dict, Tuple
from scipy import stats
class AMultiStrategyApproachtoTradingForeignExchangeFutures(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        # Symbols - Currency future, equity future, 10Y bond yield, cash rate data.
        # Cash rate source: https://www.quandl.com/data/OECD-Organisation-for-Economic-Co-operation-and-Development
        # 10Y bond yield source: www.investing.com
        self.symbols:List[Tuple[str, str, str, str]] = [
            ('CME_AD1', 'ASX_YAP1', 'AU10YT', 'IR3TIB01AUM156N'),    # Australian Dollar Futures, Continuous Contract #1
            ('CME_CD1', 'LIFFE_FCE1', 'CA10YT', 'IR3TIB01CAM156N'),  # Canadian Dollar Futures, Continuous Contract #1
            ('CME_SF1', 'EUREX_FSMI1', 'CH10YT', 'IR3TIB01CHM156N'), # Swiss Franc Futures, Continuous Contract #1
            ('CME_EC1', 'EUREX_FSTX1', 'DE10YT', 'IR3TIB01EZM156N'), # Euro FX Futures, Continuous Contract #1
            ('CME_BP1', 'LIFFE_Z1', 'GB10YT', 'LIOR3MUKM'),          # British Pound Futures, Continuous Contract #1
            ('CME_JY1', 'SGX_NK1', 'JP10YT', 'IR3TIB01JPM156N'),     # Japanese Yen Futures, Continuous Contract #1
        ]
                    
        # Symbol data.
        self.data:Dict[Symbol, SymbolData] = {}
        self.short_period:int = 3
        self.long_period:int = 12
        self.leverage:int = 20
        
        # Target risk of the allocation.
        self.target_risk:float = 0.1
        
        for currency_future, equity_future, bond_yield_symbol, cash_rate_symbol in self.symbols:
            # Currency future data.
            data = self.AddData(data_tools.QuantpediaFutures, currency_future, Resolution.Daily)
            data.SetFeeModel(data_tools.CustomFeeModel())
            data.SetLeverage(self.leverage)
            self.data[currency_future] = data_tools.SymbolData(currency_future, self, self.short_period*21, self.long_period*21, True)
            
            # Equity future data.
            self.AddData(data_tools.QuantpediaFutures, equity_future, Resolution.Daily)
            self.data[equity_future] = data_tools.SymbolData(equity_future, self, self.short_period*21, self.long_period*21, False)
            
            # Bond yield data.
            self.AddData(data_tools.QuantpediaBondYield, bond_yield_symbol, Resolution.Daily)
            # Interbank rate data.
            self.AddData(data_tools.InterestRate3M, cash_rate_symbol, Resolution.Daily)
        
        self.usa_10Y_yield:str = 'US10Y'
        self.usa_cash_rate:str = 'IR3TIB01USM156N'
        self.AddData(data_tools.QuantpediaBondYield, self.usa_10Y_yield, Resolution.Daily)
        self.AddData(data_tools.InterestRate3M, self.usa_cash_rate, Resolution.Daily)
        self.commodity_index:Symbol = self.AddEquity('DBC', Resolution.Daily).Symbol
        self.data[self.commodity_index] = data_tools.SymbolData(self.commodity_index, self, self.short_period*21, self.long_period*21, False)
        
        self.last_month:int = -1
        
    def OnData(self, data:Slice) -> None:
        # Rebalance once a month.
        if self.last_month != self.Time.month:
            self.last_month = self.Time.month
            ir_last_update_date:Dict[str, datetime.date] = data_tools.InterestRate3M.get_last_update_date()
            qp_futures_last_update_date:Dict[str, datetime.date] = data_tools.QuantpediaFutures.get_last_update_date()
            
            # Create indicators.
            weight:Dict[Symbol, float] = {}
            for currency_future, equity_future, bond_yield_symbol, cash_rate_symbol in self.symbols:
                # data is still coming
                if self.Securities[currency_future].GetLastData() and qp_futures_last_update_date[currency_future] <= self.Time.date() \
                    or self.Securities[cash_rate_symbol].GetLastData() and ir_last_update_date[cash_rate_symbol] <= self.Time.date():
                    continue
                # If data needed is ready (Momentum and SMA's).
                if self.data[currency_future].is_ready() and self.data[equity_future].is_ready() and self.data[self.commodity_index].is_ready():
                    
                    long_interest_rate_carry:Union[float,None] = self.Securities[bond_yield_symbol].Price - self.Securities[self.usa_10Y_yield].Price if self.Securities.ContainsKey(bond_yield_symbol) and self.Securities.ContainsKey(self.usa_10Y_yield) else None
                    short_interest_rate_carry:Union[float,None] = self.Securities[cash_rate_symbol].Price - self.Securities[self.usa_cash_rate].Price if self.Securities.ContainsKey(cash_rate_symbol) and self.Securities.ContainsKey(self.usa_cash_rate) else None
                    
                    curr_short_term_momentum:float = self.data[currency_future].short_term_momentum()
                    curr_long_term_momentum:float = self.data[currency_future].long_term_momentum()
                    
                    curr_long_term_reversal:Union[float,None] =  self.Securities[currency_future].Price / self.data[currency_future].long_term_sma() if self.Securities.ContainsKey(currency_future) else None
                    curr_short_term_reversal:Union[float,None] =  self.Securities[currency_future].Price / self.data[currency_future].short_term_sma() if self.Securities.ContainsKey(currency_future) else None
                    equity_short_term_momentum:float = self.data[equity_future].short_term_momentum()
                    equity_long_term_momentum:float = self.data[equity_future].long_term_momentum()
                    
                    commodity_short_term_momentum:float = self.data[self.commodity_index].short_term_momentum()
                    commodity_long_term_momentum:float = self.data[self.commodity_index].long_term_momentum()
                    
                    if long_interest_rate_carry and short_interest_rate_carry and curr_long_term_reversal and curr_short_term_reversal:
                        indicator_value:Tuple[float] = (long_interest_rate_carry, short_interest_rate_carry, curr_long_term_momentum, curr_short_term_momentum,    \
                                                        curr_long_term_reversal, curr_short_term_reversal, equity_long_term_momentum, equity_short_term_momentum,  \
                                                        commodity_long_term_momentum, commodity_short_term_momentum)
                        
                        self.data[currency_future].add_indicator_value(indicator_value)
                        
                        if self.data[currency_future].indicator_history_is_ready():
                            num_of_properties:int = len(indicator_value)
                            scores:List[float] = []
                            for propert_index in range(num_of_properties):
                                scores.append(abs((stats.percentileofscore([x[propert_index] for x in self.data[currency_future].IndicatorsHistory], indicator_value[propert_index]) / 100 ) - 0.5))
                            
                            score_sum:float = sum(scores)
                            scores:np.ndarray = np.array(scores)
                            scores = (scores / score_sum) * self.target_risk
                            risk_budget:float = sum(scores)
                            weight[currency_future] = risk_budget
            
            # Trade execution.
            invested:List[Symbol] = [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)

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读