Ver en GitHub

Autoadaptable revisado EA

Incorporación del MetaTrader 5 asesores expertos revised_self_adaptive_ea.mq5 al marco estratégico de alto nivel StockSharp.

Descripción general de la estrategia

El algoritmo escanea una serie de velas configurables y busca configuraciones de reversión envolvente confirmadas por filtros de impulso y tendencia:

  • Detección de patrones: evalúa la última vela cerrada frente a la anterior. Una configuración alcista requiere un cuerpo verde que se abre por debajo del cierre anterior mientras que la vela anterior es bajista. La lógica del espejo se aplica para configuraciones bajistas. Los cuerpos de las velas se comparan con un promedio móvil para filtrar señales débiles.
  • Filtro de impulso: un RSI clásico garantiza que las operaciones alcistas solo se activen en territorio de sobreventa y las operaciones bajistas en condiciones de sobrecompra.
  • Filtro de tendencia: una media móvil simple corta debe coincidir con la dirección comercial. Esto evita que las tendencias fuertes se desvanezcan sin confirmación.
  • Gestión de riesgos: se calculan niveles de stop-loss y take-profit impulsados por ATR para cada nueva posición. Los trailingstops opcionales siguen movimientos rentables sin reducir nunca la protección. Las posiciones se cierran a la fuerza cuando el precio alcanza los niveles de protección.
  • Diferencial y protección contra riesgos: las operaciones se omiten siempre que el diferencial actual supere el umbral configurado o cuando el stop basado en ATR arriesgaría más que el porcentaje permitido del precio.

Parámetros

Nombre Descripción
CandleType Agregación de velas utilizada para el análisis. El valor predeterminado es barras de una hora.
AverageBodyPeriod Número de velas utilizadas para calcular el filtro de tamaño corporal promedio.
MovingAveragePeriod Longitud de la media móvil simple que actúa como filtro direccional.
RsiPeriod RSI longitud utilizada para la confirmación de sobreventa/sobrecompra.
OversoldLevel RSI umbral que se debe alcanzar antes de aceptar una reversión alcista.
OverboughtLevel RSI umbral que se debe alcanzar antes de aceptar una reversión bajista.
AtrPeriod ATR longitud utilizada para distancias protectoras basadas en la volatilidad.
StopLossAtrMultiplier Factor multiplicativo aplicado a ATR para la distancia de stop-loss.
TakeProfitAtrMultiplier Factor multiplicativo aplicado a ATR para la distancia de obtención de beneficios.
TrailingStopAtrMultiplier ATR distancia mantenida por la lógica del trailing stop.
UseTrailingStop Habilita el supervisor de trailing stop.
MaxSpreadPoints Spread máximo permitido (expresado en pasos de precio/pips). Las señales se ignoran cuando el mercado es más amplio.
MaxRiskPercent Riesgo porcentual máximo aceptable basado en el stop ATR en relación con el precio de entrada.
TradeVolume Tamaño de lote base utilizado para órdenes de mercado.

Notas de comportamiento

  • Las posiciones se aplanan antes de invertir la dirección para reflejar la implementación de MetaTrader.
  • Los niveles de parada/toma de protección se vuelven a calcular después de cada llenado utilizando la lectura ATR más reciente.
  • El trailing stop solo se mueve en la dirección comercial y se desactiva cuando los datos ATR aún no están disponibles.
  • Si la estrategia se ejecuta en un instrumento sin cotizaciones de oferta/demanda confiables, el filtro de diferencial permanecerá inactivo automáticamente.

Diferencias vs. original MQL

El guión original sólo describía la rutina de detección de señales. En este puerto los elementos faltantes fueron reconstruidos utilizando los parámetros proporcionados:

  • Se agregó confirmación de promedio móvil para hacer uso del identificador MA declarado en la fuente MQL.
  • Se implementó la lógica de stop-loss, take-profit y trailing stop basada en ATR utilizando el control de volatilidad definido en el experto original.
  • Se agregó una protección de porcentaje de riesgo para que las paradas ATR sobredimensionadas se omitan en lugar de ejecutarse a ciegas.
  • Se omitieron los elementos de visualización (flechas del gráfico) porque las estrategias StockSharp no dibujan objetos en los gráficos de forma predeterminada.

Uso

  1. Adjunte la estrategia a una cartera y seguridad dentro de Hydra o su host StockSharp personalizado.
  2. Asegúrese de que la suscripción a la vela coincida con el plazo previsto (predeterminado: una hora).
  3. Ajustar los parámetros de riesgo para reflejar la volatilidad del instrumento.
  4. Inicia la estrategia. Se suscribirá automáticamente a velas, calculará indicadores y realizará órdenes de mercado cuando se cumplan las condiciones.
namespace StockSharp.Samples.Strategies;

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

using StockSharp.Algo;

/// <summary>
/// Port of the MetaTrader expert revised_self_adaptive_ea.
/// Detects bullish and bearish engulfing patterns confirmed by RSI and a trend moving average.
/// Applies ATR based risk management with optional trailing stop supervision.
/// </summary>
public class RevisedSelfAdaptiveEaStrategy : Strategy
{
	private readonly StrategyParam<int> _averageBodyPeriod;
	private readonly StrategyParam<int> _movingAveragePeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _volume;
	private readonly StrategyParam<decimal> _maxSpreadPoints;
	private readonly StrategyParam<decimal> _maxRiskPercent;
	private readonly StrategyParam<bool> _useTrailingStop;
	private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
	private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
	private readonly StrategyParam<decimal> _trailingStopAtrMultiplier;
	private readonly StrategyParam<decimal> _oversoldLevel;
	private readonly StrategyParam<decimal> _overboughtLevel;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi = null!;
	private SimpleMovingAverage _movingAverage = null!;
	private AverageTrueRange _atr = null!;
	private SimpleMovingAverage _bodyAverage = null!;

	private ICandleMessage _previousCandle;

	private decimal _lastAtrValue;
	private decimal _averageBodyValue;
	private decimal _pipSize;
	private bool _pipSizeInitialized;

	private decimal? _longEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _longTakeProfitPrice;
	private decimal? _longTrailingStopPrice;

	private decimal? _shortEntryPrice;
	private decimal? _shortStopPrice;
	private decimal? _shortTakeProfitPrice;
	private decimal? _shortTrailingStopPrice;

