La estrategia de canal equidistante adapta el asesor experto MQL4 original "Equidistant Channel" a la API de alto nivel de StockSharp. La estrategia analiza cruces de la línea MACD y gestiona posiciones existentes mediante toques de Bollinger Bands, lógica de breakeven y objetivos trailing basados en dinero.
Cuando la línea MACD cruza por encima de su señal, la estrategia abre posiciones largas; cuando cruza por debajo de la señal, abre posiciones cortas. Mientras una operación está activa, la estrategia vigila salidas cuando el precio alcanza Bollinger Bands, cuando la ganancia flotante llega a objetivos monetarios o porcentuales configurables, o cuando se infringe un umbral de drawdown trailing. Un modo breakeven replica la implementación de MetaTrader moviendo el stop de protección cuando la ganancia supera un número configurable de pasos de precio.
Indicadores
MACD (12, 26, 9) - genera señales de entrada en cruces entre la línea MACD y su línea de señal.
Bollinger Bands (20, 2) - proporcionan niveles de salida cuando el cierre de la vela toca la banda superior o inferior.
Gestión de posiciones
Distancias opcionales de stop loss, take profit y trailing stop expresadas en puntos de precio mediante StartProtection.
Lógica de take profit y trailing basada en dinero que rastrea la ganancia flotante usando metadatos de precio/tamaño de paso del instrumento.
Take profit porcentual calculado desde el valor inicial de la cartera.
Modo breakeven que empuja el stop a la entrada más un desplazamiento cuando la ganancia alcanza un disparador definido.
Parámetros
Grupo
Nombre
Predeterminado
Descripción
Trading
Volumen
1
Volumen de orden para nuevas entradas.
General
Tipo de vela
5 minutos
Serie de velas usada para los cálculos.
Indicadores
MACD rápido
12
Longitud de EMA rápida para MACD.
Indicadores
MACD lento
26
Longitud de EMA lenta para MACD.
Indicadores
Señal MACD
9
Longitud de la línea de señal para MACD.
Indicadores
Período BB
20
Período retrospectivo de Bollinger Bands.
Indicadores
Desviación BB
2
Anchura de Bollinger Bands en desviaciones estándar.
Riesgo
Stop Loss
20
Distancia del stop loss en puntos de precio.
Riesgo
Take Profit
50
Distancia del take profit en puntos de precio.
Riesgo
Trailing Stop
40
Distancia del trailing stop en puntos de precio.
Riesgo
Usar TP (dinero)
false
Cierra cuando la ganancia flotante alcanza un objetivo monetario absoluto.
Riesgo
Dinero TP
10
Valor absoluto de take profit en la divisa de la cuenta.
Riesgo
Usar TP (%)
false
Cierra cuando la ganancia flotante alcanza un porcentaje del capital inicial.
Riesgo
Porcentaje TP
10
Porcentaje del capital inicial para el take profit porcentual.
Riesgo
Habilitar trailing
true
Habilita la lógica trailing sobre la ganancia flotante.
Riesgo
Activar trailing
40
Nivel de ganancia (divisa) que arma la lógica trailing.
Riesgo
Paso trailing
10
Drawdown máximo permitido desde el pico de ganancia (divisa).
Riesgo
Usar stop BB
true
Habilita salidas cuando el precio toca Bollinger Bands.
Riesgo
Usar breakeven
true
Habilita el comportamiento breakeven.
Riesgo
Disparador breakeven
10
Ganancia (pasos de precio) necesaria para armar el stop breakeven.
Riesgo
Desplazamiento breakeven
5
Desplazamiento (pasos de precio) aplicado al nivel breakeven.
Notas
La estrategia funciona con un solo instrumento que proporcione metadatos válidos de PriceStep y StepPrice para que los cálculos monetarios sean precisos.
El módulo de trailing de ganancia sigue el comportamiento de MetaTrader: cuando la ganancia flotante supera el umbral de activación, la estrategia registra el máximo en curso y cierra la operación cuando el drawdown supera el paso trailing configurado.
La lógica breakeven replica el EA original usando disparadores y desplazamientos basados en pasos de precio.
Todos los comentarios dentro del código de la estrategia están escritos en inglés, como exigen las directrices del proyecto.
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;
public class EquidistantChannelStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public EquidistantChannelStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class equidistant_channel_strategy(Strategy):
def __init__(self):
super(equidistant_channel_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(equidistant_channel_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(equidistant_channel_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return equidistant_channel_strategy()