A Flat Channel Strategy é uma tradução em C# do assessor especialista MetaTrader 5 Flat Channel (edição de barabashkakvn). Mantém o fluxo de trabalho original: um desvio padrão suavizado destaca compressões de volatilidade, os preços mais altos e mais baixos dentro da compressão definem um canal horizontal, e ordens de stop pendentes são colocadas logo fora desse intervalo. Quando o mercado rompe, a estratégia acompanha o movimento com níveis de stop-loss e take-profit predefinidos e pode opcionalmente trailear o stop à medida que a posição ganha lucro.
Como funciona
Detecção de compressão de volatilidade – Um indicador StandardDeviation com comprimento StdDevPeriod é suavizado por uma curta SimpleMovingAverage de SmoothingLength. Sempre que a série suavizada imprime FlatBars valores consecutivos não crescentes, o mercado é tratado como flat e as flags das ordens são rearmadas.
Construção do canal – Uma vez confirmado um flat, a estratégia solicita a máxima mais alta e a mínima mais baixa nas últimas max(ChannelLookback, FlatBars + 1) velas usando os indicadores integrados Highest/Lowest. A altura do canal é filtrada por ChannelMinPips/ChannelMaxPips após converter pips em unidades de preço através de PipSize (ou o tamanho do tick detectado quando o parâmetro é deixado em zero).
Ordens pendentes – Se a posição atual é flat e o trading é permitido, a estratégia submete uma ordem de compra stop em high + IndentPips e uma ordem de venda stop em low − IndentPips. Cada ordem lembra os níveis protetores calculados no momento do envio.
Execução do Rompimento – Quando uma ordem pendente é preenchida, a ordem pendente oposta é cancelada automaticamente. O preço preenchido torna-se a âncora de entrada para a lógica de trailing stop e as distâncias de stop-loss/take-profit memorizadas são ativadas.
Gestão de posição – A posição ativa é supervisionada em cada vela completada. Se o preço tocar o nível de stop-loss ou take-profit, a estratégia emite uma saída de mercado. Quando TrailingStopPips é maior que zero, o stop é avançado uma vez que o preço de fechamento se move pelo menos TrailingStopPips + TrailingStepPips a partir do preço de preenchimento.
Filtro de sessão – Quando UseTradingHours está habilitado, a lógica de rompimento só funciona entre StartHour (inclusive) e EndHour (exclusivo). Sessões noturnas são suportadas permitindo StartHour > EndHour.
Gestão de risco
Proteção dinâmica ou fixa – Configure StopLossPips / TakeProfitPips em valores positivos para usar distâncias fixas (em pips). Mantê-los em zero muda para dimensionamento dinâmico baseado na altura do canal e nos coeficientes DynamicStopMultiplier / DynamicTakeMultiplier.
Trailing stop – Habilite TrailingStopPips para seguir o movimento assim que a negociação estiver no lucro. A lógica de trailing respeita TrailingStepPips para evitar microajustes.
Limite de posição – MaxPositions limita a exposição agregada a MaxPositions × TradeVolume. Se esse limiar for atingido, novas ordens pendentes não são submetidas até que a exposição diminua.
Filtros direcionais – UseBuy e UseSell permitem que a estratégia opere em modos apenas-Rompimento, apenas-Rompimento descendente ou bidirecional.
Parâmetros
Parâmetro
Padrão
Descrição
TradeVolume
1
Volume submetido com cada ordem pendente.
PipSize
0.0001
Substituição manual do tamanho do pip. Deixar em zero para usar o tamanho do tick do instrumento (com ajuste automático de 3/5 dígitos).
StdDevPeriod
46
Lookback para o StandardDeviation base.
SmoothingLength
3
Comprimento da média móvel aplicado à série de volatilidade.
FlatBars
3
Número de valores de volatilidade suavizada consecutivos não crescentes necessários para rearmar as ordens de rompimento.
ChannelLookback
5
Velas usadas para medir a máxima mais alta e a mínima mais baixa após um flat ser detectado. Comparado automaticamente com FlatBars + 1.
ChannelMinPips
15
Altura mínima do canal (em pips). Defina como 0 para desabilitar o limite inferior.
ChannelMaxPips
105
Altura máxima do canal (em pips). Defina como 0 para desabilitar o limite superior.
DynamicStopMultiplier
1
Multiplicador de altura do canal para cálculo dinâmico de stop-loss quando StopLossPips = 0.
DynamicTakeMultiplier
1
Multiplicador de altura do canal para cálculo dinâmico de take-profit quando TakeProfitPips = 0.
StopLossPips
0
Distância fixa de stop-loss em pips. Substitui a fórmula dinâmica quando positivo.
TakeProfitPips
0
Distância fixa de take-profit em pips. Substitui a fórmula dinâmica quando positivo.
IndentPips
0
Deslocamento adicional (em pips) além dos limites do canal para ordens pendentes.
TrailingStopPips
5
Distância do trailing stop em pips. Defina como 0 para desabilitar o trailing.
TrailingStepPips
5
Passo mínimo (em pips) necessário para mover o trailing stop.
UseBuy
true
Habilitar rompimentos longos (ordem de compra stop).
UseSell
true
Habilitar rompimentos curtos (ordem de venda stop).
MaxPositions
5
Número máximo de volumes base permitidos na posição agregada.
UseTradingHours
true
Habilitar o filtro de sessão de trading.
StartHour
0
Hora de início da sessão (inclusive).
EndHour
23
Hora de fim da sessão (exclusivo).
CandleType
H1
Série de velas usada para cálculos (padrão: período de 1 hora).
Notas
A estratégia opera exclusivamente em velas completadas através da API de alto nível SubscribeCandles().Bind(...), correspondendo ao comportamento determinista esperado do EA original.
Os preços protetores são normalizados através de Security.ShrinkPrice para respeitar os tamanhos de tick da bolsa.
Quando ambas as ordens pendentes estão ativas e uma delas é preenchida, a ordem oposta é cancelada imediatamente para que apenas uma posição de rompimento possa estar aberta de cada vez.
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;
/// <summary>
/// Flat Channel Breakout strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class FlatChannelBreakoutStrategy : 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 FlatChannelBreakoutStrategy()
{
_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 flat_channel_breakout_strategy(Strategy):
def __init__(self):
super(flat_channel_breakout_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(flat_channel_breakout_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(flat_channel_breakout_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 flat_channel_breakout_strategy()