CoensioTrader1 V06 es una estrategia de seguimiento de tendencia con rompimiento, distribuida originalmente como un Expert Advisor de MetaTrader. La versión para StockSharp conserva la lógica de reconocimiento de patrones discrecional y elimina las funciones específicas del broker y de internet presentes en la implementación MQL. La estrategia opera sobre un único instrumento y marco temporal, utilizando Bollinger Bands y una media móvil exponencial doble (DEMA) para identificar movimientos de agotamiento seguidos de reanudación de tendencia.
El robot original permitía operar con hasta seis pares de divisas con conjuntos de parámetros individuales, admitía licencias basadas en DLL y reportaba resultados de optimización a un servidor remoto. Esos servicios auxiliares se omiten intencionalmente en esta versión. El foco está en el flujo central de entradas y salidas que reacciona a los rechazos de las Bollinger Bands confirmados por la estructura de swings y la pendiente del DEMA.
Lógica de la estrategia
Suscripción a datos – la estrategia se suscribe al tipo de vela configurado (por defecto: 1 hora) y vincula Bollinger Bands junto con un DEMA.
Rechazo de Bollinger Bands – las señales se evalúan en la última vela completamente cerrada.
Configuración Largo
La vela abrió por debajo de la Bollinger Band inferior anterior y cerró de nuevo por encima de ella (ruptura fallida hacia abajo).
La vela creó un mínimo más alto comparado con la barra anterior, mientras que esa barra anterior hizo un mínimo más bajo comparado con su predecesora (estructura tipo doble suelo).
El DEMA sube estrictamente a lo largo de las últimas tres observaciones (valor actual > anterior > segundo anterior).
Configuración Corto
La vela abrió por encima de la Bollinger Band superior anterior y cerró de nuevo por debajo de ella (ruptura fallida hacia arriba).
La vela hizo un máximo más bajo comparado con la barra anterior, mientras que esa barra anterior hizo un máximo más alto comparado con su predecesora (estructura de doble techo).
El DEMA cae estrictamente a lo largo de las últimas tres observaciones.
Ejecución de órdenes – las órdenes de mercado se envían inmediatamente después de que la señal se confirma en una vela terminada. Se puede habilitar el aplanamiento opcional de posición en señales opuestas.
Gestión de riesgos – las distancias opcionales de stop-loss y take-profit se proporcionan a través de StartProtection. Ambas son compensaciones de precio absoluto; la funcionalidad de trailing stop del expert original no se reproduce.
Parámetros
Nombre
Descripción
Por defecto
BollingerPeriod
Período para el cálculo de las Bollinger Bands.
30
BollingerDeviation
Multiplicador de desviación estándar para las bandas.
1.5
DemaPeriod
Longitud de la media móvil exponencial doble utilizada para confirmación de tendencia.
20
StopLossDistance
Compensación absoluta de stop-loss pasada a StartProtection. Establecer en cero para deshabilitar.
0 (absolute)
TakeProfitDistance
Compensación absoluta de take-profit pasada a StartProtection. Establecer en cero para deshabilitar.
0 (absolute)
CloseOnSignal
Cerrar la posición actual antes de abrir una nueva en la dirección opuesta.
false
CandleType
Tipo de datos de vela o marco temporal. Por defecto es 1 hora.
1h
Notas de uso
La versión de StockSharp opera únicamente en el Strategy.Security principal. Para imitar el comportamiento multi-símbolo del expert original, lance instancias de estrategia separadas con conjuntos de parámetros distintos.
La lógica de dimensionamiento de lotes MQL (RiskMax, LotSize, LotBalanceDivider) no fue traducida. Configure Volume en la estrategia o mediante el gestor de riesgos según las reglas de su cartera.
La activación basada en DLL, el registro remoto de optimización y las rutinas de dibujo UI presentes en los archivos MQL fueron eliminadas intencionalmente.
Los valores de stop-loss y take-profit son distancias de precio absolutas. Adáptelos al tamaño de tick o valor de pip del instrumento al configurar la estrategia.
El mecanismo de paso de trailing-stop original no está implementado. Si se requiere gestión de trailing, añada un módulo de riesgo dedicado sobre esta estrategia.
Todos los comentarios del código y las lógicas se mantienen en inglés según lo solicitado; las traducciones del README se proporcionan por separado.
Diferencias con la versión MQL
Gestión multi-símbolo: reemplazada con un diseño de instrumento único para mayor claridad y facilidad de prueba.
Redes y licencias: eliminadas; no se realizan solicitudes HTTP externas ni llamadas a DLL.
Dimensionamiento de órdenes: simplificado para depender del manejo estándar de Volume de StockSharp.
Objetos visuales: las anotaciones de gráfico de MetaTrader (flechas, etiquetas, temas de color) no se recrean. Use los helpers de gráficos de StockSharp si se requiere visualización.
Trailing stop: no portado; solo se configuran las órdenes protectoras iniciales.
Esta documentación pretende ser exhaustiva para que la versión portada pueda ser revisada, probada y extendida sin necesidad de leer el código fuente MQL original.
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()