
“该策略使用失业率缺口因子在全球五个国家配置10年期债券期货,通过等权重调整每月重新平衡,以实现系统化投资。”
资产类别: 差价合约、期货 | 地区: 全球 | 周期: 每月 | 市场: 债券 | 关键词: 失业率
I. 策略概要
该策略投资于澳大利亚、加拿大、德国、英国和美国的10年期政府债券期货,使用失业率缺口因子(9个月变化、1个月变化或3年缺口)。配置策略可以遵循底部、中位数或顶部版本,或单一因子方法。因子投资组合通过按比例买卖债券期货来构建,基于将指标与横截面平均值(根据离散度调整)进行比较得出的横截面分数。通过对各国进行等权重配置来消除方向性偏差,并每日调整。投资组合每月重新平衡,利用失业率缺口趋势进行系统性债券投资决策。
II. 策略合理性
失业率与债券之间存在直观的联系,因为中央银行利用短期利率来影响产出增长并控制失业率。长期利率受债务/GDP的影响,失业率间接与GDP挂钩。虽然没有明确的基本面原因解释为什么失业率缺口能够预测债券期货,但统计和机器学习分析表明它是一个可靠的因子。在投资组合构建中,失业率缺口优于纯粹的失业率,产生更高的信息比率,使其成为债券投资策略的有效工具。
III. 来源论文
Beyond Carry and Momentum in Government Bonds [点击查看论文]
- 热罗姆·加瓦(Jerome Gava)、威廉·勒费布尔(William Lefebvre)和朱利安·特克(Julien Turc),法国巴黎银行(BNP Paribas);巴黎综合理工学院,概率、统计与建模实验室(LPSM);法国巴黎银行;法国巴黎银行;巴黎综合理工学院。
<摘要>
本文回顾了近期关于政府债券因子投资的文献,特别是关于价值和防御性投资的定义。作者使用机器学习技术,识别了政府债券期货的关键驱动因素和最真正相关的因子组。除了利差和动量之外,他们提出了一种防御性投资方法,该方法考虑了政府债券的避险性质。这两种主要风格可以辅以价值和反转因子,以实现独立于利率广泛变动的回报。
IV. 回测表现
| 年化回报 | 1.3% |
| 波动率 | 10.1% |
| β值 | -0.002 |
| 夏普比率 | 0.13 |
| 索提诺比率 | N/A |
| 最大回撤 | -40% |
| 胜率 | 53% |
V. 完整的 Python 代码
import numpy as np
from AlgorithmImports import *
import data_tools
class UnemploymentGapFactorinFixedIncome(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.symbols = {
"ASX_XT1" : "RBA/H05_GLFSURSA", # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 (Australia)
"MX_CGB1" : "UKONS/ZXDZ_M", # Ten-Year Government of Canada Bond Futures, Continuous Contract #1 (Canada)
"EUREX_FGBL1" : "UKONS/ZXDK_M", # Euro-Bund (10Y) Futures, Continuous Contract #1 (Germany)
"LIFFE_R1" : "UKONS/YCNO_M", # Long Gilt Futures, Continuous Contract #1 (U.K.)
"CME_TY1" : "UKONS/ZXDX_M" # 10 Yr Note Futures, Continuous Contract #1 (USA)
}
# Monthly unemployment data.
self.data = {}
self.period = 3 * 12
for symbol in self.symbols:
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(5)
unempl_symbol = self.symbols[symbol]
if unempl_symbol == 'ASX_XT1':
data = self.AddData(data_tools.UnemploymentDataAUD, unempl_symbol, Resolution.Daily)
else:
data = self.AddData(data_tools.UnemploymentData, unempl_symbol, Resolution.Daily)
self.data[symbol] = RollingWindow[float](self.period)
first_key = [x for x in self.symbols.keys()][0]
self.Schedule.On(self.DateRules.MonthStart(self.symbols[first_key]), self.TimeRules.At(0, 0), self.Rebalance)
def OnData(self, data):
# store monthly rates
for symbol in self.symbols:
unempl_symbol = self.symbols[symbol]
if unempl_symbol in data and data[unempl_symbol]:
unempl_rate = data[unempl_symbol].Value
if unempl_rate != 0:
self.data[symbol].Add(unempl_rate)
def Rebalance(self):
# Difference from MA.
ma_diff = {}
for symbol in self.symbols:
if self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days < 5:
# Unemployment data is ready to calculate MA.
if self.data[symbol].IsReady:
# Calculate difference from MA.
ma = np.mean([x for x in self.data[symbol]])
if ma != 0:
current_value = self.data[symbol][0]
ma_diff[symbol] = current_value - ma
if len(ma_diff) != 0:
# Difference weighting.
avg_diff = np.mean([x[1] for x in ma_diff.items()])
diff_from_avg = { symbol: diff - avg_diff for symbol, diff in ma_diff.items() }
total_diff = sum([abs(x[1]) for x in diff_from_avg.items()])
weight = { symbol: diff / total_diff for symbol, diff in diff_from_avg.items() }
for symbol, w in weight.items():
self.SetHoldings(symbol, w)
else:
self.Liquidate()