	/// <summary>
	/// Initializes a new instance of <see cref="RevisedSelfAdaptiveEaStrategy"/>.
	/// </summary>
	public RevisedSelfAdaptiveEaStrategy()
	{
		_averageBodyPeriod = Param(nameof(AverageBodyPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("Average body period", "Number of candles used to calculate the average body size filter.", "Pattern")
		
		.SetOptimize(2, 10, 1);

		_movingAveragePeriod = Param(nameof(MovingAveragePeriod), 2)
		.SetGreaterThanZero()
		.SetDisplay("MA period", "Simple moving average period used as a directional filter.", "Trend")
		
		.SetOptimize(2, 30, 1);

		_rsiPeriod = Param(nameof(RsiPeriod), 6)
		.SetGreaterThanZero()
		.SetDisplay("RSI period", "Length of the RSI oscillator applied to candle closes.", "Oscillator")
		
		.SetOptimize(3, 30, 1);

		_atrPeriod = Param(nameof(AtrPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("ATR period", "Average True Range period that controls stop distances.", "Risk")
		
		.SetOptimize(7, 50, 1);

		_volume = Param(nameof(TradeVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Trade volume", "Base position size expressed in lots.", "Trading")
		
		.SetOptimize(0.01m, 1m, 0.01m);

		_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 20m)
		.SetNotNegative()
		.SetDisplay("Max spread", "Maximum allowed spread expressed in points.", "Risk");

		_maxRiskPercent = Param(nameof(MaxRiskPercent), 10m)
		.SetNotNegative()
		.SetDisplay("Max risk percent", "Maximum percentage of the portfolio equity accepted per trade.", "Risk");

		_useTrailingStop = Param(nameof(UseTrailingStop), true)
		.SetDisplay("Use trailing stop", "Enable ATR driven trailing stop supervision.", "Risk");

		_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 2m)
		.SetNotNegative()
		.SetDisplay("Stop loss ATR multiplier", "Number of ATRs used to place the protective stop.", "Risk")
		
		.SetOptimize(0.5m, 5m, 0.5m);

		_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 4m)
		.SetNotNegative()
		.SetDisplay("Take profit ATR multiplier", "Number of ATRs used to place the profit target.", "Risk")
		
		.SetOptimize(0.5m, 8m, 0.5m);

		_trailingStopAtrMultiplier = Param(nameof(TrailingStopAtrMultiplier), 1.5m)
		.SetNotNegative()
		.SetDisplay("Trailing stop ATR multiplier", "ATR distance maintained by the trailing stop logic.", "Risk")
		
		.SetOptimize(0.5m, 5m, 0.5m);

		_oversoldLevel = Param(nameof(OversoldLevel), 40m)
		.SetNotNegative()
		.SetDisplay("Oversold level", "RSI threshold that confirms bullish reversals.", "Oscillator")
		
		.SetOptimize(10m, 50m, 5m);

		_overboughtLevel = Param(nameof(OverboughtLevel), 60m)
		.SetNotNegative()
		.SetDisplay("Overbought level", "RSI threshold that confirms bearish reversals.", "Oscillator")
		
		.SetOptimize(50m, 90m, 5m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
		.SetDisplay("Candle type", "Time frame used for pattern detection.", "General");
	}

/// <summary>
/// Rolling period used to evaluate the average candle body size.
/// </summary>
public int AverageBodyPeriod
{
	get => _averageBodyPeriod.Value;
	set => _averageBodyPeriod.Value = value;
}

/// <summary>
/// Period applied to the simple moving average filter.
/// </summary>
public int MovingAveragePeriod
{
	get => _movingAveragePeriod.Value;
	set => _movingAveragePeriod.Value = value;
}

/// <summary>
/// RSI length used to confirm overbought and oversold conditions.
/// </summary>
public int RsiPeriod
{
	get => _rsiPeriod.Value;
	set => _rsiPeriod.Value = value;
}

/// <summary>
/// Average True Range period that controls risk distances.
/// </summary>
public int AtrPeriod
{
	get => _atrPeriod.Value;
	set => _atrPeriod.Value = value;
}

/// <summary>
/// Default trade volume.
/// </summary>
public decimal TradeVolume
{
	get => _volume.Value;
	set => _volume.Value = value;
}

/// <summary>
/// Maximum allowed spread measured in points.
/// </summary>
public decimal MaxSpreadPoints
{
	get => _maxSpreadPoints.Value;
	set => _maxSpreadPoints.Value = value;
}

/// <summary>
/// Maximum portion of portfolio equity that can be exposed per trade.
/// </summary>
public decimal MaxRiskPercent
{
	get => _maxRiskPercent.Value;
	set => _maxRiskPercent.Value = value;
}

/// <summary>
/// Enables the ATR based trailing stop controller.
/// </summary>
public bool UseTrailingStop
{
	get => _useTrailingStop.Value;
	set => _useTrailingStop.Value = value;
}

/// <summary>
/// ATR multiplier used to position the stop loss.
/// </summary>
public decimal StopLossAtrMultiplier
{
	get => _stopLossAtrMultiplier.Value;
	set => _stopLossAtrMultiplier.Value = value;
}

/// <summary>
/// ATR multiplier used to position the take profit target.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
	get => _takeProfitAtrMultiplier.Value;
	set => _takeProfitAtrMultiplier.Value = value;
}

/// <summary>
/// ATR multiplier that defines the trailing stop distance.
/// </summary>
public decimal TrailingStopAtrMultiplier
{
	get => _trailingStopAtrMultiplier.Value;
	set => _trailingStopAtrMultiplier.Value = value;
}

/// <summary>
/// RSI threshold that validates bullish signals.
/// </summary>
public decimal OversoldLevel
{
	get => _oversoldLevel.Value;
	set => _oversoldLevel.Value = value;
}

/// <summary>
/// RSI threshold that validates bearish signals.
/// </summary>
public decimal OverboughtLevel
{
	get => _overboughtLevel.Value;
	set => _overboughtLevel.Value = value;
}

/// <summary>
/// Candle type consumed by the strategy.
/// </summary>
public DataType CandleType
{
	get => _candleType.Value;
	set => _candleType.Value = value;
}

/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
	return [(Security, CandleType)];
}

/// <inheritdoc />
protected override void OnReseted()
{
	base.OnReseted();

	_previousCandle = null;
	_lastAtrValue = 0m;
	_averageBodyValue = 0m;
	_pipSize = 0m;
	_pipSizeInitialized = false;
	ResetLongRiskLevels();
	ResetShortRiskLevels();
}

/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
	base.OnStarted2(time);

	Volume = TradeVolume;

	_rsi = new RelativeStrengthIndex
	{
		Length = RsiPeriod
	};

_movingAverage = new SMA
{
	Length = MovingAveragePeriod
};

_atr = new AverageTrueRange
{
	Length = AtrPeriod
};

_bodyAverage = new SMA
{
	Length = AverageBodyPeriod
};

var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _movingAverage, _atr, ProcessCandle)
.Start();

StartProtection(null, null);
}

/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
	base.OnOwnTradeReceived(trade);

	var order = trade.Order;
	if (order == null)
	return;

