A Estratégia de Grade de Cobertura Urdala Trol é uma conversão direta do consultor especializado MetaTrader 5 Urdala_Trol.mq5 para a API de alto nível do StockSharp. A estratégia mantém continuamente exposição em ambas as direções e escala posições usando uma grade do tipo martingale quando os stops são atingidos. Opera inteiramente com dados Level1 (melhor bid/ask) sem nenhum indicador.
Lógica de negociação
Cobertura inicial (Passo 0) – quando não há posições ativas, a estratégia abre imediatamente uma ordem de mercado comprada e uma vendida usando o parâmetro Base Volume.
Escalamento do lado perdedor (Passo 1.2) – se apenas uma direção permanece aberta e a posição mais perdedora nesse lado está pelo menos Grid Step pips do preço atual, a estratégia abre uma posição adicional na mesma direção. O novo volume é igual ao volume da posição menos rentável mais Min Lots Multiplier * minVolumeStep, onde minVolumeStep é derivado do VolumeStep ou MinVolume do instrumento.
Tratamento do stop-loss (Passo 1.1) – quando uma posição é fechada pelo stop-loss (incluindo ajustes de trailing) com resultado negativo, a estratégia reentra na mesma direção, a menos que já haja uma operação ativa a menos de Min Nearest pips do preço de saída.
Reação ao stop lucrativo (Passo 2.1) – quando o stop fecha uma posição com lucro, a estratégia abre imediatamente uma operação na direção oposta com o volume escalado.
Trailing stop – uma vez que o preço avança Trailing Stop + Trailing Step pips além da entrada, o stop é ajustado para manter uma distância de Trailing Stop pips. O trailing é opcional e aplicado apenas quando ambos os parâmetros são maiores que zero.
Todas as distâncias expressas em pips são convertidas em deslocamentos de preço absolutos através do PriceStep do instrumento. Para cotações de cinco ou três dígitos, a conversão multiplica o passo por dez para corresponder à lógica "adjusted point" do MQL original.
Parâmetros
Parâmetro
Padrão
Descrição
BaseVolume
0.1
Tamanho de lote inicial usado para abrir o primeiro par de cobertura.
MinLotsMultiplier
3
Número de lotes mínimos adicionados ao volume da operação perdedora ao escalar.
StopLossPips
50
Distância do stop-loss em pips. Um valor de zero desativa o stop e a lógica de trailing.
TrailingStopPips
5
Distância do trailing stop em pips. Definir como zero para desativar o trailing.
TrailingStepPips
5
Distância adicional em pips necessária antes do trailing stop se mover. Deve ser positivo quando o trailing está habilitado.
GridStepPips
50
Distância mínima de preço (em pips) entre a posição perdedora e o preço atual antes de uma nova ordem de escalamento ser colocada.
MinNearestPips
3
Se uma posição existente estiver mais próxima que esta distância ao último preço de stop, a estratégia ignora a reentrada imediata.
Notas de implementação
Usa SubscribeLevel1() para rastrear atualizações de bid/ask e executar o motor de decisão em cada tick.
As ordens são registradas via helper de alto nível RegisterOrder, permitindo rastreamento preciso através de OnOwnTradeReceived.
Objetos de posição individuais são gerenciados internamente para reproduzir o comportamento de cobertura, pois os portfólios do StockSharp são baseados em posição líquida por padrão.
A lógica de stop-loss e trailing é executada dentro da estratégia enviando ordens de mercado quando os limites são ultrapassados; nenhuma ordem stop nativa é registrada.
Dicas de uso
Atribua um instrumento líquido e um portfólio à estratégia e certifique-se de que PriceStep, VolumeStep e os valores mínimos/máximos de volume estão configurados para conversões precisas.
Inicie a estratégia; ela construirá instantaneamente um par coberto e depois reagirá a eventos de stop de acordo com a lógica MQL original.
Ajuste os parâmetros de pips para alinhar com a volatilidade do instrumento. Valores grandes de Grid Step reduzem a frequência de ordens adicionais, enquanto um Min Lots Multiplier maior acelera o crescimento martingale.
Monitore a exposição resultante com cuidado; o comportamento martingale pode escalar o volume rapidamente quando múltiplos stops são atingidos consecutivamente.
A implementação em Python não é fornecida intencionalmente nesta pasta, conforme os requisitos desta tarefa de conversão.
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>
/// Urdala Trol strategy (simplified). Uses EMA with trailing stop logic
/// for grid-style entries based on trend direction.
/// </summary>
public class UrdalaTrolStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _trailingPercent;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public decimal TrailingPercent
{
get => _trailingPercent.Value;
set => _trailingPercent.Value = value;
}
public UrdalaTrolStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
_emaLength = Param(nameof(EmaLength), 14)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "Indicators");
_trailingPercent = Param(nameof(TrailingPercent), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Trailing %", "Trailing stop percent", "Risk");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaLength };
decimal highSinceEntry = 0;
decimal lowSinceEntry = decimal.MaxValue;
decimal prevClose = 0;
decimal prevEma = 0;
var hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, (ICandleMessage candle, decimal emaVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevClose = candle.ClosePrice;
prevEma = emaVal;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevClose = candle.ClosePrice;
prevEma = emaVal;
return;
}
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
// Track trailing stop levels
if (Position > 0)
{
if (high > highSinceEntry)
highSinceEntry = high;
var trailStop = highSinceEntry * (1m - TrailingPercent / 100m);
if (close < trailStop)
{
SellMarket();
highSinceEntry = 0;
lowSinceEntry = decimal.MaxValue;
return;
}
}
else if (Position < 0)
{
if (low < lowSinceEntry)
lowSinceEntry = low;
var trailStop = lowSinceEntry * (1m + TrailingPercent / 100m);
if (close > trailStop)
{
BuyMarket();
highSinceEntry = 0;
lowSinceEntry = decimal.MaxValue;
return;
}
}
// Entry signals based on EMA
var bullishCross = prevClose <= prevEma && close > emaVal;
var bearishCross = prevClose >= prevEma && close < emaVal;
if (bullishCross && Position <= 0)
{
BuyMarket();
highSinceEntry = high;
lowSinceEntry = decimal.MaxValue;
}
else if (bearishCross && Position >= 0)
{
SellMarket();
lowSinceEntry = low;
highSinceEntry = 0;
}
prevClose = close;
prevEma = emaVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
}
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 urdala_trol_strategy(Strategy):
def __init__(self):
super(urdala_trol_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._ema_length = self.Param("EmaLength", 14) \
.SetDisplay("EMA Length", "EMA period", "Indicators")
self._trailing_percent = self.Param("TrailingPercent", 1.5) \
.SetDisplay("Trailing %", "Trailing stop percent", "Risk")
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def TrailingPercent(self):
return self._trailing_percent.Value
def OnReseted(self):
super(urdala_trol_strategy, self).OnReseted()
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(urdala_trol_strategy, self).OnStarted2(time)
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
ev = float(ema_value)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ev
self._has_prev = True
return
if self.Position > 0:
if high > self._high_since_entry:
self._high_since_entry = high
trail_stop = self._high_since_entry * (1.0 - self.TrailingPercent / 100.0)
if close < trail_stop:
self.SellMarket()
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = close
self._prev_ema = ev
return
elif self.Position < 0:
if low < self._low_since_entry:
self._low_since_entry = low
trail_stop = self._low_since_entry * (1.0 + self.TrailingPercent / 100.0)
if close > trail_stop:
self.BuyMarket()
self._high_since_entry = 0.0
self._low_since_entry = 1e18
self._prev_close = close
self._prev_ema = ev
return
bullish_cross = self._prev_close <= self._prev_ema and close > ev
bearish_cross = self._prev_close >= self._prev_ema and close < ev
if bullish_cross and self.Position <= 0:
self.BuyMarket()
self._high_since_entry = high
self._low_since_entry = 1e18
elif bearish_cross and self.Position >= 0:
self.SellMarket()
self._low_since_entry = low
self._high_since_entry = 0.0
self._prev_close = close
self._prev_ema = ev
def CreateClone(self):
return urdala_trol_strategy()