Term Structure Commodities
The strategy trades the slope of commodity futures curves. It buys contracts in backwardation and sells those in contango, betting on mean reversion in the term structure.
Each month the system ranks futures by carry, going long the strongest backwardation and short the steepest contango. Positions roll prior to expiry.
Details
- Data: Front and deferred futures prices.
- Entry: Long top carry commodities, short bottom carry.
- Exit: Roll on contract expiration or if carry flips sign.
- Instruments: Commodity futures.
- Risk: Equal dollar weighting with stop on adverse carry change.
// TermStructureCommoditiesStrategy.cs
// -----------------------------------------------------------------------------
// Term structure momentum strategy.
// Uses two moving averages to detect momentum shifts in the primary security.
// Goes long when fast MA crosses above slow MA, short when below.
// Cooldown between trades prevents excessive order generation.
// -----------------------------------------------------------------------------
// Date: 2 Aug 2025
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Term structure momentum strategy using dual moving average crossover.
/// </summary>
public class TermStructureCommoditiesStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Fast moving average period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow moving average period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private int _cooldownRemaining;
public TermStructureCommoditiesStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetDisplay("Fast Period", "Fast moving average period", "Parameters");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetDisplay("Slow Period", "Slow moving average period", "Parameters");
_cooldownBars = Param(nameof(CooldownBars), 30)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = null;
_slowEma = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new ExponentialMovingAverage { Length = FastPeriod };
_slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
SubscribeCandles(CandleType)
.Bind(_fastEma, _slowEma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fastEma.IsFormed || !_slowEma.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
// Fast above slow -> bullish momentum -> long
if (fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Fast below slow -> bearish momentum -> short
else if (fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class term_structure_commodities_strategy(Strategy):
"""Term structure momentum strategy using dual moving average crossover."""
def __init__(self):
super(term_structure_commodities_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast Period", "Fast moving average period", "Parameters")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow Period", "Slow moving average period", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 30) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_ema = None
self._slow_ema = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(term_structure_commodities_strategy, self).OnReseted()
self._fast_ema = None
self._slow_ema = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(term_structure_commodities_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = int(self._fast_period.Value)
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = int(self._slow_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription \
.Bind(self._fast_ema, self._slow_ema, self._process_candle) \
.Start()
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
fv = float(fast_val)
sv = float(slow_val)
if fv > sv and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = int(self._cooldown_bars.Value)
elif fv < sv and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = int(self._cooldown_bars.Value)
def CreateClone(self):
return term_structure_commodities_strategy()