TrailingStopFrCnStrategy é uma porta StockSharp do consultor especialista MetaTrader TrailingStopFrCn.mq4. O script original gerencia níveis de stop-loss para posições existentes usando uma combinação de distâncias finais fixas, fractais Bill Williams ou máximos/mínimos recentes de velas. Esta porta mantém a mesma flexibilidade enquanto se integra ao StockSharp API de alto nível: a estratégia assina velas e cotações de nível 1, monitora a posição líquida atual e atualiza automaticamente uma ordem de stop protetora.
Ao contrário de uma estratégia de entrada, o TrailingStopFrCn concentra-se exclusivamente na gestão de risco. Não abre novas posições. Em vez disso, ele rastreia a posição existente de Strategy.Security, cancela ordens de stop obsoletas quando a posição muda e envia uma única ordem de stop agregada que segue a lógica do consultor MetaTrader.
Lógica final
Distância de fuga fixa – quando TrailingStopPips é maior que zero, a estratégia se comporta como o parâmetro MQL original TrailingStop. Para posições longas o stop é colocado em bestBid - distance, para posições curtas em bestAsk + distance, com distance = TrailingStopPips × pip size.
Fractal trailing – quando TrailingStopPips = 0 e TrailingMode = Fractals, a estratégia detecta fractais Bill Williams de cinco barras. Cada vela finalizada é adicionada a um buffer interno e, uma vez disponível histórico suficiente, a vela duas barras atrás é avaliada como um fractal potencial. O fractal mais recente que está pelo menos MinStopDistancePips longe do preço atual torna-se o novo candidato a stop.
Trailing de vela – quando TrailingStopPips = 0 e TrailingMode = Candles, a estratégia verifica até as últimas 99 velas fechadas e seleciona a primeira mínima (para posições compradas) ou máxima (para posições vendidas) que está separada do preço atual por pelo menos MinStopDistancePips.
Depois de calcular o nível de candidato, a estratégia impõe as mesmas regras de proteção da versão MQL:
OnlyProfit evita mover o stop, a menos que o novo nível bloqueie o lucro (stop acima da entrada para posições compradas, stop abaixo da entrada para posições vendidas).
OnlyWithoutLoss interrompe o trailing quando o stop-loss ativo já protege a posição contra perdas (no script original, o processo de trailing para após o ponto de equilíbrio ser atingido).
O stop só se move na direção favorável: para cima para posições longas e para baixo para posições curtas.
Como StockSharp rastreia uma única posição líquida por título, o volume da ordem stop é igual a Math.Abs(Position) e todos os preenchimentos subjacentes são agregados.
Parâmetros
Parâmetro
Descrição
OnlyProfit
Mova o stop-loss somente quando o novo nível garantir lucro em relação ao preço médio de entrada. Espelha o sinalizador OnlyProfit de MQL.
OnlyWithoutLoss
Pare de seguir quando o stop loss ativo estiver igual ou superior ao preço de entrada. Isso replica OnlyWithoutLoss do orientador original.
TrailingStopPips
Distância de fuga fixa expressa em pips. Defina como zero para ativar o fractal ou o rastro de vela.
MinStopDistancePips
Distância mínima (em pips) entre o preço de mercado e o stop loss. Use-o para emular a restrição do corretor MODE_STOPLEVEL.
TrailingMode
Escolhe a origem final quando TrailingStopPips = 0. Opções: Fractals (Bill Williams fractais de cinco barras) ou Candles (mínimos/máximos recentes).
CandleType
Tipo de dados Candle usado para construir fractais ou para procurar pontos de oscilação. O padrão é o período de uma hora.
Notas comportamentais
A estratégia subscreve dados de Nível 1 para acessar os melhores preços de compra/venda. O rastreamento de distância fixa reage imediatamente às atualizações de nível 1, enquanto o rastreamento fractal/vela é atualizado quando novas velas chegam.
Quando a direção da posição muda, a ordem de parada atual é cancelada antes que a nova ordem seja enviada.
Se nenhum candidato a stop estiver disponível (por exemplo, velas insuficientes), a estratégia mantém o stop existente.
Se o corretor não impor uma distância mínima de parada, você poderá deixar MinStopDistancePips em zero.
Diferenças da versão MetaTrader
StockSharp mantém uma posição líquida, portanto, “tickets” individuais de MetaTrader não são rastreados. A ordem stop cobre toda a posição agregada.
O filtro Magic não é necessário: a estratégia já opera em seu próprio contexto de segurança.
As atualizações finais são conduzidas por velas concluídas mais dados de Nível 1, em vez de um loop de pesquisa de um segundo.
Objetos gráficos visuais do EA original não são recriados; em vez disso, você pode usar os auxiliares de gráficos de StockSharp ao executar a IU de amostra.
Dicas de uso
Execute a estratégia junto com qualquer lógica de entrada que abra posições no mesmo Security. TrailingStopFrCn anexará automaticamente uma ordem de stop assim que a posição aparecer.
Ajuste CandleType para corresponder ao período de tempo que deve ser analisado para fractais ou pontos de oscilação. Prazos mais altos suavizam os níveis finais, enquanto prazos mais baixos reagem mais rapidamente.
Calibre MinStopDistancePips de acordo com as limitações de nível de stop da sua corretora. Definir um valor muito baixo pode levar à rejeição de pedidos.
Ao testar dados históricos, certifique-se de que a assinatura de vela e as mensagens de nível 1 estejam disponíveis na fonte de dados para que a lógica de rastreamento possa ser acionada corretamente.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// TrailingStopFrCn: EMA crossover with ATR trailing stops.
/// </summary>
public class TrailingStopFrCnStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public TrailingStopFrCnStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class trailing_stop_fr_cn_strategy(Strategy):
def __init__(self):
super(trailing_stop_fr_cn_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 12) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(trailing_stop_fr_cn_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(trailing_stop_fr_cn_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return trailing_stop_fr_cn_strategy()