The strategy trades 11 commodity futures, creating a zero-investment portfolio by buying past overnight losers and selling winners, with daily rebalancing and adjusted weights to manage excessive volatility.

I. STRATEGY IN A NUTSHELL

The strategy targets 11 commodity futures, including Corn, Ethanol CBOT, Lean Hogs, Live Cattle, and others, selecting the most liquid contracts. A zero-investment portfolio is constructed by buying the previous day’s overnight losers and selling overnight winners. Commodity weights are determined using formulas from the academic paper, with 1/2 of the calculated weight used to estimate portfolio return and volatility, as full weights indicate excessive volatility (60% annually). The portfolio is rebalanced daily, with trades executed from the next open-to-close period after the formation period. This approach aims to exploit short-term price movements in commodities.

II. ECONOMIC RATIONALE

Price reversals often occur due to investor overreaction to news, followed by a price correction. Market closures, like overnight or weekend breaks, typically feature low liquidity and trading activity. Research by Nagel (2012) shows that uncertainty, measured by the VIX index, plays a significant role in explaining profits from the Close-to-Open (CO-OC) reversal strategy. This effect is particularly strong in futures markets. The CO-OC reversal pattern aligns with Hong and Wang’s (2000) continuous-time model, suggesting that hedging demands during market closures contribute to these reversals, leading to predictable profit opportunities.

III. SOURCE PAPER

Market Closure and Short-Term Reversal [Click to Open PDF]

Corte, Kosowski, Wang

<Abstract>

A strategy that buys securities with low past overnight returns and sells securities with high past overnight returns generates sizeable out-of-sample intraday returns and Sharpe ratios in all major asset classes. This strategy, labeled as overnight-intraday reversal, delivers an average return that is about two to five times larger than those generated by the conventional reversal strategy. Investor heterogeneity, sentiment, market uncertainty and market-wide illiquidity fail to explain this overnight-intraday reversal return. Our findings are consistent with an asset class-specific market maker liquidity provision mechanism, and we find that cross-sectional return dispersion can predict the strategy returns in every asset class. A global two-factor model, consisting of the market and overnight-intraday reversal factor, well explains the intraday return variation of diversified portfolios across asset classes.

IV. BACKTEST PERFORMANCE

Annualised Return45.75%
Volatility31.02%
Beta-0.012
Sharpe Ratio 1.47
Sortino Ratio-2.897
Maximum DrawdownN/A
Win Rate44%

V. FULL PYTHON CODE

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

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading