Открыть на GitHub

Стратегия Hans123 Trader

Описание

Hans123 Trader — портированная в StockSharp версия советника Hans123_Trader для MetaTrader 5. Алгоритм отслеживает диапазон из RangeLength свечей, в торговом окне выставляет отложенные стоп-заявки на пробой границ диапазона и управляет позицией через стоп-лосс, тейк-профит и трейлинг.

Логика работы

  1. Подписка на выбранный тип свечей и ожидание заполнения окна из RangeLength баров.
  2. На каждой завершённой свече:
    • Пересчитываются максимум и минимум последнего диапазона.
    • Если текущее время находится вне интервала [StartHour, EndHour), все отложенные заявки снимаются и новые не выставляются.
    • Внутри торгового окна снимаются старые заявки и размещаются новые:
      • Buy Stop на верхней границе диапазона объёмом OrderVolume;
      • Sell Stop на нижней границе диапазона объёмом OrderVolume.
  3. При открытии позиции:
    • Противоположный ордер отменяется, чтобы исключить хеджирование.
    • В зависимости от настроек выставляются защитные стоп- и лимит-заявки.
  4. В процессе сопровождения сделки:
    • Как только цена проходит расстояние TrailingStopPips + TrailingStepPips, защитный стоп подтягивается на расстояние TrailingStopPips в направлении прибыли.
    • При закрытии позиции все защитные ордера снимаются, внутреннее состояние сбрасывается.

Параметры

Параметр Назначение Значение по умолчанию
OrderVolume Объём входа при пробое. 0.1
RangeLength Количество свечей в диапазоне. 80
StopLossPips Размер стоп-лосса в пунктах (0 — отключено). 50
TakeProfitPips Размер тейк-профита в пунктах (0 — отключено). 50
TrailingStopPips Базовое расстояние трейлинг-стопа в пунктах (0 — без трейлинга). 10
TrailingStepPips Дополнительное смещение, после которого трейлинг обновляется; должно быть > 0 при активном трейлинге. 5
StartHour Час (UTC) начала торгового окна, включительно. 6
EndHour Час (UTC) окончания торгового окна, не включается. 10
CandleType Тип и таймфрейм свечей. 1 час

Практические замечания

  • Размер пункта вычисляется динамически: для инструментов с 3 или 5 знаками после запятой применяется десятикратная поправка.
  • Если стоп-лосс отключён, трейлинг-стоп создаёт защитный ордер только после прохождения ценой активационного расстояния.
  • Следите за соответствием OrderVolume торговым ограничениям выбранного портфеля и инструмента.
  • В стратегию встроена отрисовка свечей, диапазона и сделок для визуального контроля.

Отличия от версии MT5

  • Используются высокоуровневые методы StockSharp (BuyStop, SellStop, SellLimit, BuyLimit) вместо прямых MT5-запросов.
  • Параметры оформлены через StrategyParam, что позволяет подключать оптимизацию без дополнительного кода.
  • Отложенные заявки обновляются по событию закрытия свечи, так как в StockSharp обработка построена на свечном фиде.

Рекомендации по использованию

  1. Привяжите стратегию к нужному инструменту и портфелю, убедитесь, что таймфрейм соответствует исходным настройкам.
  2. Отрегулируйте границы торгового окна и дистанции защитных ордеров под ликвидность конкретного рынка.
  3. Запустите стратегию и контролируйте графические элементы (диапазон, сделки) для проверки корректности поведения.
  4. При необходимости выполняйте оптимизацию параметров с использованием стандартных средств тестирования StockSharp.
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;
	}
}