Ver en GitHub

Estrategia Hans123 Trader

Descripción general

Hans123 Trader es un sistema de ruptura convertido desde el asesor experto original de MetaTrader 5 Hans123_Trader. La estrategia escanea un rango de precios dinámico y coloca órdenes pendientes de stop durante una ventana intradía configurable. Los stops de protección, objetivos de beneficio y reglas de trailing replican la lógica MQL5 para que el puerto StockSharp se comporte como el robot original.

Conceptos principales

  • Ruptura de rango – utiliza el máximo más alto y el mínimo más bajo de las últimas N velas para definir el canal de ruptura.
  • Filtro de tiempo – solo evalúa señales entre las horas de inicio y fin para evitar el ruido nocturno.
  • Órdenes pendientes sincrónicas – actualiza las órdenes buy stop y sell stop en cada vela completada dentro de la ventana de trading.
  • Control de riesgo – distancias opcionales de stop-loss, take-profit y trailing stop expresadas en pips.
  • Trailing dinámico – una vez que el precio recorre la distancia de trailing stop más trailing step, el stop de protección se ajusta para asegurar ganancias.

Lógica de trading

  1. Suscribirse a la serie de velas seleccionada y esperar a que se forme la ventana del indicador RangeLength.
  2. En cada vela finalizada:
    • Actualizar el canal de máximo/mínimo de 80 barras (configurable).
    • Omitir el procesamiento si el tiempo actual está fuera del intervalo [StartHour, EndHour).
    • Cancelar las órdenes de entrada existentes y colocar nuevas órdenes stop:
      • Buy stop en el máximo del rango por OrderVolume.
      • Sell stop en el mínimo del rango por OrderVolume.
  3. Cuando se ejecuta una orden de entrada:
    • Cancelar la orden pendiente opuesta.
    • Registrar órdenes de stop-loss y take-profit si las distancias en pips correspondientes son mayores que cero.
  4. Mientras haya una posición abierta:
    • Si el precio avanza al menos TrailingStopPips + TrailingStepPips, mover el stop de protección hacia el mercado en TrailingStopPips.
    • Las órdenes de protección se cancelan automáticamente cuando la posición vuelve a plano.

Parámetros

Nombre Descripción Valor predeterminado
OrderVolume Tamaño de la orden para entradas en ruptura. 0.1
RangeLength Número de velas en el canal de ruptura. 80
StopLossPips Distancia del stop-loss en pips (0 desactiva el stop). 50
TakeProfitPips Distancia del take-profit en pips (0 desactiva el objetivo). 50
TrailingStopPips Distancia del trailing stop en pips (0 desactiva el trailing). 10
TrailingStepPips Pips adicionales necesarios antes de que se actualice el trailing stop. Debe ser positivo cuando el trailing está habilitado. 5
StartHour Hora del día inclusiva (UTC) en que comienzan las órdenes de ruptura. 6
EndHour Hora del día exclusiva (UTC) en que se detienen las órdenes de ruptura. 10
CandleType Tipo de datos de vela y marco temporal de trabajo. Velas de 1 hora

Notas prácticas

  • El tamaño del pip se adapta a los decimales del instrumento (los símbolos forex de 3/5 dígitos reciben el ajuste habitual ×10).
  • Los trailing stops solo se crean después de que una posición recorre la distancia de activación; si StopLossPips es cero, el stop inicial se omite hasta que se cumplan las condiciones de trailing.
  • Mantenga los permisos del portafolio alineados con el OrderVolume seleccionado y el tamaño del contrato del instrumento.
  • La conversión StockSharp usa ayudas gráficas para visualizar velas, el canal y las operaciones para depuración.

Diferencias con la versión MQL5

  • Las órdenes de stop y objetivo se registran a través de los ayudantes de alto nivel de StockSharp en lugar de las solicitudes de trading de MetaTrader.
  • Los valores predeterminados de volumen siguen siendo idénticos (0.1 lotes) pero pueden optimizarse mediante metadatos StrategyParam.
  • Las órdenes pendientes se actualizan en cada vela completada en lugar de esperar actualizaciones a nivel de tick, adaptándose al modelo de eventos de StockSharp.

Uso

  1. Adjuntar la estrategia a un par de portafolio/instrumento y verificar que la suscripción de velas coincida con el marco temporal deseado.
  2. Ajustar los parámetros según la volatilidad del instrumento y los límites de sesión.
  3. Iniciar la estrategia; monitorear la superposición del área del gráfico para confirmar los niveles de ruptura y las operaciones ejecutadas.
  4. Usar los parámetros integrados para optimización dentro del entorno de pruebas de StockSharp si se desea.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Hans123 breakout strategy converted from MQL5.