	if (order.Side == Sides.Buy)
	{
		if (Position > 0m)
		{
			// Long exposure established, compute fresh protective levels.
			_longEntryPrice = trade.Trade.Price;
			InitializeLongRiskLevels(trade.Trade.Price);
		}
		else if (Position >= 0m)
		{
			// Short exposure was reduced or closed.
			ResetShortRiskLevels();
		}
	}
	else if (order.Side == Sides.Sell)
	{
		if (Position < 0m)
		{
			// Short exposure established, compute protective levels.
			_shortEntryPrice = trade.Trade.Price;
			InitializeShortRiskLevels(trade.Trade.Price);
		}
		else if (Position <= 0m)
		{
			// Long exposure was reduced or closed.
			ResetLongRiskLevels();
		}
	}
}

/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
	base.OnPositionReceived(position);

	if (Position == 0m)
	{
		// Flat state clears pending protective levels.
		ResetLongRiskLevels();
		ResetShortRiskLevels();
	}
}

private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal maValue, decimal atrValue)
{
	if (candle.State != CandleStates.Finished)
	return;

	if (!_pipSizeInitialized)
	InitializePipSize();

	_lastAtrValue = atrValue;
	UpdateAverageBody(candle);
	ManageOpenPositions(candle);

	if (!_rsi.IsFormed || !_movingAverage.IsFormed || !_atr.IsFormed)
	{
		_previousCandle = candle;
		return;
	}

if (_previousCandle == null)
{
	_previousCandle = candle;
	return;
}

if (!IsSpreadWithinLimit())
{
	_previousCandle = candle;
	return;
}

var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var minimumBody = _averageBodyValue > 0m ? _averageBodyValue : 0m;

var bullishEngulfing = candle.ClosePrice > candle.OpenPrice &&
_previousCandle.ClosePrice < _previousCandle.OpenPrice &&
candle.OpenPrice <= _previousCandle.ClosePrice &&
bodySize >= minimumBody;

var bearishEngulfing = candle.ClosePrice < candle.OpenPrice &&
_previousCandle.ClosePrice > _previousCandle.OpenPrice &&
candle.OpenPrice >= _previousCandle.ClosePrice &&
bodySize >= minimumBody;

if (bullishEngulfing && rsiValue <= OversoldLevel && candle.ClosePrice >= maValue)
{
	TryOpenLong(candle.ClosePrice);
}
else if (bearishEngulfing && rsiValue >= OverboughtLevel && candle.ClosePrice <= maValue)
{
	TryOpenShort(candle.ClosePrice);
}

_previousCandle = candle;
}

private void TryOpenLong(decimal price)
{

	if (Position > 0m)
	return;

	if (ShouldBlockByRisk(price))
	return;

	if (Position < 0m)
	{
		// Close the opposing short exposure before flipping direction.
		BuyMarket(Math.Abs(Position));
		return;
	}

var volume = GetTradeVolume();
if (volume <= 0m)
return;

// Market order is used to replicate the MetaTrader execution style.
BuyMarket(volume);
}

private void TryOpenShort(decimal price)
{
	if (Position < 0m)
	return;

	if (ShouldBlockByRisk(price))
	return;

	if (Position > 0m)
	{
		// Close the opposing long exposure before flipping direction.
		SellMarket(Math.Abs(Position));
		return;
	}

var volume = GetTradeVolume();
if (volume <= 0m)
return;

SellMarket(volume);
}

private void ManageOpenPositions(ICandleMessage candle)
{
	if (Position > 0m)
	{
		// Manage protective logic for long exposure.
		var exitVolume = Position;

		if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
		{
			SellMarket(exitVolume);
			return;
		}

	if (_longTakeProfitPrice.HasValue && candle.HighPrice >= _longTakeProfitPrice.Value)
	{
		SellMarket(exitVolume);
		return;
	}

if (UseTrailingStop && _longTrailingStopPrice.HasValue && _lastAtrValue > 0m)
{
	var candidate = candle.ClosePrice - _lastAtrValue * TrailingStopAtrMultiplier;
	if (candidate > _longTrailingStopPrice.Value)
	_longTrailingStopPrice = candidate;

	if (candle.LowPrice <= _longTrailingStopPrice.Value)
	{
		SellMarket(exitVolume);
		return;
	}
}
}
else if (Position < 0m)
{
	// Manage protective logic for short exposure.
	var exitVolume = Math.Abs(Position);

	if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
	{
		BuyMarket(exitVolume);
		return;
	}

if (_shortTakeProfitPrice.HasValue && candle.LowPrice <= _shortTakeProfitPrice.Value)
{
	BuyMarket(exitVolume);
	return;
}

if (UseTrailingStop && _shortTrailingStopPrice.HasValue && _lastAtrValue > 0m)
{
	var candidate = candle.ClosePrice + _lastAtrValue * TrailingStopAtrMultiplier;
	if (candidate < _shortTrailingStopPrice.Value)
	_shortTrailingStopPrice = candidate;

	if (candle.HighPrice >= _shortTrailingStopPrice.Value)
	{
		BuyMarket(exitVolume);
		return;
	}
}
}
}

