Ver no GitHub

Estratégia Hans123 Trader

Visão geral

Hans123 Trader é um sistema de rompimento convertido do assessor especialista original do MetaTrader 5 Hans123_Trader. A estratégia verifica um intervalo de preços dinâmico e coloca ordens pendentes de stop durante uma janela intradiária configurável. Stops de proteção, metas de lucro e regras de trailing espelham a lógica MQL5 para que o port StockSharp se comporte como o robô fonte.

Conceitos principais

  • Rompimento de intervalo – usa a máxima mais alta e a mínima mais baixa das últimas N velas para definir o canal de rompimento.
  • Filtro de tempo – avalia sinais apenas entre as horas de início e fim para evitar ruído noturno.
  • Ordens pendentes síncronas – atualiza ordens buy stop e sell stop a cada vela completa dentro da janela de negociação.
  • Controle de risco – distâncias opcionais de stop-loss, take-profit e trailing stop expressas em pips.
  • Trailing dinâmico – assim que o preço percorre a distância de trailing stop mais trailing step, o stop de proteção é ajustado para garantir ganhos.

Lógica de negociação

  1. Subscrever a série de velas selecionada e aguardar a formação da janela do indicador RangeLength.
  2. A cada vela finalizada:
    • Atualizar o canal de máxima/mínima de 80 barras (configurável).
    • Pular o processamento se o horário atual estiver fora do intervalo [StartHour, EndHour).
    • Cancelar ordens de entrada existentes e colocar novas ordens stop:
      • Buy stop na máxima do intervalo por OrderVolume.
      • Sell stop na mínima do intervalo por OrderVolume.
  3. Quando uma ordem de entrada é executada:
    • Cancelar a ordem pendente oposta.
    • Registrar ordens de stop-loss e take-profit se as distâncias em pips correspondentes forem maiores que zero.
  4. Enquanto uma posição estiver aberta:
    • Se o preço avançar pelo menos TrailingStopPips + TrailingStepPips, mover o stop de proteção em direção ao mercado por TrailingStopPips.
    • As ordens de proteção são canceladas automaticamente quando a posição volta a zero.

Parâmetros

Nome Descrição Valor padrão
OrderVolume Tamanho da ordem para entradas em rompimento. 0.1
RangeLength Número de velas no canal de rompimento. 80
StopLossPips Distância do stop-loss em pips (0 desativa o stop). 50
TakeProfitPips Distância do take-profit em pips (0 desativa o alvo). 50
TrailingStopPips Distância do trailing stop em pips (0 desativa o trailing). 10
TrailingStepPips Pips adicionais necessários antes de o trailing stop ser atualizado. Deve ser positivo quando o trailing estiver habilitado. 5
StartHour Hora inclusiva do dia (UTC) em que as ordens de rompimento começam. 6
EndHour Hora exclusiva do dia (UTC) em que as ordens de rompimento param. 10
CandleType Tipo de dados de vela e período de trabalho. Velas de 1 hora

Notas práticas

  • O tamanho do pip se adapta aos decimais do instrumento (símbolos forex de 3/5 dígitos recebem o ajuste usual ×10).
  • Os trailing stops só são criados após uma posição percorrer a distância de ativação; se StopLossPips for zero, o stop inicial é omitido até que as condições de trailing sejam atendidas.
  • Manter as permissões do portfólio alinhadas com o OrderVolume selecionado e o tamanho do contrato do instrumento.
  • A conversão StockSharp usa auxiliares de gráfico para visualizar velas, o canal e operações para depuração.

Diferenças em relação à versão MQL5

  • As ordens de stop e alvo são registradas através dos auxiliares de alto nível do StockSharp, em vez de solicitações de negociação do MetaTrader.
  • Os valores padrão de volume permanecem idênticos (0.1 lotes), mas podem ser otimizados via metadados StrategyParam.
  • As ordens pendentes são atualizadas a cada vela completa, em vez de aguardar atualizações no nível de tick, adaptando-se ao modelo de eventos do StockSharp.

Uso

  1. Anexar a estratégia a um par de portfólio/instrumento e verificar se a subscrição de velas corresponde ao período desejado.
  2. Ajustar os parâmetros para a volatilidade do instrumento e os limites de sessão.
  3. Iniciar a estratégia; monitorar a sobreposição da área do gráfico para confirmar os níveis de rompimento e as operações executadas.
  4. Usar os parâmetros integrados para otimização dentro do ambiente de testes do StockSharp, se desejado.
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;
	}
}