
“该策略交易来自六个国家的10年期债券期货,使用基于股票波动率的因子来分配头寸,通过等权重每日调整和重新平衡来消除方向性偏差,以提高精确度。”
资产类别: 差价合约、期货 | 地区: 全球 | 周期: 每日 | 市场: 债券 | 关键词: 避险
I. 策略概要
该策略侧重于来自六个国家(澳大利亚、加拿大、德国、日本、英国和美国)的10年期政府债券期货。它构建了一个基于股票波动率(三年、一年或一个月)的因子,其中高波动率预示着正向债券配置,反之亦然。配置可以遵循底部、中位数或顶部策略,或者跨版本采用组合方法。因子投资组合通过按比例买卖债券期货来构建,使用通过将指标与调整后的横截面平均值进行比较得出的横截面分数。通过对所有国家进行等权重配置来消除方向性偏差,并每日调整。该策略每日重新平衡以确保精确性。
II. 策略合理性
防御性债券投资符合债券作为避险资产的声誉,研究表明股票市场波动性指导防御性债券策略。当信心下降时,投资者从股票转向债券,这种现象与股票市场冲击期间的“避险”效应相关。相反,债券市场冲击对债券和股票都产生负面影响。股票市场波动性是债券投资的有用指标,因为它反映了投资者情绪和风险规避行为,使其成为把握防御性债券策略时机的重要工具。这一洞察解释了为什么股票市场波动性可以有效地用于债券市场决策。
III. 来源论文
Beyond Carry and Momentum in Government Bonds [点击查看论文]
- 热罗姆·加瓦(Jerome Gava)、威廉·勒费布尔(William Lefebvre)和朱利安·特克(Julien Turc),法国巴黎银行(BNP Paribas);巴黎综合理工学院,概率、统计与建模实验室(LPSM);法国巴黎银行;法国巴黎银行;巴黎综合理工学院。
<摘要>
本文回顾了近期关于政府债券因子投资的文献,特别是关于价值和防御性投资的定义。作者使用机器学习技术,识别了政府债券期货的关键驱动因素和最真正相关的因子组。除了利差和动量之外,他们提出了一种防御性投资方法,该方法考虑了政府债券的避险性质。这两种主要风格可以辅以价值和反转因子,以实现独立于利率广泛变动的回报。
IV. 回测表现
| 年化回报 | 2.8% |
| 波动率 | 8.2% |
| β值 | 0.013 |
| 夏普比率 | 0.34 |
| 索提诺比率 | -1.898 |
| 最大回撤 | -15% |
| 胜率 | 49% |
V. 完整的 Python 代码
import numpy as np
from AlgorithmImports import *
class FlighttoQualityFactor(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = {
"EWA" : "ASX_XT1", # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 (Australia)
"EWC" : "MX_CGB1", # Ten-Year Government of Canada Bond Futures, Continuous Contract #1 (Canada)
"EWG" : "EUREX_FGBL1", # Euro-Bund (10Y) Futures, Continuous Contract #1 (Germany)
"EWJ" : "SGX_JB1", # SGX 10-Year Mini Japanese Government Bond Futures, Continuous Contract #1 (Japan)
"EWU" : "LIFFE_R1", # Long Gilt Futures, Continuous Contract #1 (U.K.)
"SPY" : "CME_TY1" # 10 Yr Note Futures, Continuous Contract #1 (USA)
}
self.data = {}
self.period = 12 * 21
leverage: int = 2
self.SetWarmUp(self.period)
for symbol in self.symbols:
bond = self.symbols[symbol]
self.AddEquity(symbol, Resolution.Daily)
self.data[symbol] = RollingWindow[float](self.period)
data = self.AddData(QuantpediaFutures, bond, Resolution.Daily)
data.set_leverage(leverage)
data.SetFeeModel(CustomFeeModel())
def OnData(self, data):
for symbol, bond in self.symbols.items():
if self.securities[bond].get_last_data() and self.time.date() > QuantpediaFutures.get_last_update_date()[bond]:
self.liquidate(bond)
self.data[symbol].reset()
continue
if symbol in data and data[symbol]:
price = data[symbol].Value
self.data[symbol].Add(price)
if self.IsWarmingUp: return
volatility = {}
for symbol in self.symbols:
if self.data[symbol].IsReady:
prices = np.array([x for x in self.data[symbol]])
returns = prices[:-1] / prices[1:] - 1
volatility[symbol] = np.std(returns)
if len(volatility) <= 1: return
avg_volatility = np.mean([x[1] for x in volatility.items()])
diff = {symbol : (volatility[symbol] - avg_volatility) for symbol in volatility}
total_diff = sum([abs(x[1]) for x in diff.items()])
for symbol in diff:
bond = self.symbols[symbol]
if data.contains_key(bond) and data[bond]:
self.SetHoldings(bond, diff[symbol] / total_diff)
# 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.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
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("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
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
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