PriceChannel Signal v2 é um sistema de breakout de acompanhamento de tendências construído em torno de um canal Donchian modificado. O consultor especialista MQL5 original observa transições na tendência do canal, condições opcionais de reentrada quando o preço retrocede através das bandas e níveis de saída protetores derivados da mesma faixa. A porta StockSharp mantém o comportamento original: negocia uma única posição por vez, reage apenas a velas concluídas e pode ser restrita a uma janela intradiária.
Lógica de negociação
O alto e o baixo do canal Donchian são calculados sobre o ChannelPeriod configurado.
O intervalo bruto é alterado por dois multiplicadores:
Fator de Risco – comprime as bandas de entrada em direção à mediana do canal.
Nível de saída – cria um segundo par de faixas internas que acionam saídas.
Um estado de tendência é mantido:
Quando o fechamento ultrapassa a banda de entrada superior, a tendência torna-se de alta.
Quando o fechamento quebra abaixo da banda de entrada inferior, a tendência torna-se de baixa.
Caso contrário, a tendência anterior é mantida.
Sinais gerados a partir desse estado:
Entrada longa – a tendência muda de baixa para alta.
Entrada curta – a tendência muda de alta para baixa.
Reentrada longa – opcional, o preço fecha acima da banda superior enquanto a tendência já é de alta.
Reentrada curta – opcional, o preço fecha abaixo da banda inferior enquanto a tendência já é de baixa.
Saída longa – opcional, o preço fecha abaixo da banda de saída de alta após estar acima dela na barra anterior.
Saída curta – opcional, o preço fecha acima da banda de saída de baixa após ficar abaixo dela na barra anterior.
É permitido apenas um pedido por barra e por direção. A estratégia recusa-se a abrir uma nova posição se outra já estiver ativa.
Se o filtro intradiário estiver habilitado, todos os sinais acima serão ignorados fora da janela configurada.
Parâmetros
Parâmetro
Descrição
ChannelPeriod
Donchian comprimento de lookback usado para calcular o canal de preço e as faixas de saída.
RiskFactor
Mudança das bandas de entrada (0–10). Valores mais baixos ampliam as bandas, valores mais altos as estreitam.
ExitLevel
Mudança das bandas de saída. Deve ser maior que RiskFactor para permanecer dentro do intervalo de entrada.
UseReEntry
Permite negociações de reentrada quando o preço retrocede através da banda ativa.
UseExitSignals
Ativa a lógica de saída com base nas faixas de proteção internas.
CandleType
Prazo usado para construir velas e executar os indicadores.
UseTimeControl
Alterna a janela de negociação intradiária.
StartHour / StartMinute
Início inclusivo da janela de negociação quando o controle de tempo estiver ativo.
EndHour / EndMinute
Fim exclusivo da janela de negociação quando o controle de tempo estiver ativo.
Regras de entrada e saída
Enter long: a tendência muda para alta ou condições de reentrada disparam, a posição atual é plana e a barra está dentro da janela de tempo permitida.
Enter short: a tendência muda para baixa ou a condição de reentrada curta é disparada, a posição atual é plana e a barra está dentro da janela de tempo permitida.
Saída longa:UseExitSignals está habilitado e o fechamento fica abaixo da banda de saída após estar acima dela na barra anterior.
Saída curta:UseExitSignals está habilitado e o fechamento sobe acima da banda de saída depois de ficar abaixo dela na barra anterior.
Notas adicionais
A estratégia funciona com ordens de mercado e não faz pirâmide de posições.
Os valores dos indicadores são processados apenas em velas acabadas para evitar a repintura intrabarra.
O volume padrão é 1 contrato se não for fornecido explicitamente.
O controle de tempo segue o comportamento original EA: o horário de término é exclusivo e há suporte para agrupamento até a meia-noite.
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()