La Estrategia de Cuadrícula de Cobertura Urdala Trol es una conversión directa del asesor experto de MetaTrader 5 Urdala_Trol.mq5 a la API de alto nivel de StockSharp. La estrategia mantiene continuamente exposición en ambas direcciones y escala posiciones usando una cuadrícula tipo martingala cuando se activan los stops. Opera enteramente con datos Level1 (mejor bid/ask) sin ningún indicador.
Lógica de trading
Cobertura inicial (Paso 0) – cuando no hay posiciones activas, la estrategia abre inmediatamente una orden de mercado larga y una corta usando el parámetro Base Volume.
Escalado del lado perdedor (Paso 1.2) – si solo permanece abierta una dirección y la posición más perdedora en ese lado está al menos Grid Step pips del precio actual, la estrategia abre una posición adicional en la misma dirección. El nuevo volumen es igual al volumen de la posición menos rentable más Min Lots Multiplier * minVolumeStep, donde minVolumeStep se deriva del VolumeStep o MinVolume del instrumento.
Manejo del stop-loss (Paso 1.1) – cuando una posición es cerrada por el stop-loss (incluyendo ajustes de trailing) con resultado negativo, la estrategia vuelve a entrar en la misma dirección a menos que ya haya una operación activa a menos de Min Nearest pips del precio de salida.
Reacción al stop rentable (Paso 2.1) – cuando el stop cierra una posición con beneficio, la estrategia abre inmediatamente una operación en la dirección opuesta con el volumen escalado.
Trailing stop – una vez que el precio avanza Trailing Stop + Trailing Step pips más allá de la entrada, el stop se ajusta para mantener una distancia de Trailing Stop pips. El trailing es opcional y se aplica solo cuando ambos parámetros son mayores que cero.
Todas las distancias expresadas en pips se convierten a desplazamientos de precio absolutos a través del PriceStep del instrumento. Para cotizaciones de cinco o tres dígitos, la conversión multiplica el paso por diez para coincidir con la lógica "adjusted point" del MQL original.
Parámetros
Parámetro
Predeterminado
Descripción
BaseVolume
0.1
Tamaño de lote inicial para abrir el primer par de cobertura.
MinLotsMultiplier
3
Número de lotes mínimos añadidos al volumen de la operación perdedora al escalar.
StopLossPips
50
Distancia del stop-loss en pips. Un valor de cero deshabilita el stop y la lógica de trailing.
TrailingStopPips
5
Distancia del trailing stop en pips. Establecer en cero para deshabilitar el trailing.
TrailingStepPips
5
Distancia adicional en pips requerida antes de que el trailing stop se mueva. Debe ser positivo cuando el trailing está habilitado.
GridStepPips
50
Distancia mínima de precio (en pips) entre la posición perdedora y el precio actual antes de colocar una nueva orden de escalado.
MinNearestPips
3
Si una posición existente está más cerca que esta distancia al último precio de stop, la estrategia omite la re-entrada inmediata.
Notas de implementación
Usa SubscribeLevel1() para rastrear actualizaciones de bid/ask y ejecutar el motor de decisiones en cada tick.
Las órdenes se registran mediante el asistente de alto nivel RegisterOrder, permitiendo seguimiento preciso a través de OnOwnTradeReceived.
Los objetos de posición individuales se gestionan internamente para reproducir el comportamiento de cobertura, ya que los portafolios de StockSharp son de posición neta por defecto.
La lógica de stop-loss y trailing se ejecuta dentro de la estrategia enviando órdenes de mercado una vez que se superan los umbrales; no se registran órdenes stop nativas.
Consejos de uso
Asigne un instrumento líquido y un portafolio a la estrategia y asegúrese de que PriceStep, VolumeStep y los valores mínimos/máximos de volumen estén configurados para conversiones precisas.
Inicie la estrategia; construirá instantáneamente un par cubierto y luego reaccionará a los eventos de stop según la lógica MQL original.
Ajuste los parámetros de pips según la volatilidad del instrumento. Valores grandes de Grid Step reducen la frecuencia de órdenes adicionales, mientras que un Min Lots Multiplier mayor acelera el crecimiento martingala.
Monitoree la exposición resultante con cuidado; el comportamiento martingala puede escalar el volumen rápidamente cuando se activan múltiples stops consecutivos.
La implementación en Python no se proporciona intencionalmente en esta carpeta, según los requisitos de esta tarea de conversión.
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()