2DLimits es un puerto directo del experto asesor MetaTrader 4 2DLimits_EA_v2. La estrategia evalúa las dos últimas velas diarias completadas y solo participa cuando forman un patrón escalonado (máximos y mínimos más altos, o máximos y mínimos más bajos). Cuando el patrón es válido, la estrategia envía órdenes de stop en el extremo del día anterior y protege la posición con un stop-loss en el punto medio y un objetivo igual al rango diario anterior.
La implementación se basa en las suscripciones de velas de alto nivel de StockSharp junto con cotizaciones de nivel 1. Las velas diarias proporcionan los niveles de ruptura mientras que las instantáneas del mejor bid/ask aseguran que las configuraciones largas solo se activen cuando el precio opera por debajo del punto medio y las cortas solo cuando opera por encima.
Lógica de la estrategia
Filtro de estructura diaria
La estrategia mantiene una ventana deslizante de dos días de velas diarias completadas (configurable a través del parámetro de tipo de vela).
Una configuración alcista requiere que el día más reciente registre tanto un máximo más alto como un mínimo más alto en comparación con el día anterior.
Una configuración bajista requiere que el día más reciente presente tanto un máximo más bajo como un mínimo más bajo que el día anterior.
El punto medio del día más reciente se calcula como (high + low) / 2, y el rango de la vela se almacena para el objetivo de beneficio.
Reglas de entrada
Solo hay un lote de órdenes pendientes activas a la vez; todas las órdenes se cancelan y recalculan cuando se cierra una nueva vela diaria.
Las entradas largas se preparan cuando:
El filtro de estructura alcista está satisfecho.
El último precio ask está por debajo del punto medio del día anterior (refleja el control Ask < middleY del EA original).
Se coloca una orden de compra-stop exactamente en el máximo del día anterior.
Las entradas cortas se preparan cuando:
El filtro de estructura bajista está satisfecho.
El último precio bid está por encima del punto medio del día anterior (refleja Bid > middleY).
Se coloca una orden de venta-stop en el mínimo del día anterior.
Si ambas comprobaciones de estructura fallan, no se dejan órdenes activas para la próxima sesión.
Reglas de salida
Cuando una orden de stop se activa, la orden de entrada opuesta se cancela de inmediato para que la estrategia nunca mantenga exposiciones largas y cortas simultáneas.
Tras una ruptura larga, se registran dos órdenes protectoras:
Una orden de stop en el punto medio del día de referencia actúa como stop-loss.
Una orden de take-profit en máximo anterior + rango anterior coincide con la distancia de take-profit de MetaTrader.
Tras una ruptura corta, se aplica protección simétrica:
Una orden de stop en el punto medio (buy-stop) cubre el stop-loss.
Una orden de take-profit en mínimo anterior - rango anterior refleja el objetivo original.
Las órdenes protectoras se reactivan cada vez que cambia el tamaño de la posición ejecutada y se eliminan una vez que la posición vuelve a ser plana.
Ciclo de vida de la orden y comprobaciones de seguridad
Las órdenes pendientes se actualizan solo después de que se completa la siguiente vela diaria, imponiendo una única configuración por día de trading.
La estrategia omite la generación de señales siempre que ya tenga una posición, evitando superposiciones entre configuraciones.
La última instantánea de bid/ask se retiene de SubscribeLevel1(); si no está disponible, el último precio de transacción se usa como respaldo para evitar enviar órdenes ciegas.
El redondeo se realiza con el paso de precio del instrumento para que todas las órdenes se alineen con el tamaño de tick del mercado.
Parámetros
Nombre
Descripción
Volume
Volumen de la orden para las entradas de stop. Debe ser mayor que cero.
CandleType
Tipo de vela que proporciona el rango de referencia (predeterminado velas diarias).
Notas adicionales
La estrategia gestiona cada orden directamente a través de la API de alto nivel; no hay dependencia de colecciones personalizadas o buffers de indicadores.
Solo se proporciona la implementación C# en este paquete. No se crea versión Python para esta conversión.
Las pruebas no se modifican según se solicitó.
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;
public class TwoDLimitsStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public TwoDLimitsStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 two_d_limits_strategy(Strategy):
def __init__(self):
super(two_d_limits_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(two_d_limits_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(two_d_limits_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return two_d_limits_strategy()