La estrategia Zone Recovery Button es una conversión directa del asesor experto de MetaTrader "ZONE RECOVERY BUTTON VER1" (MQL/25347). El robot original dependía de botones BUY/SELL en el gráfico para iniciar una cesta cubierta. En esta adaptación a StockSharp, el panel manual se sustituye por parámetros, mientras que se conservan la lógica de recuperación, los take-profits monetarios/porcentuales, el trailing stop en divisa y la protección equity-stop.
Cuando la estrategia recibe una dirección inicial, abre una orden de mercado inicial. Cada vez que el precio atraviesa el ancho de zona configurado, el sistema apila una operación opuesta con mayor volumen. La cesta se cierra cuando se alcanza el take-profit de referencia, la ganancia flotante llega al objetivo monetario/porcentual configurado, el trailing stop devuelve demasiada ganancia o se infringe el umbral de equity-stop.
Reglas de trading
Dirección inicial - emula pulsar el botón BUY o SELL. La estrategia abre la primera orden inmediatamente cuando recibe datos y tiene permiso para operar. Después de cerrar la cesta, puede reiniciar automáticamente con la misma dirección.
Recuperación por zonas - en cada paso de recuperación, el algoritmo alterna la dirección. En ciclos largos vende cuando el precio cae por debajo de Base Price - Zone Width, y luego compra de nuevo cuando el mercado vuelve por encima de la base. En ciclos cortos, la lógica se refleja.
Escalado de volumen - cada cobertura adicional multiplica el volumen anterior o añade un incremento fijo, reproduciendo la configuración "Lots"/"Multiply" del EA.
Controles de take-profit - la cesta se cierra por:
take-profit basado en pips medido desde el precio de referencia;
objetivo monetario en la divisa de la cuenta;
objetivo porcentual calculado desde el valor actual de la cartera;
lógica trailing que bloquea ganancias cuando la ganancia flotante supera un umbral y después devuelve más que el drawdown permitido;
equity-stop de emergencia que compara la pérdida flotante actual con el equity más alto observado durante el ciclo.
Parámetros
Parámetro
Predeterminado
Descripción
CandleType
TimeSpan.FromMinutes(5)
Tipo de vela usado para monitorizar movimientos de precio.
StartDirection
Buy
Dirección inicial del ciclo (BUY/SELL/NONE).
AutoRestart
true
Reinicia automáticamente un nuevo ciclo después de cerrar la cesta anterior.
TakeProfitPips
200
Distancia en pips entre el precio base y el objetivo de take-profit en pips.
ZoneRecoveryPips
10
Distancia en pips que dispara la siguiente cobertura en la dirección opuesta.
InitialVolume
0.01
Volumen (lotes) de la primera operación.
UseVolumeMultiplier
true
Si está habilitado, cada cobertura multiplica el volumen anterior; de lo contrario se añade VolumeIncrement.
VolumeMultiplier
2
Multiplicador aplicado cuando UseVolumeMultiplier es true.
VolumeIncrement
0.01
Incremento de volumen cuando UseVolumeMultiplier es false.
MaxTrades
100
Número máximo de operaciones en la cesta.
UseMoneyTakeProfit
false
Habilita el cierre cuando la ganancia flotante supera MoneyTakeProfit.
MoneyTakeProfit
40
Objetivo de ganancia en la divisa de la cuenta.
UsePercentTakeProfit
false
Habilita el cierre cuando la ganancia flotante supera PercentTakeProfit por ciento del balance.
PercentTakeProfit
10
Objetivo de ganancia como porcentaje del valor actual de la cartera.
EnableTrailing
true
Habilita el trailing de ganancia en divisa.
TrailingProfitThreshold
40
Nivel de ganancia que activa trailing.
TrailingDrawdown
10
Drawdown permitido desde el pico de ganancia flotante antes de cerrar la cesta.
UseEquityStop
true
Habilita el equity stop de emergencia.
TotalEquityRiskPercent
1
Pérdida flotante máxima (en porcentaje del máximo de equity) antes de aplanar.
Notas
La estrategia funciona con cualquier instrumento que proporcione valores PriceStep y StepPrice. Estos parámetros son necesarios para convertir distancias en pips a unidades de precio y divisa.
Como StockSharp usa un modelo de posición neta, la cuadrícula de cobertura se simula internamente. La estrategia mantiene su propia lista de pasos de operación para reproducir el cálculo de ganancias de MetaTrader.
La lógica trailing opera sobre la ganancia flotante de la cesta activa. No usa trailing stops basados en órdenes.
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 ZoneRecoveryButtonStrategy : 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 ZoneRecoveryButtonStrategy()
{
_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 zone_recovery_button_strategy(Strategy):
def __init__(self):
super(zone_recovery_button_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(zone_recovery_button_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(zone_recovery_button_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 zone_recovery_button_strategy()