Estrategia de Cruce de Cero CMO
Esta estrategia opera basándose en los cruces de la línea cero del Oscilador de Momento Chande (CMO). Cuando el oscilador cruza por debajo de cero, se abre una posición larga. Cuando cruza por encima de cero, se abre una posición corta. Niveles opcionales de stop loss y take profit (en puntos) protegen la posición. Las entradas y salidas de operaciones largas y cortas pueden habilitarse o deshabilitarse individualmente.
Parámetros
Volume– volumen de la orden.CmoPeriod– período para el indicador CMO.StopLoss– stop loss en puntos.TakeProfit– take profit en puntos.AllowLongEntry– permitir apertura de posiciones largas.AllowShortEntry– permitir apertura de posiciones cortas.AllowLongExit– permitir cierre de posiciones largas ante señal opuesta.AllowShortExit– permitir cierre de posiciones cortas ante señal opuesta.CandleType– marco temporal utilizado para los cálculos.
Lógica de Trading
- Suscribirse a velas del marco temporal seleccionado y calcular el CMO.
- Cuando el CMO cruza de arriba hacia abajo de cero:
- Cerrar posiciones cortas si está permitido.
- Abrir una posición larga si está permitido.
- Cuando el CMO cruza de abajo hacia arriba de cero:
- Cerrar posiciones largas si está permitido.
- Abrir una posición corta si está permitido.
- Stop loss y take profit se aplican usando órdenes de protección en puntos.
Notas
- Las decisiones de trading se toman únicamente en velas completadas.
- La estrategia utiliza la API de alto nivel de StockSharp y enlaza indicadores a través de
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()