Esta estratégia porta o Constituents EA de MQL/22595 para a API de alto nível do StockSharp. Ela recria a lógica original
de colocar duas ordens pendentes em torno do intervalo mais recente em uma hora específica, mantendo o fluxo de trabalho
compatível com o manuseio de ordens e os helpers de proteção de risco do StockSharp.
Como a estratégia funciona
Ativação programada – ao final de cada candle a estratégia verifica se a próxima barra começará em StartHour. Apenas
nesse momento são consideradas novas ordens pendentes, o que replica o código do MetaTrader que reagia ao nascimento da barra
cujo tempo de abertura coincide com a hora configurada.
Detecção de intervalo – a maior máxima e a menor mínima entre os SearchDepth candles concluídos anteriores são
rastreadas com indicadores Highest/Lowest. Esses dois preços definem os níveis de ruptura/reversão à média usados para
colocação de ordens.
Filtros de distância de preço – as melhores cotações de bid/ask atuais são transmitidas do feed do livro de ordens. As
ordens são colocadas apenas se a distância entre a cotação e o preço candidato for maior ou igual a MinOrderDistancePips
(convertido para preço absoluto usando PointValue). Isso reimplementa a validação do nível de congelamento original e
evita ordens pendentes inválidas.
Seleção de estilo de ordem – PendingOrderMode escolhe entre ordens limitadas (buy limit na mínima, sell limit na
máxima) ou ordens stop (buy stop acima da máxima, sell stop abaixo da mínima). Ambas as ordens são enviadas
simultaneamente, assim como no script do MetaTrader.
Proteção de risco – o helper integrado StartProtection anexa níveis de stop-loss e take-profit expressos em passos de
preço absolutos (StopLossPips/TakeProfitPips). As verificações de distância mínima contra MinStopDistancePips
replicam o requisito do MT5 de que as ordens protetoras devem respeitar o nível de stop do símbolo.
Gerenciamento de ordens – se uma ordem pendente for executada, a ordem oposta é cancelada imediatamente. Durante o
intervalo da barra a estratégia nunca coloca ordens adicionais enquanto existem ordens ativas, correspondendo ao
comportamento do EA de origem.
Parâmetros
Parâmetro
Descrição
StartHour
Hora (0-23) quando o novo par de ordens pendentes é criado.
SearchDepth
Número de candles concluídos anteriores usados para calcular o intervalo máximo/mínimo.
PendingOrderMode
Limit replica a variante de reversão à média, Stop coloca ordens de ruptura.
StopLossPips
Distância de stop-loss medida em pips (convertida com PointValue). Definir como 0 para desabilitar.
TakeProfitPips
Distância de take-profit em pips. Definir como 0 para desabilitar.
PointValue
Valor do pip em unidades de preço. Definir como 0 para auto-detectar de Security.PriceStep/MinStep.
MinOrderDistancePips
Distância mínima permitida entre bid/ask atual e o preço pendente, modelando verificações de freeze-level.
MinStopDistancePips
Distância mínima permitida para stop/take, espelhando verificações de StopsLevel.
CandleType
Período usado para cálculo de intervalo e lógica de agendamento.
Strategy.Volume controla o tamanho da ordem; mantê-lo positivo para que BuyLimit, SellLimit, BuyStop e SellStop
possam enviar ordens.
Uso
Anexar a estratégia a um instrumento e definir CandleType para o período que se deseja operar.
Configurar StartHour e SearchDepth exatamente como nas entradas do MT5. Ajustar os limites Min*Pips se o broker
aplicar distâncias mínimas entre ordens e o preço de mercado.
Calibrar PointValue quando a auto-detecção dos metadados do instrumento não for possível (por exemplo, em instrumentos
sintéticos).
Definir StopLossPips e TakeProfitPips para corresponder ao EA original. O módulo de proteção anexará automaticamente
stops e alvos assim que uma ordem for executada.
Fornecer um Volume positivo e iniciar a estratégia. Ela se inscreverá em dados de candles e livro de ordens, colocará
ambas as ordens pendentes na barra agendada e cancelará a ordem oposta quando uma operação for executada.
Diferenças em relação ao EA original
O modo de risco MoneyFixedMargin do MetaTrader (dimensionamento baseado em porcentagem) não está portado. Os usuários do
StockSharp devem configurar Strategy.Volume diretamente ou envolver a estratégia com um módulo externo de dimensionamento
de posição.
As verificações de freeze-level e stop-level são expressas através dos parâmetros configuráveis MinOrderDistancePips e
MinStopDistancePips porque os metadados equivalentes da exchange nem sempre estão disponíveis através do StockSharp.
A colocação de ordens ocorre quando o candle anterior fecha e a próxima barra começa em StartHour. Isso é funcionalmente
idêntico à implementação do MT5 que era acionada no nascimento da nova barra.
Todos os comentários dentro do código fonte foram traduzidos para o inglês, enquanto a documentação externa está disponível
em vários idiomas por conveniência.
Ajustar as distâncias e a hora de trading para corresponder ao instrumento que se planeja operar. Em mercados com spreads
amplos pode ser necessário aumentar MinOrderDistancePips ou valores de pip para evitar rejeição imediata pelo broker.
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>
/// Constituents breakout strategy converted from the original MetaTrader expert advisor.
/// Detects the recent high/low range from N candles and enters with market orders
/// when price breaks above the high (buy) or below the low (sell).
/// Uses stop-loss, take-profit, and trailing stop for risk management.
/// </summary>
public class ConstituentsEAStrategy : Strategy
{
private readonly StrategyParam<int> _searchDepth;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest = null!;
private Lowest _lowest = null!;
private decimal _pipSize;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _prevHigh;
private decimal _prevLow;
private bool _exitRequested;
/// <summary>
/// Number of completed candles used to determine the recent range.
/// </summary>
public int SearchDepth
{
get => _searchDepth.Value;
set => _searchDepth.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Working candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ConstituentsEaStrategy"/> class.
/// </summary>
public ConstituentsEAStrategy()
{
_searchDepth = Param(nameof(SearchDepth), 3)
.SetGreaterThanZero()
.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup");
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Stop loss distance expressed in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Take profit distance expressed in pips", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Working timeframe used to evaluate highs/lows", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null!;
_lowest = null!;
_pipSize = 0m;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_prevHigh = 0m;
_prevLow = 0m;
_exitRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
_highest = new Highest { Length = SearchDepth };
_lowest = new Lowest { Length = SearchDepth };
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;
// Process indicators
var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true });
var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true });
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
var currentHigh = highValue.ToDecimal();
var currentLow = lowValue.ToDecimal();
// Manage existing position
if (Position != 0)
{
ManagePosition(candle);
// Update range for next trade
_prevHigh = currentHigh;
_prevLow = currentLow;
return;
}
// Check for breakout signals using previous range
if (_prevHigh > 0m && _prevLow > 0m)
{
// Breakout above the recent high -> buy
if (candle.ClosePrice > _prevHigh)
{
_entryPrice = candle.ClosePrice;
_exitRequested = false;
if (StopLossPips > 0m)
_stopPrice = _entryPrice - StopLossPips * _pipSize;
else
_stopPrice = null;
if (TakeProfitPips > 0m)
_takePrice = _entryPrice + TakeProfitPips * _pipSize;
else
_takePrice = null;
BuyMarket();
}
// Breakout below the recent low -> sell
else if (candle.ClosePrice < _prevLow)
{
_entryPrice = candle.ClosePrice;
_exitRequested = false;
if (StopLossPips > 0m)
_stopPrice = _entryPrice + StopLossPips * _pipSize;
else
_stopPrice = null;
if (TakeProfitPips > 0m)
_takePrice = _entryPrice - TakeProfitPips * _pipSize;
else
_takePrice = null;
SellMarket();
}
}
// Update range for next candle
_prevHigh = currentHigh;
_prevLow = currentLow;
}
private void ManagePosition(ICandleMessage candle)
{
if (_exitRequested)
return;
if (Position > 0)
{
// Check take profit
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
_exitRequested = true;
SellMarket();
return;
}
// Check stop loss
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
_exitRequested = true;
SellMarket();
return;
}
}
else if (Position < 0)
{
// Check take profit
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
_exitRequested = true;
BuyMarket();
return;
}
// Check stop loss
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
_exitRequested = true;
BuyMarket();
return;
}
}
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 0.01m;
return step;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class constituents_ea_strategy(Strategy):
def __init__(self):
super(constituents_ea_strategy, self).__init__()
self._search_depth = self.Param("SearchDepth", 3) \
.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup")
self._stop_loss_pips = self.Param("StopLossPips", 50.0) \
.SetDisplay("Stop Loss pips", "Stop loss distance expressed in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 100.0) \
.SetDisplay("Take Profit pips", "Take profit distance expressed in pips", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Working timeframe used to evaluate highs and lows", "General")
self._highest = None
self._lowest = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._prev_high = 0.0
self._prev_low = 0.0
self._exit_requested = False
@property
def search_depth(self):
return self._search_depth.Value
@property
def stop_loss_pips(self):
return self._stop_loss_pips.Value
@property
def take_profit_pips(self):
return self._take_profit_pips.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(constituents_ea_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._prev_high = 0.0
self._prev_low = 0.0
self._exit_requested = False
def OnStarted2(self, time):
super(constituents_ea_strategy, self).OnStarted2(time)
step = self.Security.PriceStep if self.Security is not None else None
self._pip_size = float(step) if step is not None and float(step) > 0 else 0.01
self._highest = Highest()
self._highest.Length = self.search_depth
self._lowest = Lowest()
self._lowest.Length = self.search_depth
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._highest, self._lowest, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
current_high = float(high_value)
current_low = float(low_value)
if self.Position != 0:
self._manage_position(candle)
self._prev_high = current_high
self._prev_low = current_low
return
close = float(candle.ClosePrice)
sl_pips = float(self.stop_loss_pips)
tp_pips = float(self.take_profit_pips)
if self._prev_high > 0 and self._prev_low > 0:
if close > self._prev_high:
self._entry_price = close
self._exit_requested = False
self._stop_price = self._entry_price - sl_pips * self._pip_size if sl_pips > 0 else None
self._take_price = self._entry_price + tp_pips * self._pip_size if tp_pips > 0 else None
self.BuyMarket()
elif close < self._prev_low:
self._entry_price = close
self._exit_requested = False
self._stop_price = self._entry_price + sl_pips * self._pip_size if sl_pips > 0 else None
self._take_price = self._entry_price - tp_pips * self._pip_size if tp_pips > 0 else None
self.SellMarket()
self._prev_high = current_high
self._prev_low = current_low
def _manage_position(self, candle):
if self._exit_requested:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._take_price is not None and high >= self._take_price:
self._exit_requested = True
self.SellMarket()
return
if self._stop_price is not None and low <= self._stop_price:
self._exit_requested = True
self.SellMarket()
return
elif self.Position < 0:
if self._take_price is not None and low <= self._take_price:
self._exit_requested = True
self.BuyMarket()
return
if self._stop_price is not None and high >= self._stop_price:
self._exit_requested = True
self.BuyMarket()
return
def CreateClone(self):
return constituents_ea_strategy()