
“该策略通过在格林尼治标准时间+3区晚上7点(GMT+3)在异常回报为正的日子开立原油多头头寸,并持有至午夜以获取短期收益来交易原油。”
资产类别: 差价合约、ETFs、期货 | 地区: 全球 | 周期: 日内 | 市场: 大宗商品 | 关键词: 原油、动量
I. 策略概要
该策略侧重于原油商品,将正异常回报定义为超过平均回报两个标准差的回报。在检测到此类回报的日子,投资者在格林尼治标准时间+3区晚上7点(GMT+3)开立多头头寸,并持有至午夜。这种方法旨在利用显著的短期价格上涨,利用时机最大化异常回报事件的潜在收益。
II. 策略合理性
异常日回报表现出强烈的暂时性动量效应,持续到当天结束。这种模式与经典动量理论一致,通常归因于投资者羊群行为,即上涨的价格被集体行动进一步放大。这种现象类似于一列正在加速的火车,使其难以突然停止。这种暂时性动量表明,由异常回报驱动的价格变动持续时间较短,为利用这种持续效应的策略提供了机会。
III. 来源论文
Gold and Oil Prices: Abnormal Returns, Momentum and Contrarian Effects [点击查看论文]
- Guglielmo Maria Caporale, Alex Plastun
<摘要>
本文探讨了在异常回报日和随后的日子里,两种商品市场中的价格(动量和反向)效应。具体来说,使用2009年1月1日至2020年3月31日期间的每日黄金和原油价格数据,检验了以下假设:H1)异常回报日存在价格效应;H2)异常回报发生后的第二天存在价格效应;H3)异常回报引起的价格效应是可利用的。为此,采用了平均分析、t检验、CAR和交易模拟方法。主要结果可总结如下。异常回报日期间的小时回报显著大于“正常”日期间的平均回报。当异常回报发生时,价格倾向于朝着异常回报的方向移动,直到当天结束。异常回报的存在通常可以在当天结束前通过估计特定时间参数来检测到,并且可以检测到动量效应。第二天检测到两种不同的价格模式:原油价格的动量效应和黄金价格的反向效应。交易模拟表明,这些效应可以被利用以产生异常利润。


IV. 回测表现
| 年化回报 | 21.36% |
| 波动率 | N/A |
| β值 | 0.001 |
| 夏普比率 | N/A |
| 索提诺比率 | -1.329 |
| 最大回撤 | N/A |
| 胜率 | 48% |
V. 完整的 Python 代码
from AlgorithmImports import *
import numpy as np
from math import floor
#endregion
class OilIntradayMomentum(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(1000000)
self.period = 3 * 21
self.treshhold_value = 2
self.future:Future = self.AddFuture(Futures.Energies.CrudeOilWTI, \
Resolution.Minute, \
dataNormalizationMode=DataNormalizationMode.BackwardsRatio, \
contractDepthOffset=0)
self.symbol:Symbol = self.future.Symbol
self.daily_ret = [] # daily returns
self.open = 0 # latest open
self.day_close_flag:bool = False
self.day_open_flag:bool = False
self.market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.BeforeMarketClose(self.market, 1), self.DayClose)
self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.AfterMarketOpen(self.market, 1), self.DayOpen)
def OnData(self, data: Slice) -> None:
# close
if self.day_close_flag:
self.day_close_flag = False
self.Liquidate()
if self.symbol in data and data[self.symbol]:
close:float = data[self.symbol].Close
if close != 0 and self.open != 0 and close != self.open:
todays_ret:float = close / self.open - 1
self.daily_ret.append(todays_ret)
if len(self.daily_ret) < self.period:
return
mean:float = np.mean(self.daily_ret)
std:float = np.std(self.daily_ret)
high_threshhold = mean + self.treshhold_value * std
if todays_ret > high_threshhold:
if not self.Portfolio.Invested:
self.MarketOrder(self.future.Mapped, 1)
self.open = 0
# open
if self.day_open_flag:
self.day_open_flag = False
if self.symbol in data and data[self.symbol]:
self.open = data[self.symbol].Open
def DayClose(self) -> None:
self.day_close_flag = True
def DayOpen(self) -> None:
self.day_open_flag = True