private decimal GetTradeVolume()
{
	var volume = TradeVolume;
	var security = Security;
	if (security == null)
	return volume;

	var step = security.VolumeStep;
	if (step != null && step.Value > 0m)
	{
		var steps = Math.Max(1m, Math.Round(volume / step.Value, MidpointRounding.AwayFromZero));
		volume = steps * step.Value;
	}

	var minVolume = security.MinVolume;
	if (minVolume.HasValue && volume < minVolume.Value)
		volume = minVolume.Value;

	var maxVolume = security.MaxVolume;
	if (maxVolume.HasValue && volume > maxVolume.Value)
		volume = maxVolume.Value;

return volume;
}

private bool ShouldBlockByRisk(decimal price)
{
	if (MaxRiskPercent <= 0m)
	return false;

	if (_lastAtrValue <= 0m || StopLossAtrMultiplier <= 0m || price <= 0m)
	return false;

	var potentialLoss = _lastAtrValue * StopLossAtrMultiplier;
	var riskPercent = potentialLoss / price * 100m;
	return riskPercent > MaxRiskPercent;
}

private void UpdateAverageBody(ICandleMessage candle)
{
	var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
	var value = _bodyAverage.Process(new DecimalIndicatorValue(_bodyAverage, body, candle.OpenTime));
	if (value.IsFinal)
		_averageBodyValue = value.GetValue<decimal>();
}

private void InitializePipSize()
{
	_pipSize = GetPipSize();
	_pipSizeInitialized = _pipSize > 0m;
}

private decimal GetPipSize()
{
	var security = Security;
	if (security == null)
	return 0.0001m;

	var step = security.PriceStep;
	if (step != null && step.Value > 0m)
		return step.Value;

	var decimals = security.Decimals;
	if (decimals.HasValue)
	{
		var pow = Math.Pow(10, -decimals.Value);
		return Convert.ToDecimal(pow);
	}

return 0.0001m;
}

private bool IsSpreadWithinLimit()
{
	if (MaxSpreadPoints <= 0m)
	return true;

	var security = Security;
	if (security == null)
	return true;

	var bestBid = GetSecurityValue<decimal?>(Level1Fields.BestBidPrice);
	var bestAsk = GetSecurityValue<decimal?>(Level1Fields.BestAskPrice);
	if (!bestBid.HasValue || !bestAsk.HasValue || !_pipSizeInitialized || _pipSize <= 0m)
	return true;

	var spreadPoints = (bestAsk.Value - bestBid.Value) / _pipSize;
	return spreadPoints <= MaxSpreadPoints;
}

private void InitializeLongRiskLevels(decimal entryPrice)
{
	if (_lastAtrValue <= 0m)
	{
		ResetLongRiskLevels();
		return;
	}

_longStopPrice = StopLossAtrMultiplier > 0m ? entryPrice - _lastAtrValue * StopLossAtrMultiplier : null;
_longTakeProfitPrice = TakeProfitAtrMultiplier > 0m ? entryPrice + _lastAtrValue * TakeProfitAtrMultiplier : null;
_longTrailingStopPrice = UseTrailingStop && TrailingStopAtrMultiplier > 0m ? entryPrice - _lastAtrValue * TrailingStopAtrMultiplier : null;
}

private void InitializeShortRiskLevels(decimal entryPrice)
{
	if (_lastAtrValue <= 0m)
	{
		ResetShortRiskLevels();
		return;
	}

_shortStopPrice = StopLossAtrMultiplier > 0m ? entryPrice + _lastAtrValue * StopLossAtrMultiplier : null;
_shortTakeProfitPrice = TakeProfitAtrMultiplier > 0m ? entryPrice - _lastAtrValue * TakeProfitAtrMultiplier : null;
_shortTrailingStopPrice = UseTrailingStop && TrailingStopAtrMultiplier > 0m ? entryPrice + _lastAtrValue * TrailingStopAtrMultiplier : null;
}

private void ResetLongRiskLevels()
{
	_longEntryPrice = null;
	_longStopPrice = null;
	_longTakeProfitPrice = null;
	_longTrailingStopPrice = null;
}

private void ResetShortRiskLevels()
{
	_shortEntryPrice = null;
	_shortStopPrice = null;
	_shortTakeProfitPrice = null;
	_shortTrailingStopPrice = null;
}
}