
“通过API数据与彭博社共识的预测器交易WTI期货,在EIA公告前60分钟建仓,并在公告前1分钟平仓。”
资产类别: 期货 | 地区: 全球 | 周期: 日内 | 市场: 商品 | 关键词: 库存、原油
I. 策略概要
投资范围包括近月WTI期货合约。预测器计算为API实际数据与彭博社中值共识之差,除以库存水平。如果预测器为正,则卖出WTI期货;如果为负,则买入WTI期货。头寸在EIA公告前60分钟建立,并在公告前1分钟平仓,利用预测器的洞察力在库存数据发布前后进行短期交易。
II. 策略合理性
市场参与者对API和EIA原油库存公告的处理方式不同,尽管两者收集的数据相似。EIA报告更广为人知,而API报告在正常交易时间后发布,流动性较低,因此交易员并未完全将其计入价格。API发布后回报对EIA发布前原油回报的预测能力仅在原油关注度和流动性较低的时期才显著。这与Chordia、Roll和Subrahmanyam(2008)的发现一致,他们发现回报预测能力在流动性高的情况下会下降。这些发现表明,市场状况,特别是低流动性和关注度,导致API报告中的信息延迟纳入油价。
III. 来源论文
Market Inefficiencies Surrounding Energy Announcements [点击查看论文]
- Alturki, Sultan 和 Kurov, Alexander。沙特国王大学。西弗吉尼亚大学商学院
<摘要>
我们利用连续的能源库存公告,为金融市场的信息效率提供了新的见解。我们的研究结果为原油期货和股票市场的低效率提供了明确证据。这种低效率可以被经验丰富的交易者利用。我们研究了市场流动性对信息有效纳入的影响。我们还构建了一个预测因子,可以预测库存意外和公告前回报的样本内和样本外情况。最后,我们开发了一个组合预测,可以作为市场对原油库存公告预期的替代指标。


IV. 回测表现
| 年化回报 | 6.7% |
| 波动率 | 10% |
| β值 | 0.017 |
| 夏普比率 | 0.67 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 48% |
V. 完整的 Python 代码
from AlgorithmImports import *
#endregion
import data_tools
EXPIRY_MIN_DAYS = TimeSpan.FromDays(5)
EXPIRY_MAX_DAYS = TimeSpan.FromDays(35)
class InventoryMispricingPredictsOilReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2012, 3, 27)
self.SetCash(100000)
self.max_missing_days = 14
future = self.AddFuture(Futures.Energies.CrudeOilWTI, Resolution.Minute)
future.SetFilter(EXPIRY_MIN_DAYS, EXPIRY_MAX_DAYS)
self.active_future = None
self.symbol = future.Symbol
self.api_report = self.AddData(data_tools.APIOilReport, 'APIOilReport', Resolution.Daily).Symbol # API data comes one day before EIAOilReport
self.api_report_date = None
self.eia_report = self.AddData(data_tools.EIAOilReport, 'EIAOilReport', Resolution.Daily).Symbol
def OnData(self, slice):
# check if contract isn't None and if it is about to expire in 1 day
if self.active_future and self.active_future.Expiry.date() - timedelta(days=1) <= self.Time.date():
# liquidate contract
self.Liquidate(self.active_future.Symbol)
self.active_future = None
# pick nearest contract if there is no selected contract
if not self.active_future:
for chain in slice.FutureChains:
contracts = [contract for contract in chain.Value]
# there aren't any active contracts
if len(contracts) == 0:
continue
# contract = sorted(contracts, key=lambda k : k.OpenInterest, reverse=True)[0]
near_contract = sorted(contracts, key=lambda x: x.Expiry, reverse=True)[0]
self.active_future = near_contract
# new api report data came
if self.api_report in slice and slice[self.api_report]:
self.api_report_date = self.Time.date()
# liquidate
if self.Portfolio.Invested and ((self.Time.hour == 10 and self.Time.minute >= 29) or (self.Time.hour > 10)):
self.Liquidate()
# day after api report
if self.api_report_date and self.Time.date() == self.api_report_date:
if self.active_future:
# active contract is available
# if and self.Securities.ContainsKey(self.api_report) and self.Securities.ContainsKey(self.eia_report):
if self.Time.hour == 9 and self.Time.minute == 31:
api_actual = None
eia_prior = None
inv_level = None
api_report = self.Securities[self.api_report].GetLastData()
if api_report:
api_actual = api_report['actual']
eia_report = self.Securities[self.eia_report].GetLastData() # at 9:31 EIA report 'survey' and 'prior' columns should be available
if eia_report:
eia_survey = eia_report['survey'] # Bloomberg median consensus
inv_level = eia_report['prior']
if api_actual is not None and \
eia_survey is not None and \
inv_level is not None and \
inv_level != 0:
# predictor = (api_actual - eia_survey) / inv_level
predictor = api_actual - (eia_survey / inv_level)
# Sell (buy) WTI futures contracts 60 minutes before the EIA announcement and close the position 1 minute before the EIA announcement if the predictor is positive (negative).
if self.Securities[self.active_future.Symbol].IsTradable:
if predictor > 0:
self.SetHoldings(self.active_future.Symbol, -0.5)
else:
self.SetHoldings(self.active_future.Symbol, 0.5)
else:
if not all(self.Securities[x].GetLastData() and (self.Time.date() - self.Securities[x].GetLastData().Time.date()).days <= self.max_missing_days for x in [self.eia_report, self.api_report]):
self.Liquidate()
return