Chande Momentum Oscillator Strategy
Strategy buys when the Chande Momentum Oscillator drops below a lower threshold and closes the position when it rises above an upper threshold or after a fixed number of bars.
Testing indicates an average annual return of about 40%. It performs best in trending markets.
The oscillator compares recent gains and losses to gauge momentum. Extreme negative values suggest oversold conditions, which the strategy uses for long entries. Positions are closed when momentum turns positive or the holding period expires.
Details
- Entry Criteria:
CMO < LowerThreshold. - Long/Short: Long only.
- Exit Criteria:
CMO > UpperThresholdorMaxBarsInPositionbars elapsed. - Stops: No.
- Default Values:
CmoPeriod= 9LowerThreshold= -50UpperThreshold= 50MaxBarsInPosition= 5CandleType= TimeSpan.FromMinutes(1)
- Filters:
- Category: Mean reversion
- Direction: Long
- Indicators: CMO
- Stops: No
- Complexity: Basic
- Timeframe: Intraday (1m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Chande Momentum Oscillator strategy.
/// Long only: buys when CMO(CmoPeriod) is below LowerThreshold and closes the long when CMO rises above UpperThreshold or after
/// MaxBarsInPosition candles.
/// </summary>
public class ChandeMomentumOscillatorStrategy : Strategy
{
private readonly StrategyParam<int> _cmoPeriod;
private readonly StrategyParam<decimal> _lowerThreshold;
private readonly StrategyParam<decimal> _upperThreshold;
private readonly StrategyParam<int> _maxBarsInPosition;
private readonly StrategyParam<DataType> _candleType;
private int _barsInPosition;
/// <summary>
/// CMO period.
/// </summary>
public int CmoPeriod
{
get => _cmoPeriod.Value;
set => _cmoPeriod.Value = value;
}
/// <summary>
/// CMO level below which the strategy buys.
/// </summary>
public decimal LowerThreshold
{
get => _lowerThreshold.Value;
set => _lowerThreshold.Value = value;
}
/// <summary>
/// CMO level above which the long closes.
/// </summary>
public decimal UpperThreshold
{
get => _upperThreshold.Value;
set => _upperThreshold.Value = value;
}
/// <summary>
/// Candles a position is held at most.
/// </summary>
public int MaxBarsInPosition
{
get => _maxBarsInPosition.Value;
set => _maxBarsInPosition.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ChandeMomentumOscillatorStrategy()
{
_cmoPeriod = Param(nameof(CmoPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("CMO Period", "CMO period", "CMO");
_lowerThreshold = Param(nameof(LowerThreshold), -50m)
.SetDisplay("Lower Threshold", "CMO level below which the strategy buys", "CMO");
_upperThreshold = Param(nameof(UpperThreshold), 50m)
.SetDisplay("Upper Threshold", "CMO level above which the long closes", "CMO");
_maxBarsInPosition = Param(nameof(MaxBarsInPosition), 5)
.SetGreaterThanZero()
.SetDisplay("Max Bars In Position", "Candles a position is held at most", "Exit");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_barsInPosition = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_barsInPosition = 0;
var cmo = new ChandeMomentumOscillator { Length = CmoPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(cmo, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, cmo);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue cmoValue)
{
if (candle.State != CandleStates.Finished)
return;
if (Position > 0)
_barsInPosition++;
if (!cmoValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var cmo = cmoValue.GetValue<decimal>();
if (Position > 0)
{
if (cmo > UpperThreshold || _barsInPosition >= MaxBarsInPosition)
SellMarket(Position);
return;
}
if (Position == 0 && cmo < LowerThreshold)
{
BuyMarket(Volume);
_barsInPosition = 0;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ChandeMomentumOscillator
from StockSharp.Algo.Strategies import Strategy
class chande_momentum_oscillator_strategy(Strategy):
"""
Chande Momentum Oscillator strategy.
Long only: buys when CMO(CmoPeriod) is below LowerThreshold and closes the long when CMO rises above UpperThreshold or after
MaxBarsInPosition candles.
"""
def __init__(self):
super(chande_momentum_oscillator_strategy, self).__init__()
self._cmo_period = self.Param("CmoPeriod", 9).SetGreaterThanZero().SetDisplay("CMO Period", "CMO period", "CMO")
self._lower_threshold = self.Param("LowerThreshold", -50.0).SetDisplay("Lower Threshold", "CMO level below which the strategy buys", "CMO")
self._upper_threshold = self.Param("UpperThreshold", 50.0).SetDisplay("Upper Threshold", "CMO level above which the long closes", "CMO")
self._max_bars_in_position = self.Param("MaxBarsInPosition", 5).SetGreaterThanZero().SetDisplay("Max Bars In Position", "Candles a position is held at most", "Exit")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._bars_in_position = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(chande_momentum_oscillator_strategy, self).OnReseted()
self._bars_in_position = 0
def OnStarted2(self, time):
super(chande_momentum_oscillator_strategy, self).OnStarted2(time)
self._bars_in_position = 0
cmo = ChandeMomentumOscillator()
cmo.Length = self._cmo_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(cmo, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, cmo)
def _process_candle(self, candle, cmo_value):
if candle.State != CandleStates.Finished:
return
if self.Position > 0:
self._bars_in_position += 1
if not cmo_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
cmo = cmo_value.GetValue[Decimal](None)
if self.Position > 0:
if cmo > Decimal(self._upper_threshold.Value) or self._bars_in_position >= self._max_bars_in_position.Value:
self.SellMarket(self.Position)
return
if self.Position == 0 and cmo < Decimal(self._lower_threshold.Value):
self.BuyMarket(self.Volume)
self._bars_in_position = 0
def CreateClone(self):
return chande_momentum_oscillator_strategy()