CMO Zero Cross Strategy
This strategy trades based on zero line crossings of the Chande Momentum Oscillator (CMO). When the oscillator crosses below zero, a long position is opened. When it crosses above zero, a short position is opened. Optional stop loss and take profit levels (in points) protect the position. Entries and exits for long and short trades can be individually enabled or disabled.
Parameters
Volume– order volume.CmoPeriod– period for the CMO indicator.StopLoss– stop loss in points.TakeProfit– take profit in points.AllowLongEntry– allow opening long positions.AllowShortEntry– allow opening short positions.AllowLongExit– allow closing long positions on opposite signal.AllowShortExit– allow closing short positions on opposite signal.CandleType– timeframe used for calculations.
Trading Logic
- Subscribe to candles of the selected timeframe and calculate the CMO.
- When CMO crosses from above to below zero:
- Close short positions if allowed.
- Open a long position if allowed.
- When CMO crosses from below to above zero:
- Close long positions if allowed.
- Open a short position if allowed.
- Stop loss and take profit are applied using protective orders in points.
Notes
- Trading decisions are made only on completed candles.
- The strategy uses StockSharp high-level API and binds indicators through
Bind.
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>
/// Strategy that enters on Chande Momentum Oscillator zero cross.
/// </summary>
public class CmoZeroCrossStrategy : Strategy
{
private readonly StrategyParam<int> _cmoPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<bool> _allowLongEntry;
private readonly StrategyParam<bool> _allowShortEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minAbsCmo;
private readonly StrategyParam<int> _cooldownBars;
private ChandeMomentumOscillator _cmo = null!;
private decimal? _prevCmo;
private int _cooldownRemaining;
public int CmoPeriod
{
get => _cmoPeriod.Value;
set => _cmoPeriod.Value = value;
}
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public bool AllowLongEntry
{
get => _allowLongEntry.Value;
set => _allowLongEntry.Value = value;
}
public bool AllowShortEntry
{
get => _allowShortEntry.Value;
set => _allowShortEntry.Value = value;
}
public bool AllowLongExit
{
get => _allowLongExit.Value;
set => _allowLongExit.Value = value;
}
public bool AllowShortExit
{
get => _allowShortExit.Value;
set => _allowShortExit.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal MinAbsCmo
{
get => _minAbsCmo.Value;
set => _minAbsCmo.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public CmoZeroCrossStrategy()
{
_cmoPeriod = Param(nameof(CmoPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CMO Period", "Period for Chande Momentum Oscillator", "Indicators");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetNotNegative()
.SetDisplay("Stop Loss (pt)", "Stop loss in points", "Risk Management");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetNotNegative()
.SetDisplay("Take Profit (pt)", "Take profit in points", "Risk Management");
_allowLongEntry = Param(nameof(AllowLongEntry), true)
.SetDisplay("Allow Long Entry", "Permission to open long positions", "Strategy");
_allowShortEntry = Param(nameof(AllowShortEntry), true)
.SetDisplay("Allow Short Entry", "Permission to open short positions", "Strategy");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Allow Long Exit", "Permission to close long positions", "Strategy");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Allow Short Exit", "Permission to close short positions", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_minAbsCmo = Param(nameof(MinAbsCmo), 5m)
.SetDisplay("Minimum CMO", "Minimum absolute CMO value required after a zero cross", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cmo = null!;
_prevCmo = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cmo = new ChandeMomentumOscillator { Length = CmoPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_cmo, ProcessCandle).Start();
StartProtection(
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Absolute));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _cmo);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cmoValue)
{
if (candle.State != CandleStates.Finished || _cmo == null || !_cmo.IsFormed)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var prev = _prevCmo;
_prevCmo = cmoValue;
if (prev == null || _cooldownRemaining > 0)
return;
var crossUp = prev < 0m && cmoValue > 0m && Math.Abs(cmoValue) >= MinAbsCmo;
var crossDown = prev > 0m && cmoValue < 0m && Math.Abs(cmoValue) >= MinAbsCmo;
if (crossUp)
{
if (AllowShortExit && Position < 0)
BuyMarket();
if (AllowLongEntry && Position <= 0)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (crossDown)
{
if (AllowLongExit && Position > 0)
SellMarket();
if (AllowShortEntry && Position >= 0)
{
SellMarket();
_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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ChandeMomentumOscillator
from StockSharp.Algo.Strategies import Strategy
class cmo_zero_cross_strategy(Strategy):
def __init__(self):
super(cmo_zero_cross_strategy, self).__init__()
self._cmo_period = self.Param("CmoPeriod", 14) \
.SetDisplay("CMO Period", "Period for Chande Momentum Oscillator", "Indicators")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss (pt)", "Stop loss in points", "Risk Management")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit (pt)", "Take profit in points", "Risk Management")
self._allow_long_entry = self.Param("AllowLongEntry", True) \
.SetDisplay("Allow Long Entry", "Permission to open long positions", "Strategy")
self._allow_short_entry = self.Param("AllowShortEntry", True) \
.SetDisplay("Allow Short Entry", "Permission to open short positions", "Strategy")
self._allow_long_exit = self.Param("AllowLongExit", True) \
.SetDisplay("Allow Long Exit", "Permission to close long positions", "Strategy")
self._allow_short_exit = self.Param("AllowShortExit", True) \
.SetDisplay("Allow Short Exit", "Permission to close short positions", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for calculations", "General")
self._min_abs_cmo = self.Param("MinAbsCmo", 5.0) \
.SetDisplay("Minimum CMO", "Minimum absolute CMO value required after a zero cross", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading")
self._prev_cmo = None
self._cooldown_remaining = 0
@property
def cmo_period(self):
return self._cmo_period.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def allow_long_entry(self):
return self._allow_long_entry.Value
@property
def allow_short_entry(self):
return self._allow_short_entry.Value
@property
def allow_long_exit(self):
return self._allow_long_exit.Value
@property
def allow_short_exit(self):
return self._allow_short_exit.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def min_abs_cmo(self):
return self._min_abs_cmo.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(cmo_zero_cross_strategy, self).OnReseted()
self._prev_cmo = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(cmo_zero_cross_strategy, self).OnStarted2(time)
cmo = ChandeMomentumOscillator()
cmo.Length = self.cmo_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(cmo, self.process_candle).Start()
self.StartProtection(
Unit(float(self.take_profit), UnitTypes.Absolute),
Unit(float(self.stop_loss), UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cmo)
self.DrawOwnTrades(area)
def process_candle(self, candle, cmo_value):
if candle.State != CandleStates.Finished:
return
cmo_value = float(cmo_value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
prev = self._prev_cmo
self._prev_cmo = cmo_value
if prev is None or self._cooldown_remaining > 0:
return
min_cmo = float(self.min_abs_cmo)
cross_up = prev < 0 and cmo_value > 0 and abs(cmo_value) >= min_cmo
cross_down = prev > 0 and cmo_value < 0 and abs(cmo_value) >= min_cmo
if cross_up:
if self.allow_short_exit and self.Position < 0:
self.BuyMarket()
if self.allow_long_entry and self.Position <= 0:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif cross_down:
if self.allow_long_exit and self.Position > 0:
self.SellMarket()
if self.allow_short_entry and self.Position >= 0:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
def CreateClone(self):
return cmo_zero_cross_strategy()