Эта стратегия — перенос на StockSharp эксперта MetaTrader «iCHO Trend CCIDualOnMA Filter». Она объединяет фильтр по нулевой линии осциллятора Chaikin с подтверждением двух индикаторов Commodity Channel Index (CCI), вычисляемых на сглаженной цене. Такой подход позволяет работать по тренду, но при этом требовать согласованного импульса от пары CCI перед входом в сделку.
Логика работы
Основа Chaikin. Линия накопления/распределения сглаживается двумя настраиваемыми скользящими средними. Их разность воспроизводит осциллятор Chaikin. Пересечения нулевой линии показывают смену доминирующего потока капитала.
Двойной CCI. Оба индикатора CCI используют одну и ту же сглаженную цену, но с разными периодами. Для лонга быстрый CCI должен выйти из отрицательной зоны и пересечь медленный сверху при положительном значении Chaikin. Для шорта условия зеркальны.
Инверсия сигналов. Как и в оригинале, флаг «Reverse» меняет местами условия лонга и шорта, что позволяет тестировать контртрендовый режим.
Управление позицией. Дополнительные настройки закрывают противоположные позиции перед входом и ограничивают стратегию одной открытой позицией. Правило «не более одной сделки на бар» соответствует реализации в MetaTrader.
Торговая сессия. Сделки могут ограничиваться внутридневным окном, включая периоды, переходящие через полуночь.
Параметры
Параметр
Описание
FastChaikinLength
Период быстрой средней внутри осциллятора Chaikin.
SlowChaikinLength
Период медленной средней внутри осциллятора Chaikin.
ChaikinMethod
Тип скользящей средней (Simple, Exponential, Smoothed, LinearWeighted) для линии накопления/распределения.
FastCciLength
Период быстрого CCI.
SlowCciLength
Период медленного CCI.
MaLength
Длина предварительной скользящей средней, подающей данные в CCI.
MaMethod
Тип скользящей средней для предварительного сглаживания цены.
MaPrice
Используемый тип цены (close, open, high, low, median, typical, weighted).
UseClosedBar
Обрабатывать только полностью сформированные свечи (аналог SignalsBarCurrent = bar_1).
ReverseSignals
Инвертировать лонговые и шортовые условия.
CloseOpposite
Закрывать встречную позицию перед входом.
OnlyOnePosition
Разрешать только одну открытую позицию.
TradeMode
Ограничение направлений торговли (BuyOnly, SellOnly, BuyAndSell).
UseTimeFilter
Включить фильтр торговой сессии.
StartHour, StartMinute, EndHour, EndMinute
Границы торгового окна в биржевом времени (начало включительно, конец исключительно), поддерживаются ночные переходы.
CandleType
Таймфрейм свечей, подаваемых в индикаторы.
Дополнительно
Стратегия работает только через высокоуровневый SubscribeCandles и штатные индикаторы — дополнительные буферы не требуются.
Сглаживание цены перед расчётом CCI повторяет схему оригинального индикатора CCIDualOnMA.
Значения по умолчанию соответствуют исходному советнику: Chaikin EMA 3/10, CCI 14 и 50, сглаживание SMA(12) и торговое окно 10:01–15:02.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Combines a Chaikin oscillator zero-line filter with CCI confirmation.
/// Buys when Chaikin crosses above zero and CCI is rising, sells on opposite.
/// </summary>
public class iCHO_Trend_CCIDualOnMA_FilterStrategy : Strategy
{
private readonly StrategyParam<int> _cciLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _cciLevel;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal? _prevCci;
private decimal? _prevPrevCci;
private int _candlesSinceTrade;
public int CciLength
{
get => _cciLength.Value;
set => _cciLength.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal CciLevel
{
get => _cciLevel.Value;
set => _cciLevel.Value = value;
}
public int SignalCooldownCandles
{
get => _signalCooldownCandles.Value;
set => _signalCooldownCandles.Value = value;
}
public iCHO_Trend_CCIDualOnMA_FilterStrategy()
{
_cciLength = Param(nameof(CciLength), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Length", "CCI period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "Data");
_cciLevel = Param(nameof(CciLevel), 100m)
.SetDisplay("CCI Level", "CCI threshold for entry", "Indicators");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = null;
_prevPrevCci = null;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
_prevPrevCci = null;
_candlesSinceTrade = SignalCooldownCandles;
var cci = new CommodityChannelIndex { Length = CciLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_prevCci.HasValue && _prevPrevCci.HasValue)
{
if (_prevPrevCci.Value < -CciLevel && _prevCci.Value < -CciLevel && cciValue > -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (_prevPrevCci.Value > CciLevel && _prevCci.Value > CciLevel && cciValue < CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
_prevPrevCci = _prevCci;
_prevCci = cciValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class i_cho__trend_cci_dual_on_ma__filter_strategy(Strategy):
def __init__(self):
super(i_cho__trend_cci_dual_on_ma__filter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._cci_length = self.Param("CciLength", 14)
self._cci_level = self.Param("CciLevel", 100.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_cci = 0.0
self._prev_prev_cci = 0.0
self._candles_since_trade = 4
self._has_two = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciLength(self):
return self._cci_length.Value
@CciLength.setter
def CciLength(self, value):
self._cci_length.Value = value
@property
def CciLevel(self):
return self._cci_level.Value
@CciLevel.setter
def CciLevel(self, value):
self._cci_level.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(i_cho__trend_cci_dual_on_ma__filter_strategy, self).OnReseted()
self._prev_cci = 0.0
self._prev_prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_two = False
def OnStarted2(self, time):
super(i_cho__trend_cci_dual_on_ma__filter_strategy, self).OnStarted2(time)
self._prev_cci = 0.0
self._prev_prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_two = False
cci = CommodityChannelIndex()
cci.Length = self.CciLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
cci_val = float(cci_value)
level = float(self.CciLevel)
if self._has_two:
# Both prev_prev and prev were below -level, now crossing above -level
if self._prev_prev_cci < -level and self._prev_cci < -level and cci_val > -level and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
# Both prev_prev and prev were above +level, now crossing below +level
elif self._prev_prev_cci > level and self._prev_cci > level and cci_val < level and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_prev_cci = self._prev_cci
self._prev_cci = cci_val
if not self._has_two:
if self._prev_prev_cci != 0.0 or self._prev_cci != 0.0:
self._has_two = True
def CreateClone(self):
return i_cho__trend_cci_dual_on_ma__filter_strategy()