PriceChannel Signal v2 es un sistema de seguimiento de tendencias creado alrededor de un canal Donchian modificado. El asesor experto original MQL5 observa las transiciones en la tendencia del canal, las condiciones de reingreso opcionales cuando el precio retrocede a través de las bandas y los niveles de salida protectores derivados del mismo rango. El puerto StockSharp mantiene el comportamiento original: negocia una sola posición a la vez, reacciona solo ante velas completadas y puede restringirse a una ventana intradiaria.
Lógica comercial
El nivel alto y bajo del canal Donchian se calcula sobre el ChannelPeriod configurado.
El rango bruto se desplaza mediante dos multiplicadores:
Factor de Riesgo – comprime las bandas de entrada hacia la mediana del canal.
Nivel de salida: construye un segundo par de bandas internas que activan las salidas.
Se mantiene un estado de tendencia:
Cuando el cierre supera la banda de entrada superior, la tendencia se vuelve alcista.
Cuando el cierre cae por debajo de la banda de entrada inferior, la tendencia se vuelve bajista.
En caso contrario se mantiene la tendencia anterior.
Señales generadas a partir de ese estado:
Entrada larga – la tendencia cambia de bajista a alcista.
Entrada corta – la tendencia cambia de alcista a bajista.
Reentrada larga – opcional, el precio cierra nuevamente por encima de la banda superior mientras la tendencia ya es alcista.
Reentrada corta – opcional, el precio cierra nuevamente por debajo de la banda inferior mientras la tendencia ya es bajista.
Salida larga – opcional, el precio cierra por debajo de la banda de salida alcista después de estar por encima de ella en la barra anterior.
Salida corta – opcional, el precio cierra por encima de la banda de salida bajista después de estar por debajo de ella en la barra anterior.
Sólo se permite un pedido por barra y por dirección. La estrategia se niega a abrir una nueva posición si ya hay otra activa.
Si el filtro de tiempo intradiario está habilitado, todas las señales anteriores se ignoran fuera de la ventana configurada.
Parámetros
Parámetro
Descripción
ChannelPeriod
Donchian longitud retrospectiva utilizada para calcular el canal de precios y las bandas de salida.
RiskFactor
Desplazamiento de las bandas de entrada (0–10). Los valores más bajos ensanchan las bandas, los valores más altos las estrechan.
ExitLevel
Desplazamiento de las bandas de salida. Debe ser mayor que RiskFactor para permanecer dentro del rango de entrada.
UseReEntry
Permite operaciones de reingreso cuando el precio retrocede a través de la banda activa.
UseExitSignals
Habilita la lógica de salida basada en las bandas protectoras internas.
CandleType
Marco de tiempo utilizado para construir velas y ejecutar los indicadores.
UseTimeControl
Alterna la ventana de negociación intradía.
StartHour / StartMinute
Inicio inclusivo de la ventana de negociación cuando el control de tiempo está activo.
EndHour / EndMinute
Fin exclusivo de la ventana de negociación cuando el control de tiempo está activo.
Reglas de entrada y salida.
Entrar en largo: la tendencia cambia a alcista o se activa la condición de reingreso, la posición actual es plana y la barra está dentro de la ventana de tiempo permitida.
Entrar en corto: la tendencia cambia a bajista o se dispara una condición de reingreso en corto, la posición actual es plana y la barra está dentro de la ventana de tiempo permitida.
Salida larga:UseExitSignals está habilitado y el cierre cae por debajo de la banda de salida después de estar por encima de ella en la barra anterior.
Salida corta:UseExitSignals está habilitado y el cierre se eleva por encima de la banda de salida después de estar por debajo de ella en la barra anterior.
Notas adicionales
La estrategia funciona con órdenes de mercado y no piramidal.
Los valores del indicador se procesan únicamente en velas terminadas para evitar el repintado dentro de la barra.
El volumen predeterminado es 1 contrato si no se proporciona explícitamente.
El control de tiempo sigue el comportamiento original de EA: la hora de finalización es exclusiva y se admite el ajuste hasta la medianoche.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Price Channel Signal v2 strategy that reacts to Donchian channel breakouts.
/// </summary>
public class PriceChannelSignalV2Strategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highHistory = new();
private readonly Queue<decimal> _lowHistory = new();
private int _previousTrend;
private decimal? _previousClose;
/// <summary>
/// Channel lookback length.
/// </summary>
public int ChannelPeriod
{
get => _channelPeriod.Value;
set => _channelPeriod.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize a new instance of <see cref="PriceChannelSignalV2Strategy"/>.
/// </summary>
public PriceChannelSignalV2Strategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Channel Period", "Donchian lookback used for Price Channel", "Price Channel");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for Price Channel", "General");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousTrend = 0;
_previousClose = null;
_highHistory.Clear();
_lowHistory.Clear();
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;
if (_highHistory.Count < ChannelPeriod)
{
EnqueueCandle(candle);
return;
}
var highs = _highHistory.ToArray();
var lows = _lowHistory.ToArray();
var channelHigh = GetMax(highs);
var channelLow = GetMin(lows);
var range = channelHigh - channelLow;
if (range <= 0m)
{
_previousClose = candle.ClosePrice;
EnqueueCandle(candle);
return;
}
var mid = (channelHigh + channelLow) / 2m;
// Update trend state based on channel breakout
var trend = _previousTrend;
if (candle.ClosePrice > channelHigh + range * 0.05m)
trend = 1;
else if (candle.ClosePrice < channelLow - range * 0.05m)
trend = -1;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Trend reversal signals
var changedPosition = false;
if (trend > 0 && _previousTrend <= 0)
{
if (Position <= 0)
{
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
changedPosition = true;
}
}
else if (trend < 0 && _previousTrend >= 0)
{
if (Position >= 0)
{
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
changedPosition = true;
}
}
// Exit on mid-line cross
if (!changedPosition && Position > 0 && _previousClose is decimal pc1 && pc1 >= mid && candle.ClosePrice < mid)
{
SellMarket(Math.Abs(Position));
}
else if (!changedPosition && Position < 0 && _previousClose is decimal pc2 && pc2 <= mid && candle.ClosePrice > mid)
{
BuyMarket(Math.Abs(Position));
}
_previousTrend = trend;
_previousClose = candle.ClosePrice;
EnqueueCandle(candle);
}
private void EnqueueCandle(ICandleMessage candle)
{
_highHistory.Enqueue(candle.HighPrice);
_lowHistory.Enqueue(candle.LowPrice);
while (_highHistory.Count > ChannelPeriod)
_highHistory.Dequeue();
while (_lowHistory.Count > ChannelPeriod)
_lowHistory.Dequeue();
}
private static decimal GetMax(IEnumerable<decimal> values)
{
var max = decimal.MinValue;
foreach (var value in values)
{
if (value > max)
max = value;
}
return max;
}
private static decimal GetMin(IEnumerable<decimal> values)
{
var min = decimal.MaxValue;
foreach (var value in values)
{
if (value < min)
min = value;
}
return min;
}
/// <inheritdoc />
protected override void OnReseted()
{
_previousTrend = 0;
_previousClose = null;
_highHistory.Clear();
_lowHistory.Clear();
base.OnReseted();
}
}
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.Strategies import Strategy
class price_channel_signal_v2_strategy(Strategy):
def __init__(self):
super(price_channel_signal_v2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._channel_period = self.Param("ChannelPeriod", 20)
self._high_history = []
self._low_history = []
self._prev_trend = 0
self._prev_close = 0.0
self._has_prev_close = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def ChannelPeriod(self):
return self._channel_period.Value
@ChannelPeriod.setter
def ChannelPeriod(self, value):
self._channel_period.Value = value
def OnReseted(self):
super(price_channel_signal_v2_strategy, self).OnReseted()
self._high_history = []
self._low_history = []
self._prev_trend = 0
self._prev_close = 0.0
self._has_prev_close = False
def OnStarted2(self, time):
super(price_channel_signal_v2_strategy, self).OnStarted2(time)
self._high_history = []
self._low_history = []
self._prev_trend = 0
self._prev_close = 0.0
self._has_prev_close = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
period = self.ChannelPeriod
if len(self._high_history) < period:
self._high_history.append(high)
self._low_history.append(low)
self._prev_close = close
self._has_prev_close = True
return
channel_high = max(self._high_history)
channel_low = min(self._low_history)
ch_range = channel_high - channel_low
if ch_range <= 0:
self._prev_close = close
self._high_history.append(high)
self._low_history.append(low)
while len(self._high_history) > period:
self._high_history.pop(0)
while len(self._low_history) > period:
self._low_history.pop(0)
return
mid = (channel_high + channel_low) / 2.0
trend = self._prev_trend
if close > channel_high + ch_range * 0.05:
trend = 1
elif close < channel_low - ch_range * 0.05:
trend = -1
changed_position = False
if trend > 0 and self._prev_trend <= 0:
if self.Position <= 0:
self.BuyMarket()
changed_position = True
elif trend < 0 and self._prev_trend >= 0:
if self.Position >= 0:
self.SellMarket()
changed_position = True
# Exit on mid-line cross
if not changed_position and self._has_prev_close:
if self.Position > 0 and self._prev_close >= mid and close < mid:
self.SellMarket()
elif self.Position < 0 and self._prev_close <= mid and close > mid:
self.BuyMarket()
self._prev_trend = trend
self._prev_close = close
self._has_prev_close = True
self._high_history.append(high)
self._low_history.append(low)
while len(self._high_history) > period:
self._high_history.pop(0)
while len(self._low_history) > period:
self._low_history.pop(0)
def CreateClone(self):
return price_channel_signal_v2_strategy()