CoensioTrader1 V06 — это эксперт MetaTrader, который ищет ложные пробои границ полос Боллинджера и подтверждает сигналы по структуре экстремумов и наклону DEMA. Перенос в StockSharp сохраняет ключевые торговые правила и отказывается от сетевых проверок лицензии, загрузки логов и другой инфраструктуры, присутствовавшей в MQL-коде. Реализация рассчитана на один инструмент и один таймфрейм, что упрощает тестирование и эксплуатацию.
Изначально советник позволял работать с шестью валютными парами, отправлял результаты оптимизации на сервер разработчика и рисовал на графике служебные объекты. В версии для StockSharp оставлена только торговая логика: формирование сигналов, открытие/закрытие позиций и базовое управление рисками через StartProtection.
Логика торговли
Подписка на данные — стратегия подписывается на выбранный тип свечей (по умолчанию 1 час) и привязывает к потоку свечей индикаторы Bollinger Bands и DEMA.
Анализ закрытой свечи — сигналы рассчитываются после появления новой полностью сформированной свечи.
Покупка
Свеча открылась ниже предыдущей нижней полосы Боллинджера и закрылась выше неё (ложный пробой вниз).
Минимум свечи выше минимума предыдущей свечи, а та, в свою очередь, обновила минимум ещё более ранней свечи (паттерн «двойное дно»).
Значение DEMA последовательно растёт в течение трёх отсчётов (текущий > предыдущего > предпредыдущего).
Продажа
Свеча открылась выше предыдущей верхней полосы Боллинджера и закрылась ниже неё (ложный пробой вверх).
Максимум свечи ниже максимума предыдущей свечи, при этом предыдущая свеча выше ещё более ранней (паттерн «двойная вершина»).
Значение DEMA последовательно снижается.
Исполнение сделок — при выполнении условий стратегия отправляет рыночные заявки. Опция CloseOnSignal позволяет закрыть противоположную позицию перед открытием новой.
Риск-менеджмент — используются опциональные уровни стоп-лосса и тейк-профита, задаваемые в абсолютных ценовых величинах. Шаговый трейлинг из оригинального MQL-скрипта не реализован.
Параметры
Параметр
Назначение
Значение по умолчанию
BollingerPeriod
Период расчёта полос Боллинджера.
30
BollingerDeviation
Множитель стандартного отклонения.
1.5
DemaPeriod
Период двойной экспоненциальной средней (DEMA).
20
StopLossDistance
Абсолютная дистанция стоп-лосса, передаваемая в StartProtection (0 отключает стоп).
0 (абсолют)
TakeProfitDistance
Абсолютная дистанция тейк-профита, передаваемая в StartProtection.
0 (абсолют)
CloseOnSignal
Закрывать ли текущую позицию при появлении обратного сигнала.
false
CandleType
Тип/таймфрейм свечей.
1 час
Практические замечания
Стратегия работает только с инструментом, указанным в Strategy.Security. Для обслуживания нескольких инструментов запустите несколько экземпляров с разными настройками.
Алгоритм расчёта объёма из MQL (RiskMax, LotSize, LotBalanceDivider) не переносился. Используйте свой риск-менеджмент и свойство Volume стратегии.
Весь код, связанный с DLL, сетевыми запросами и отрисовкой объектов на графике, удалён. Для визуализации используйте стандартные средства StockSharp.
Значения стопов задаются в абсолютных ценовых величинах, поэтому их нужно адаптировать под шаг цены конкретного инструмента.
Механизм пошагового трейлинга из оригинала отсутствует. При необходимости добавьте собственный модуль сопровождения позиции.
Комментарии в исходнике приведены на английском языке; к стратегии прилагаются детальные описания на английском, китайском и русском.
Отличия от MQL-версии
Мультивалютность: стратегия обслуживает только один инструмент, что облегчает контроль состояния.
Сетевая активность: в порте нет HTTP-запросов, файловой телеметрии и проверок лицензии.
Размер позиций: размер сделки определяется общими настройками StockSharp.
Графические элементы: стрелки и подписи с графика MetaTrader не воспроизводятся.
Трейлинг: только начальные стоп/тейк, без автоматического подтягивания.
Документация максимально подробна, чтобы упростить аудит и дальнейшее развитие стратегии без необходимости изучать исходный MQL-код.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the CoensioTrader1 V06 MQL strategy.
/// The strategy buys after a lower Bollinger Band rejection paired with a higher low pattern and bullish DEMA trend.
/// It sells after an upper Bollinger Band rejection with a lower high structure and bearish DEMA trend.
/// </summary>
public class CoensioTrader1V06Strategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _demaPeriod;
private readonly StrategyParam<Unit> _stopLossDistance;
private readonly StrategyParam<Unit> _takeProfitDistance;
private readonly StrategyParam<bool> _closeOnSignal;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevOpen;
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _prevClose;
private decimal? _prev2High;
private decimal? _prev2Low;
private decimal? _prev3High;
private decimal? _prev3Low;
private decimal? _prevUpperBand;
private decimal? _prevLowerBand;
private decimal? _prevDema;
private decimal? _prev2Dema;
/// <summary>
/// Initializes a new instance of the <see cref="CoensioTrader1V06Strategy"/> class.
/// </summary>
public CoensioTrader1V06Strategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Length of Bollinger Bands", "Indicators")
;
_bollingerDeviation = Param(nameof(BollingerDeviation), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier", "Indicators")
;
_demaPeriod = Param(nameof(DemaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("DEMA Period", "Length of double exponential moving average", "Indicators")
;
_stopLossDistance = Param(nameof(StopLossDistance), new Unit(0m, UnitTypes.Absolute))
.SetDisplay("Stop Loss", "Absolute stop loss offset from entry", "Risk");
_takeProfitDistance = Param(nameof(TakeProfitDistance), new Unit(0m, UnitTypes.Absolute))
.SetDisplay("Take Profit", "Absolute take profit offset from entry", "Risk");
_closeOnSignal = Param(nameof(CloseOnSignal), false)
.SetDisplay("Close On Opposite Signal", "Close current trades when opposite setup appears", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for signal calculations", "General");
Volume = 0.01m;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Double exponential moving average period.
/// </summary>
public int DemaPeriod
{
get => _demaPeriod.Value;
set => _demaPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance from entry price.
/// </summary>
public Unit StopLossDistance
{
get => _stopLossDistance.Value;
set => _stopLossDistance.Value = value;
}
/// <summary>
/// Take-profit distance from entry price.
/// </summary>
public Unit TakeProfitDistance
{
get => _takeProfitDistance.Value;
set => _takeProfitDistance.Value = value;
}
/// <summary>
/// Close current position when an opposite signal appears.
/// </summary>
public bool CloseOnSignal
{
get => _closeOnSignal.Value;
set => _closeOnSignal.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevOpen = null;
_prevHigh = null;
_prevLow = null;
_prevClose = null;
_prev2High = null;
_prev2Low = null;
_prev3High = null;
_prev3Low = null;
_prevUpperBand = null;
_prevLowerBand = null;
_prevDema = null;
_prev2Dema = null;
}
/// <inheritdoc />
private BollingerBands _bollinger;
private DoubleExponentialMovingAverage _dema;
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation
};
_dema = new DoubleExponentialMovingAverage
{
Length = DemaPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var bollingerResult = _bollinger.Process(candle);
var demaResult = _dema.Process(candle);
if (bollingerResult.IsEmpty || demaResult.IsEmpty || !_bollinger.IsFormed || !_dema.IsFormed)
{
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_prevLow = candle.LowPrice;
_prevHigh = candle.HighPrice;
return;
}
var bollingerValue = (BollingerBandsValue)bollingerResult;
var upper = bollingerValue.UpBand ?? 0m;
var lower = bollingerValue.LowBand ?? 0m;
var demaValue = demaResult.GetValue<decimal>();
if (_prevOpen.HasValue && _prevClose.HasValue && _prevLow.HasValue && _prevHigh.HasValue &&
_prevLowerBand.HasValue && _prevUpperBand.HasValue && _prevDema.HasValue)
{
// Long setup: lower band rejection with rising DEMA.
var crossedLowerBand = _prevLow.Value <= _prevLowerBand.Value && _prevClose.Value > _prevLowerBand.Value;
var bullishTrend = demaValue > _prevDema.Value;
if (crossedLowerBand && bullishTrend)
{
if (CloseOnSignal && Position < 0)
{
if (Position > 0) SellMarket(Position);
else if (Position < 0) BuyMarket(-Position);
}
if (Position <= 0)
BuyMarket();
}
// Short setup: upper band rejection with falling DEMA.
var crossedUpperBand = _prevHigh.Value >= _prevUpperBand.Value && _prevClose.Value < _prevUpperBand.Value;
var bearishTrend = demaValue < _prevDema.Value;
if (crossedUpperBand && bearishTrend)
{
if (CloseOnSignal && Position > 0)
{
if (Position > 0) SellMarket(Position);
else if (Position < 0) BuyMarket(-Position);
}
if (Position >= 0)
SellMarket();
}
}
_prev3Low = _prev2Low;
_prev3High = _prev2High;
_prev2Low = _prevLow;
_prev2High = _prevHigh;
_prevLowerBand = lower;
_prevUpperBand = upper;
_prev2Dema = _prevDema;
_prevDema = demaValue;
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_prevLow = candle.LowPrice;
_prevHigh = candle.HighPrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import BollingerBands, DoubleExponentialMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan
class coensio_trader1_v06_strategy(Strategy):
def __init__(self):
super(coensio_trader1_v06_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 30)
self._bollinger_deviation = self.Param("BollingerDeviation", 1.5)
self._dema_period = self.Param("DemaPeriod", 20)
self._close_on_signal = self.Param("CloseOnSignal", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_open = None
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._prev_upper_band = None
self._prev_lower_band = None
self._prev_dema = None
self._bollinger = None
self._dema = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(coensio_trader1_v06_strategy, self).OnStarted2(time)
self._bollinger = BollingerBands()
self._bollinger.Length = self._bollinger_period.Value
self._bollinger.Width = self._bollinger_deviation.Value
self._dema = DoubleExponentialMovingAverage()
self._dema.Length = self._dema_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ1 = CandleIndicatorValue(self._bollinger, candle)
civ1.IsFinal = True
bb_result = self._bollinger.Process(civ1)
civ2 = CandleIndicatorValue(self._dema, candle)
civ2.IsFinal = True
dema_result = self._dema.Process(civ2)
if bb_result.IsEmpty or dema_result.IsEmpty or not self._bollinger.IsFormed or not self._dema.IsFormed:
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_low = float(candle.LowPrice)
self._prev_high = float(candle.HighPrice)
return
upper = float(bb_result.UpBand) if bb_result.UpBand is not None else 0.0
lower = float(bb_result.LowBand) if bb_result.LowBand is not None else 0.0
dema_value = float(dema_result)
if (self._prev_open is not None and self._prev_close is not None and
self._prev_low is not None and self._prev_high is not None and
self._prev_lower_band is not None and self._prev_upper_band is not None and
self._prev_dema is not None):
crossed_lower = self._prev_low <= self._prev_lower_band and self._prev_close > self._prev_lower_band
bullish_trend = dema_value > self._prev_dema
if crossed_lower and bullish_trend:
if self._close_on_signal.Value and self.Position < 0:
self.BuyMarket(abs(self.Position))
if self.Position <= 0:
self.BuyMarket()
crossed_upper = self._prev_high >= self._prev_upper_band and self._prev_close < self._prev_upper_band
bearish_trend = dema_value < self._prev_dema
if crossed_upper and bearish_trend:
if self._close_on_signal.Value and self.Position > 0:
self.SellMarket(self.Position)
if self.Position >= 0:
self.SellMarket()
self._prev_lower_band = lower
self._prev_upper_band = upper
self._prev_dema = dema_value
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_low = float(candle.LowPrice)
self._prev_high = float(candle.HighPrice)
def OnReseted(self):
super(coensio_trader1_v06_strategy, self).OnReseted()
self._prev_open = None
self._prev_high = None
self._prev_low = None
self._prev_close = None
self._prev_upper_band = None
self._prev_lower_band = None
self._prev_dema = None
def CreateClone(self):
return coensio_trader1_v06_strategy()