Estratégia de Cruzamento de Zero CMO
Esta estratégia opera com base nos cruzamentos da linha zero do Oscilador de Momento Chande (CMO). Quando o oscilador cruza abaixo de zero, uma posição comprada é aberta. Quando cruza acima de zero, uma posição vendida é aberta. Níveis opcionais de stop loss e take profit (em pontos) protegem a posição. As entradas e saídas para operações compradas e vendidas podem ser habilitadas ou desabilitadas individualmente.
Parâmetros
Volume– volume da ordem.CmoPeriod– período para o indicador CMO.StopLoss– stop loss em pontos.TakeProfit– take profit em pontos.AllowLongEntry– permitir abertura de posições compradas.AllowShortEntry– permitir abertura de posições vendidas.AllowLongExit– permitir fechamento de posições compradas com sinal oposto.AllowShortExit– permitir fechamento de posições vendidas com sinal oposto.CandleType– período utilizado para os cálculos.
Lógica de Negociação
- Subscrever candles do período selecionado e calcular o CMO.
- Quando o CMO cruza de cima para baixo de zero:
- Fechar posições vendidas se permitido.
- Abrir uma posição comprada se permitido.
- Quando o CMO cruza de baixo para cima de zero:
- Fechar posições compradas se permitido.
- Abrir uma posição vendida se permitido.
- Stop loss e take profit são aplicados usando ordens de proteção em pontos.
Observações
- As decisões de negociação são tomadas apenas em candles completos.
- A estratégia usa a API de alto nível do StockSharp e vincula indicadores atravé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()