Estrategia de escaleras
La Estrategia de escaleras reproduce el comportamiento del experto MetaTrader original. Comienza colocando órdenes stop simétricas alrededor del precio de venta actual y luego reconstruye continuamente la cuadrícula alrededor del llenado más reciente. Las ganancias se acumulan en incrementos de precio (pips) sin ponderación por volumen, exactamente como en el script original. Cuando se alcanza un objetivo de ganancias, la estrategia liquida todas las posiciones por orden de mercado, elimina las paradas pendientes y restablece la red.
Lógica comercial
- Cuando no haya posiciones abiertas, coloque un stop de compra y un stop de venta a una distancia de
ChannelSteps / 2 pasos de precio por encima y por debajo del precio de venta actual.
- Después de que se complete la primera orden de parada, vuelva a armar la cuadrícula alrededor del precio ejecutado:
- Si hay menos de dos órdenes stop activas, cancele las obsoletas.
- Siempre que el precio de oferta actual se mantenga dentro de la mitad de la distancia del canal desde la última entrada, coloque un nuevo stop de compra y de venta a
ChannelSteps del llenado más reciente.
- Cuando
AddLots esté habilitado, aumente el volumen de pedidos pendientes en el lote base después de cada ejecución.
- Mantenga dos listas actualizadas con todas las entradas largas y cortas para reproducir la cesta cubierta utilizada por la versión MT4.
- Calcule el beneficio no realizado de la cesta en cada vela terminada utilizando la mejor oferta para posiciones largas y la mejor demanda para posiciones cortas. Las distancias se normalizan según el paso del precio del instrumento, reflejando el cálculo de puntos original.
- Activar una liquidación total cuando se supere cualquiera de los umbrales:
ProfitSteps – beneficio producido únicamente por el símbolo actual.
CommonProfitSteps – beneficio en toda la cesta.
- La liquidación envía órdenes de mercado para cerrar cada exposición larga y corta por separado. Las órdenes stop pendientes se cancelan una vez que la cesta está plana.
Nota: El experto original adjuntó niveles de stop-loss al registrar órdenes pendientes. StockSharp no admite niveles de protección por orden a través del nivel alto API, por lo tanto, el puerto cierra operaciones exclusivamente a través de la lógica basada en ganancias descrita anteriormente.
Parámetros
| Parámetro |
Descripción |
Predeterminado |
ChannelSteps |
Distancia (en pasos de precio mínimo) entre las órdenes stop simétricas. |
1000 |
ProfitSteps |
Umbral de ganancia (en pasos) requerido para cerrar la canasta local. |
1500 |
CommonProfitSteps |
Umbral de beneficio global (en pasos) que obliga a una liquidación total. |
1000 |
AddLots |
Cuando esté habilitado, aumente el siguiente volumen de pedido pendiente en el lote base después de cada ejecución. |
true |
BaseVolume |
Volumen utilizado para el primer par de órdenes stop. |
0.1m |
CandleType |
Plazo utilizado para las suscripciones de velas y la gestión comercial. |
1 minute |
Notas de implementación
- Utiliza la API de alto nivel de StockSharp con
SubscribeCandles() y Bind() para procesar velas terminadas únicamente.
- Realiza un seguimiento de las entradas individuales dentro de
OnOwnTradeReceived para que el cálculo de ganancias pueda imitar la lógica de cobertura de la versión MQL.
- Los umbrales de ganancias operan en distancias puras de precio-escalón, sin multiplicarse por el volumen ejecutado, coincidiendo con la forma en que el experto de MT4 sumó los pips.
- Todas las órdenes stop se crean a través de
BuyStop y SellStop, mientras que las salidas se ejecutan con órdenes de mercado para mantener la lógica portátil entre los proveedores de datos.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stairs grid strategy: places trades at regular ATR-based intervals,
/// adding to position on trending moves, closing on profit target.
/// </summary>
public class StairsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _gridMultiplier;
private readonly StrategyParam<int> _maxLayers;
private readonly StrategyParam<decimal> _profitMultiplier;
private readonly StrategyParam<int> _emaLength;
private decimal _entryPrice;
private decimal _lastGridPrice;
private int _gridCount;
private decimal _prevEma;
public StairsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for grid step.", "Indicators");
_gridMultiplier = Param(nameof(GridMultiplier), 1.5m)
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step.", "Grid");
_maxLayers = Param(nameof(MaxLayers), 5)
.SetDisplay("Max Layers", "Maximum grid layers.", "Grid");
_profitMultiplier = Param(nameof(ProfitMultiplier), 2.0m)
.SetDisplay("Profit Multiplier", "ATR multiplier for profit target.", "Grid");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "EMA for trend direction.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal GridMultiplier
{
get => _gridMultiplier.Value;
set => _gridMultiplier.Value = value;
}
public int MaxLayers
{
get => _maxLayers.Value;
set => _maxLayers.Value = value;
}
public decimal ProfitMultiplier
{
get => _profitMultiplier.Value;
set => _profitMultiplier.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_lastGridPrice = 0;
_gridCount = 0;
_prevEma = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0 || _prevEma == 0)
{
_prevEma = emaVal;
return;
}
var close = candle.ClosePrice;
var gridStep = atrVal * GridMultiplier;
var profitTarget = atrVal * ProfitMultiplier;
// Check profit target
if (Position > 0 && _entryPrice > 0)
{
if (close - _entryPrice >= profitTarget || close < emaVal)
{
SellMarket();
_gridCount = 0;
_entryPrice = 0;
_lastGridPrice = 0;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (_entryPrice - close >= profitTarget || close > emaVal)
{
BuyMarket();
_gridCount = 0;
_entryPrice = 0;
_lastGridPrice = 0;
}
}
// Grid: add to winning direction
if (Position > 0 && _lastGridPrice > 0 && _gridCount < MaxLayers)
{
if (close - _lastGridPrice >= gridStep)
{
BuyMarket();
_lastGridPrice = close;
_gridCount++;
}
}
else if (Position < 0 && _lastGridPrice > 0 && _gridCount < MaxLayers)
{
if (_lastGridPrice - close >= gridStep)
{
SellMarket();
_lastGridPrice = close;
_gridCount++;
}
}
// Initial entry based on trend
if (Position == 0)
{
var emaRising = emaVal > _prevEma;
var emaFalling = emaVal < _prevEma;
if (emaRising && close > emaVal)
{
_entryPrice = close;
_lastGridPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (emaFalling && close < emaVal)
{
_entryPrice = close;
_lastGridPrice = close;
_gridCount = 0;
SellMarket();
}
}
_prevEma = emaVal;
}
}
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 AverageTrueRange, ExponentialMovingAverage
class stairs_strategy(Strategy):
def __init__(self):
super(stairs_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for grid step", "Indicators")
self._grid_multiplier = self.Param("GridMultiplier", 1.5) \
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step", "Grid")
self._max_layers = self.Param("MaxLayers", 5) \
.SetDisplay("Max Layers", "Maximum grid layers", "Grid")
self._profit_multiplier = self.Param("ProfitMultiplier", 2.0) \
.SetDisplay("Profit Multiplier", "ATR multiplier for profit target", "Grid")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA for trend direction", "Indicators")
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def GridMultiplier(self):
return self._grid_multiplier.Value
@property
def MaxLayers(self):
return self._max_layers.Value
@property
def ProfitMultiplier(self):
return self._profit_multiplier.Value
@property
def EmaLength(self):
return self._ema_length.Value
def OnStarted2(self, time):
super(stairs_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val, ema_val):
if candle.State != CandleStates.Finished:
return
atr_v = float(atr_val)
ema_v = float(ema_val)
if atr_v <= 0 or self._prev_ema == 0:
self._prev_ema = ema_v
return
close = float(candle.ClosePrice)
grid_step = atr_v * float(self.GridMultiplier)
profit_target = atr_v * float(self.ProfitMultiplier)
# Check profit target
if self.Position > 0 and self._entry_price > 0:
if close - self._entry_price >= profit_target or close < ema_v:
self.SellMarket()
self._grid_count = 0
self._entry_price = 0.0
self._last_grid_price = 0.0
elif self.Position < 0 and self._entry_price > 0:
if self._entry_price - close >= profit_target or close > ema_v:
self.BuyMarket()
self._grid_count = 0
self._entry_price = 0.0
self._last_grid_price = 0.0
# Grid: add to winning direction
max_layers = self.MaxLayers
if self.Position > 0 and self._last_grid_price > 0 and self._grid_count < max_layers:
if close - self._last_grid_price >= grid_step:
self.BuyMarket()
self._last_grid_price = close
self._grid_count += 1
elif self.Position < 0 and self._last_grid_price > 0 and self._grid_count < max_layers:
if self._last_grid_price - close >= grid_step:
self.SellMarket()
self._last_grid_price = close
self._grid_count += 1
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema = ema_v
return
# Initial entry based on trend
if self.Position == 0:
ema_rising = ema_v > self._prev_ema
ema_falling = ema_v < self._prev_ema
if ema_rising and close > ema_v:
self._entry_price = close
self._last_grid_price = close
self._grid_count = 0
self.BuyMarket()
elif ema_falling and close < ema_v:
self._entry_price = close
self._last_grid_price = close
self._grid_count = 0
self.SellMarket()
self._prev_ema = ema_v
def OnReseted(self):
super(stairs_strategy, self).OnReseted()
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
def CreateClone(self):
return stairs_strategy()