Стратегия пересечения нуля CMO
Стратегия торгует по пересечениям нулевой линии индикатора Chande Momentum Oscillator (CMO). Когда осциллятор пересекает ноль сверху вниз, открывается длинная позиция. При пересечении снизу вверх открывается короткая позиция. Опциональные уровни стоп-лосса и тейк-профита (в пунктах) защищают позицию. Возможность открытия и закрытия длинных и коротких позиций может быть включена или отключена отдельно.
Параметры
Volume– объём заявки.CmoPeriod– период индикатора CMO.StopLoss– стоп-лосс в пунктах.TakeProfit– тейк-профит в пунктах.AllowLongEntry– разрешение на открытие длинных позиций.AllowShortEntry– разрешение на открытие коротких позиций.AllowLongExit– разрешение на закрытие длинных позиций при противоположном сигнале.AllowShortExit– разрешение на закрытие коротких позиций при противоположном сигнале.CandleType– таймфрейм, используемый для расчётов.
Логика торговли
- Подписка на свечи выбранного таймфрейма и расчёт CMO.
- При пересечении CMO сверху вниз:
- Закрываются короткие позиции, если разрешено.
- Открывается длинная позиция, если разрешено.
- При пересечении CMO снизу вверх:
- Закрываются длинные позиции, если разрешено.
- Открывается короткая позиция, если разрешено.
- Стоп-лосс и тейк-профит устанавливаются как защитные ордера в пунктах.
Примечания
- Торговые решения принимаются только по завершённым свечам.
- Стратегия использует высокоуровневый API StockSharp и связывает индикаторы через
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()