
“该策略根据最后30分钟的交易情况,交易流动性债券期货。回报为正时建立多头头寸,回报为负时建立空头头寸,并持有头寸直至市场收盘。”
资产类别: 差价合约、期货 | 地区: 全球 | 周期: 日内 | 市场: 债券 | 关键词: 动量
I. 策略概要
该投资范围包括来自北美、欧洲和亚洲/澳大利亚发达市场的16种流动性最强的债券期货。该策略涉及在交易最后30分钟内回报为正的期货中建立多头头寸,回报为负的期货中建立空头头寸。头寸持有至交易日结束。期货合约单独评估并等权重。该策略使用流动性最强的合约,通常是最近交割的合约,并在其交易量超过当前合约时展期到下一个合约。该策略每日重新平衡,更多细节请参见原始论文的附录。
II. 策略合理性
日内交易策略基于以下证据:日内早些时候的大幅价格变动预示着随后的回报,这与日内对冲活动相符。支持这一点的主要原因有四个:1)交易成本使得在价格变动期间部分对冲最优,之后需要额外对冲;2)临近市场收盘时流动性增加,提高了交易执行效率;3)对冲通常在收盘时完成,以避免隔夜风险;4)隔夜持仓会产生更高的成本。此外,不频繁的投资组合再平衡和迟到的知情交易可能有助于动量,因为机构投资者通常在交易日的开始和结束时进行再平衡,从而产生动量。对于股票期货,最大的交易量发生在常规市场交易时间,而对于其他资产类别,交易量在开盘和收盘时飙升,使得这些时期成为理想的交易时机。
III. 来源论文
Low-Risk Effect: Hedging demand and market intraday momentum [点击查看论文]
- Guido Baltussen, Zhi Da, Sten Lammers, Martin Martens。鹿特丹伊拉斯姆斯大学经济学院,Burgemeester Oudlaan 50, Rotterdam 3000 DR, Netherlands。鹿博资产管理,Weena 850, Rotterdam 3014 DA, Netherlands。圣母大学,239 Mendoza College of Business, Notre Dame, IN 46556, USA。鹿特丹伊拉斯姆斯大学经济学院,Burgemeester Oudlaan 50, Rotterdam 3000 DR, Netherlands。鹿博资产管理,Weena 850, Rotterdam 3014 DA, Netherlands
<摘要>
为对冲空头伽马敞口,需要顺应价格变动方向进行交易,从而产生价格动量。我们通过对1974年至2020年间股票、债券、商品和货币等60多种期货的日内回报进行研究,发现各地都存在强劲的“市场日内动量”。市场收盘前30分钟的回报与当天其余时间(从上次市场收盘到最后30分钟)的回报呈正相关。这种预测能力在经济和统计上都非常显著,并在接下来的几天内反转。我们提供了新的证据,将市场日内动量与期权和杠杆ETF做市商等市场参与者的伽马对冲需求联系起来。


IV. 回测表现
| 年化回报 | 2.16% |
| 波动率 | 1.33% |
| β值 | 0 |
| 夏普比率 | 1.62 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 6% |
V. 完整的 Python 代码
from datetime import time
EXPIRY_MIN_DAYS = 90
EXPIRY_LIQUIDATE_DAYS = 5
class IntradayMomentuminFixedIncome(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2008, 1, 1)
self.SetCash(100000)
self.symbols = [
Futures.Financials.Y30TreasuryBond,
Futures.Financials.Y10TreasuryNote,
Futures.Financials.Y5TreasuryNote,
Futures.Financials.Y2TreasuryNote,
]
self.close_price = {}
self.active_future = {}
for symbol in self.symbols:
future = self.AddFuture(symbol)
future.SetFilter(EXPIRY_LIQUIDATE_DAYS, EXPIRY_MIN_DAYS)
self.active_future[symbol] = None
self.Schedule.On(self.DateRules.EveryDay(future.Symbol), self.TimeRules.BeforeMarketClose(future.Symbol, 1), self.Close)
self.Schedule.On(self.DateRules.EveryDay(future.Symbol), self.TimeRules.BeforeMarketClose(future.Symbol, 30), self.Purchase)
def OnData(self, slice):
for chain in slice.FutureChains:
contracts = [contract for contract in chain.Value]
sym = chain.Value.Symbol.Value
if len(contracts) == 0:
self.active_future[sym] = None
continue
contract = sorted(contracts, key=lambda k : k.OpenInterest, reverse=True)[0]
if sym in self.active_future and self.active_future[sym] is not None and contract.Symbol == self.active_future[sym].Symbol:
continue
self.active_future[sym] = contract
self.Securities[contract.Symbol].SetFeeModel(CustomFeeModel(self))
def Purchase(self):
if len(self.close_price) == 0: return
performance_until_purchase = {}
for symbol, close_price in self.close_price.items():
if symbol in self.active_future:
current_price = (self.active_future[symbol].AskPrice + self.active_future[symbol].BidPrice) / 2 if self.active_future[symbol].LastPrice == 0 else self.active_future[symbol].LastPrice
sym = self.active_future[symbol].Symbol
if current_price != 0 and close_price != 0:
performance_until_purchase[sym] = current_price / close_price - 1
self.close_price.clear()
if len(performance_until_purchase) == 0: return
long = [x[0] for x in performance_until_purchase.items() if x[1] > 0]
short = [x[0] for x in performance_until_purchase.items() if x[1] < 0]
long_count = len(long)
short_count = len(short)
for symbol in long:
self.SetHoldings(symbol, 0.1 / long_count)
for symbol in short:
self.SetHoldings(symbol, -0.1 / short_count)
def Close(self):
self.Liquidate()
for symbol, contract in self.active_future.items():
if contract:
self.close_price[symbol] = (contract.AskPrice + contract.BidPrice) / 2 if contract.LastPrice == 0 else contract.LastPrice
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0
return OrderFee(CashAmount(fee, "USD"))