
“该策略交易11种商品期货,通过买入过去的隔夜输家和卖出赢家来创建零投资组合,每日重新平衡,并调整权重以管理过度波动。”
资产类别: 差价合约、期货 | 地区: 全球 | 周期: 日内 | 市场: 大宗商品 | 关键词: 隔夜,反转
I. 策略概要
该策略针对11种商品期货,包括玉米、CBOT乙醇、瘦猪、活牛等,选择流动性最强的合约。通过买入前一天的隔夜输家和卖出隔夜赢家,构建零投资组合。商品权重使用学术论文中的公式确定,使用计算权重的1/2来估计投资组合的回报和波动率,因为全部权重表明波动率过高(每年60%)。投资组合每日重新平衡,交易在形成期后的下一个开盘至收盘期间执行。此方法旨在利用商品中的短期价格波动。
II. 策略合理性
价格反转通常是由于投资者对新闻反应过度,随后出现价格修正。像隔夜或周末休息这样的市场休市,通常流动性和交易活动较低。Nagel(2012)的研究表明,以VIX指数衡量的不确定性在解释收盘至开盘(CO-OC)反转策略的利润方面起着重要作用。这种效应在期货市场尤为明显。CO-OC反转模式与Hong和Wang(2000)的连续时间模型一致,表明市场休市期间的对冲需求促成了这些反转,从而产生了可预测的盈利机会。
III. 来源论文
市场休市和短期反转 [点击查看论文]
- Corte, Kosowski, Wang
<摘要>
一种买入过去隔夜回报较低的证券,卖出过去隔夜回报较高的证券的策略,在所有主要资产类别中都能产生可观的样本外日内回报和夏普比率。这种被称为隔夜-日内反转的策略,其平均回报大约是传统反转策略产生的回报的二到五倍。投资者异质性、情绪、市场不确定性和全市场流动性不足都无法解释这种隔夜-日内反转回报。我们的发现与资产类别特定的做市商流动性供给机制一致,我们发现横截面回报分散可以预测每个资产类别的策略回报。一个由市场和隔夜-日内反转因子组成的全球双因子模型,很好地解释了跨资产类别的多元化投资组合的日内回报变化。


IV. 回测表现
| 年化回报 | 45.75% |
| 波动率 | 31.02% |
| β值 | -0.012 |
| 夏普比率 | 1.47 |
| 索提诺比率 | -2.897 |
| 最大回撤 | N/A |
| 胜率 | 44% |
V. 完整的 Python 代码
from AlgorithmImports import *
#endregion
class OvernightIntradayDailyReversalinCommodities(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2015, 1, 1)
self.SetCash(1000000)
symbols:List[str] = [
Futures.Grains.Corn,
Futures.Meats.LeanHogs,
Futures.Meats.LiveCattle,
Futures.Forestry.Lumber,
Futures.Grains.Oats,
Futures.Grains.SoybeanMeal,
Futures.Grains.Soybeans,
Futures.Grains.Wheat,
]
self.traded_percentage:float = 0.2
self.futures:List[Symbol] = []
self.recent_close:Dict[Symbol, float] = {}
for symbol in symbols:
future = self.AddFuture(symbol, Resolution.Minute, dataNormalizationMode=DataNormalizationMode.BackwardsRatio, contractDepthOffset=0)
self.futures.append(future.Symbol)
self.day_close_flag:bool = False
self.day_open_flag:bool = False
self.Schedule.On(self.DateRules.EveryDay(self.futures[3]), self.TimeRules.BeforeMarketClose(self.futures[3], 1), self.DayClose)
self.Schedule.On(self.DateRules.EveryDay(self.futures[3]), self.TimeRules.AfterMarketOpen(self.futures[3], 1), self.DayOpen)
def OnData(self, data: Slice) -> None:
if self.day_open_flag:
self.day_open_flag = False
returns:Dict[Symbol, float] = { symbol : data[symbol].Open / self.recent_close[symbol] - 1 for symbol in self.futures if symbol in self.recent_close and symbol in data and data[symbol] }
self.recent_close.clear()
long:List[Symbol] = [x[0] for x in returns.items() if x[1] < 0]
short:List[Symbol] = [x[0] for x in returns.items() if x[1] > 0]
for i, portfolio in enumerate([long, short]):
for symbol in portfolio:
# notional value = asset price * contract multiplier
notional_value:float = (self.Securities[symbol].Price * self.Securities[symbol].SymbolProperties.ContractMultiplier)
quantity:int = int((((-1) ** i) * self.Portfolio.TotalPortfolioValue) / len(portfolio) * self.traded_percentage) // notional_value
self.MarketOrder(self.Securities[symbol].Mapped, quantity)
if self.day_close_flag:
self.day_close_flag = False
self.Liquidate()
self.recent_close = { symbol : data[symbol].Close for symbol in self.futures if symbol in data and data[symbol] }
def DayClose(self) -> None:
self.day_close_flag = True
def DayOpen(self) -> None:
self.day_open_flag = True