/// Collects an intraday range and trades pending stop orders within a trading window.
/// Applies configurable stop-loss, take-profit, and trailing protection.
/// </summary>
public class Hans123TraderStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _rangeLength;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest = null!;
	private Lowest _lowest = null!;
	private decimal _entryPrice;
	private decimal _pipSize;
	private decimal _highestSinceEntry;
	private decimal _lowestSinceEntry;

	/// <summary>
	/// Volume used for breakout orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Number of candles that form the breakout range.
	/// </summary>
	public int RangeLength
	{
		get => _rangeLength.Value;
		set => _rangeLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Extra move (in pips) before trailing activates again.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Start hour (inclusive) of the trading window.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// End hour (exclusive) of the trading window.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="Hans123TraderStrategy"/> class.
	/// </summary>
	public Hans123TraderStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetDisplay("Order Volume", "Breakout order volume", "General")
			
			.SetOptimize(0.1m, 2m, 0.1m);

		_rangeLength = Param(nameof(RangeLength), 40)
			.SetGreaterThanZero()
			.SetDisplay("Range Length", "Candles in breakout range", "General")
			
			.SetOptimize(40, 120, 10);

		_stopLossPips = Param(nameof(StopLossPips), 50)
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk Management")
			
			.SetOptimize(0, 150, 10);

		_takeProfitPips = Param(nameof(TakeProfitPips), 50)
			.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk Management")
			
			.SetOptimize(0, 200, 10);

		_trailingStopPips = Param(nameof(TrailingStopPips), 10)
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk Management")
			
			.SetOptimize(0, 100, 5);

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetDisplay("Trailing Step (pips)", "Extra pips before trailing updates", "Risk Management")
			
			.SetOptimize(0, 50, 5);

		_startHour = Param(nameof(StartHour), 0)
			.SetDisplay("Start Hour", "Hour (UTC) when orders can be placed", "Schedule")
			
			.SetOptimize(0, 23, 1);

		_endHour = Param(nameof(EndHour), 24)
			.SetDisplay("End Hour", "Hour (UTC) when orders stop", "Schedule")
			
			.SetOptimize(1, 24, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(3).TimeFrame())
			.SetDisplay("Candle Type", "Working candle timeframe", "General");
	}

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

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

		_highest = null;
		_lowest = null;
		_entryPrice = 0m;
		_pipSize = 0m;
		_highestSinceEntry = 0m;
		_lowestSinceEntry = 0m;
	}

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


		_pipSize = CalculatePipSize();

		_highest = new Highest { Length = RangeLength };
		_lowest = new Lowest { Length = RangeLength };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(_highest, _lowest, ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _highest);
			DrawIndicator(area, _lowest);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Check protective levels
		CheckProtection(candle);

		if (!_highest.IsFormed || !_lowest.IsFormed)
			return;

		if (!IsWithinTradingWindow(candle.OpenTime))
			return;

		if (OrderVolume <= 0m || highest <= lowest)
			return;

		// Track extremes for trailing
		if (Position > 0 && candle.HighPrice > _highestSinceEntry)
			_highestSinceEntry = candle.HighPrice;
		if (Position < 0 && (_lowestSinceEntry == 0 || candle.LowPrice < _lowestSinceEntry))
			_lowestSinceEntry = candle.LowPrice;

		// Breakout entry logic
		if (Position == 0)
		{
			if (candle.HighPrice >= highest)
			{
				BuyMarket(OrderVolume);
			}
			else if (candle.LowPrice <= lowest)
			{
				SellMarket(OrderVolume);
			}
		}
	}

	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (trade?.Trade == null) return;
		if (Position != 0m && _entryPrice == 0m)
		{
			_entryPrice = trade.Trade.Price;
			_highestSinceEntry = trade.Trade.Price;
			_lowestSinceEntry = trade.Trade.Price;
		}
		if (Position == 0m)
		{
			_entryPrice = 0m;
			_highestSinceEntry = 0m;
			_lowestSinceEntry = 0m;
		}
	}

	private void CheckProtection(ICandleMessage candle)
	{
		if (Position == 0 || _entryPrice == 0m)
			return;

		var stopDist = StopLossPips > 0 ? StopLossPips * _pipSize : 0m;
		var takeDist = TakeProfitPips > 0 ? TakeProfitPips * _pipSize : 0m;
		var trailDist = TrailingStopPips > 0 ? TrailingStopPips * _pipSize : 0m;
		var activation = (TrailingStopPips + TrailingStepPips) * _pipSize;

		if (Position > 0)
		{
			// Stop loss
			if (stopDist > 0m && candle.LowPrice <= _entryPrice - stopDist)
			{
				SellMarket(Math.Abs(Position));
				return;
			}
			// Take profit
			if (takeDist > 0m && candle.HighPrice >= _entryPrice + takeDist)
			{
				SellMarket(Math.Abs(Position));
				return;
			}
			// Trailing stop
			if (trailDist > 0m && _highestSinceEntry - _entryPrice > activation)
			{
				var trailStop = _highestSinceEntry - trailDist;
				if (candle.LowPrice <= trailStop)
				{
					SellMarket(Math.Abs(Position));
					return;
				}
			}
		}
		else if (Position < 0)
		{
			if (stopDist > 0m && candle.HighPrice >= _entryPrice + stopDist)
			{
				BuyMarket(Math.Abs(Position));
				return;
			}
			if (takeDist > 0m && candle.LowPrice <= _entryPrice - takeDist)
			{
				BuyMarket(Math.Abs(Position));
				return;
			}
			if (trailDist > 0m && _lowestSinceEntry > 0m && _entryPrice - _lowestSinceEntry > activation)
			{
				var trailStop = _lowestSinceEntry + trailDist;
				if (candle.HighPrice >= trailStop)
				{
					BuyMarket(Math.Abs(Position));
					return;
				}
			}
		}
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;
		return step;
	}

	private bool IsWithinTradingWindow(DateTimeOffset time)
	{
		return time.Hour >= StartHour && time.Hour < EndHour;
	}
}