Esta estratégia reproduz o comportamento do consultor especialista my_ts15.mq5 original, gerenciando ordens de trailing stop em torno de uma posição líquida existente. Uma média móvel ponderada linear (LWMA) orienta a colocação do stop e pode ser substituída por outros métodos de suavização. A lógica continuamente:
Lê o valor da média móvel de um número configurável de velas concluídas.
Compara o progresso dos preços com a trilha da média móvel e compensações baseadas em preços.
Move a ordem de parada de proteção somente quando o novo nível melhora o anterior em pelo menos o passo especificado.
Opcionalmente, impõe uma distância máxima de perda fixando o stop ou liquidando imediatamente a posição quando o limite é quebrado.
The strategy does not produce entry signals. Destina-se a funcionar em conjunto com outros componentes (manuais ou automatizados) que abrem posições no mesmo título.
Lógica de negociação
Assine a série de velas selecionada e vincule um indicador de média móvel usando o StockSharp API de alto nível.
Assim que uma vela terminar, armazene o resultado do indicador e obtenha o valor que está MaBarsTrail + MaShift barras atrás da barra atual.
Converta as configurações baseadas em pontos em distâncias de preços absolutos usando o tamanho do tick do instrumento.
For long positions, choose the lowest of:
The moving average minus its offset.
O preço atual menos a compensação “no lucro”.
Depois fixe a trilha na distância “em perda” e opcionalmente na perda máxima permitida.
For short positions, choose the highest of:
The moving average plus its offset.
O preço atual mais a compensação “no lucro”.
Depois fixe a trilha na distância “em perda” e opcionalmente na perda máxima permitida.
Atualize a ordem de parada somente quando a melhoria exceder TrailStepPoints (a menos que seja zero, caso em que todas as melhorias serão aceitas).
Se o preço ultrapassar a distância máxima de perda e EnforceMaxStopLoss estiver ativado, a estratégia fecha a posição imediatamente.
Todas as entradas de preço usam o preço da vela especificado em MaPrice, correspondendo à configuração original MQL onde o indicador é alimentado com a série PRICE_WEIGHTED.
Parâmetros
Nome
Padrão
Descrição
MaPeriod
50
Comprimento da média móvel usada como backbone móvel.
MaShift
0
Mudança adicional (em barras) aplicada ao amostrar o valor da média móvel.
MaMethod
LinearWeighted
Método de suavização da média móvel (simples, exponencial, suavizada, linear ponderada).
MaPrice
Weighted
Candle price fed to the moving average.
MaBarsTrail
1
Número de barras completadas entre a vela atual e a amostra de média móvel.
TrailBehindMaPoints
5
Distância em pontos mantidos entre o stop e a média móvel.
TrailBehindPricePoints
30
Distância em pontos mantida atrás do preço quando a posição é lucrativa.
TrailBehindNegativePoints
60
Distância em pontos mantida atrás do preço quando a posição está perdendo.
TrailStepPoints
0
Melhoria mínima (em pontos) necessária antes de mover o stop. Zero replica o comportamento “sempre atualizar”.
EnforceMaxStopLoss
false
Se ativado, fixe o stop na perda máxima permitida e liquide a posição quando o preço exceder esse limite.
MaxStopLossPoints
100
Maximum allowed loss distance in points.
ShowIndicator
true
Desenhe a média móvel e os marcadores comerciais no gráfico quando a IU estiver disponível.
CandleType
M1
Candle data type driving the calculations.
Todas as entradas baseadas em pontos são convertidas em distâncias de preço por meio do tamanho do pip do instrumento calculado em Security.PriceStep.
Notas de conversão
O especialista MQL atualizou o identificador MA manualmente. A implementação StockSharp usa BindEx para processar o indicador sem acessar buffers internos ou chamar GetValue.
Os preços de compra/venda não estão disponíveis diretamente nas velas finalizadas, portanto, os cálculos finais usam o preço da vela selecionado por MaPrice. Isso mantém o comportamento consistente porque o script original alimentou o indicador com o mesmo preço ponderado e o comparou com os ticks Bid/Ask.
PositionModify é substituído pelo cancelamento e recriação de ordens de stop de proteção (SellStop para longo, BuyStop para curto). A estratégia armazena o último nível de parada para imitar os limites finais MetaTrader.
O fechamento forçado opcional (pre_init) segue a lógica original: assim que o mercado ultrapassar MaxStopLossPoints, a posição é fechada imediatamente.
No entry logic has been added; os usuários devem combinar este módulo final com seu próprio provedor de sinal.
Dicas de uso
Anexe a estratégia ao mesmo título que abre as posições.
Ajuste as distâncias dos pontos ao tamanho do tick do instrumento (os símbolos Forex geralmente usam valores “pip”, os CFDs podem exigir multiplicadores diferentes).
Defina TrailStepPoints como um valor positivo para reduzir a rotatividade de pedidos em instrumentos ilíquidos.
Desative EnforceMaxStopLoss se outro gerenciador de risco já controla distâncias de parada brusca.
Mantenha ShowIndicator ativado enquanto ajusta os parâmetros para visualizar a média móvel e o comportamento final.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// My TS15 strategy: WMA trend following with trailing stop management.
/// Enters on price crossing WMA, exits with trailing stop logic.
/// </summary>
public class MyTs15Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _trailMultiplier;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _entryPrice;
private decimal _bestPrice;
private bool _wasBullish;
private bool _hasPrevSignal;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal TrailMultiplier { get => _trailMultiplier.Value; set => _trailMultiplier.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public MyTs15Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("MA Period", "WMA period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for trailing", "Indicators");
_trailMultiplier = Param(nameof(TrailMultiplier), 3m)
.SetDisplay("Trail Multiplier", "ATR multiplier for trailing stop", "Risk");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_bestPrice = 0m;
_wasBullish = false;
_hasPrevSignal = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_bestPrice = 0;
_hasPrevSignal = false;
_candlesSinceTrade = SignalCooldownCandles;
var wma = new WeightedMovingAverage { Length = MaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(wma, atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal wmaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var trailDist = atrValue * TrailMultiplier;
var isBullish = close > wmaValue;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
// Trailing stop check
if (Position > 0)
{
if (close > _bestPrice) _bestPrice = close;
if (_bestPrice - close > trailDist)
{
SellMarket();
_entryPrice = 0;
_bestPrice = 0;
_candlesSinceTrade = 0;
return;
}
}
else if (Position < 0)
{
if (close < _bestPrice) _bestPrice = close;
if (close - _bestPrice > trailDist)
{
BuyMarket();
_entryPrice = 0;
_bestPrice = 0;
_candlesSinceTrade = 0;
return;
}
}
// Entry signals
if (_hasPrevSignal && isBullish != _wasBullish && _candlesSinceTrade >= SignalCooldownCandles)
{
if (isBullish && Position <= 0)
{
BuyMarket();
_entryPrice = close;
_bestPrice = close;
_candlesSinceTrade = 0;
}
else if (!isBullish && Position >= 0)
{
SellMarket();
_entryPrice = close;
_bestPrice = close;
_candlesSinceTrade = 0;
}
}
_wasBullish = isBullish;
_hasPrevSignal = true;
}
}
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 WeightedMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class my_ts15_strategy(Strategy):
def __init__(self):
super(my_ts15_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 100) \
.SetDisplay("MA Period", "WMA period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for trailing", "Indicators")
self._trail_multiplier = self.Param("TrailMultiplier", 3.0) \
.SetDisplay("Trail Multiplier", "ATR multiplier for trailing stop", "Risk")
self._signal_cooldown = self.Param("SignalCooldownCandles", 12) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._wma = None
self._atr = None
self._entry_price = 0.0
self._best_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
self._candles_since_trade = 0
@property
def ma_period(self):
return self._ma_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def trail_multiplier(self):
return self._trail_multiplier.Value
@property
def signal_cooldown(self):
return self._signal_cooldown.Value
def OnReseted(self):
super(my_ts15_strategy, self).OnReseted()
self._wma = None
self._atr = None
self._entry_price = 0.0
self._best_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
self._candles_since_trade = self.signal_cooldown
def OnStarted2(self, time):
super(my_ts15_strategy, self).OnStarted2(time)
self._wma = WeightedMovingAverage()
self._wma.Length = self.ma_period
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
self._entry_price = 0.0
self._best_price = 0.0
self._has_prev_signal = False
self._candles_since_trade = self.signal_cooldown
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(120)))
subscription.Bind(self._wma, self._atr, self._process_candle)
subscription.Start()
def _process_candle(self, candle, wma_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._wma.IsFormed or not self._atr.IsFormed:
return
close = float(candle.ClosePrice)
wma_val = float(wma_value)
atr_val = float(atr_value)
trail_dist = atr_val * self.trail_multiplier
is_bullish = close > wma_val
if self._candles_since_trade < self.signal_cooldown:
self._candles_since_trade += 1
if self.Position > 0:
if close > self._best_price:
self._best_price = close
if self._best_price - close > trail_dist:
self.SellMarket()
self._entry_price = 0.0
self._best_price = 0.0
self._candles_since_trade = 0
return
elif self.Position < 0:
if close < self._best_price:
self._best_price = close
if close - self._best_price > trail_dist:
self.BuyMarket()
self._entry_price = 0.0
self._best_price = 0.0
self._candles_since_trade = 0
return
if self._has_prev_signal and is_bullish != self._was_bullish and self._candles_since_trade >= self.signal_cooldown:
if is_bullish and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._best_price = close
self._candles_since_trade = 0
elif not is_bullish and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._best_price = close
self._candles_since_trade = 0
self._was_bullish = is_bullish
self._has_prev_signal = True
def CreateClone(self):
return my_ts15_strategy()