Открыть на GitHub

Стратегия LeMan Signal

Обзор

Стратегия LeMan Signal является портом оригинального советника MetaTrader LeManSignal. Подход анализирует последние максимумы и минимумы в двух последовательных диапазонах для выявления возможного разворота тренда. При выполнении определённых условий открывается длинная или короткая позиция на следующей свече.

Принцип работы

  1. Стратегия наблюдает за завершёнными свечами выбранного таймфрейма.
  2. Для предыдущей свечи сравниваются максимумы и минимумы в двух последовательных диапазонах:
    • H1 и H2 – максимумы двух соседних диапазонов.
    • H3 и H4 – максимумы следующей пары диапазонов.
    • L1 и L2 – минимумы двух соседних диапазонов.
    • L3 и L4 – минимумы следующей пары диапазонов.
  3. Сигнал покупки формируется при выполнении условий H3 <= H4 и H1 > H2.
  4. Сигнал продажи формируется при выполнении условий L3 >= L4 и L1 < L2.
  5. Заявки исполняются по рынку, открытая противоположная позиция закрывается автоматически.
  6. Для управления рисками используется StartProtection со стоп‑лоссом 1% и тейк‑профитом 2%.

Параметры

  • Period – длина анализируемого периода индикатора.
  • Signal Bar – смещение, используемое для подтверждения сигнала (по умолчанию 1).
  • Candle Type – тип свечей для анализа.

Примечания

  • Стратегия реагирует только на завершённые свечи.
  • Внутренние буферы ограничены минимально необходимым количеством значений.
  • Для использования стратегии добавьте её в терминал 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>
/// Strategy based on LeManSignal indicator.
/// Opens long on buy signal and short on sell signal.
/// Signals are derived from high and low breakouts over two consecutive periods.
/// </summary>
public class LeManSignalStrategy : Strategy
{
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	/// <summary>
	/// Indicator period length.
	/// </summary>
	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	/// <summary>
	/// Offset to confirm signal.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public LeManSignalStrategy()
	{
		_period = Param(nameof(Period), 12)
			.SetDisplay("Period", "LeManSignal lookback period", "Indicator")
			.SetGreaterThanZero()
			
			.SetOptimize(5, 30, 5);

		_signalBar = Param(nameof(SignalBar), 1)
			.SetDisplay("Signal Bar", "Offset for confirmed signal", "Indicator")
			.SetNotNegative()
			
			.SetOptimize(0, 2, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
	}

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

		var warmup = new ExponentialMovingAverage { Length = 2 * Period + 3 };

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

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

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		var maxLen = 2 * Period + 3;
		if (_highs.Count > maxLen)
		{
			_highs.RemoveAt(0);
			_lows.RemoveAt(0);
		}

		if (_highs.Count < maxLen)
			return;

		var signal = GetSignal(SignalBar);

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (signal > 0 && Position <= 0)
			BuyMarket();
		else if (signal < 0 && Position >= 0)
			SellMarket();
	}

	private int GetSignal(int bar)
	{
		var size = _highs.Count;

		var bar1 = bar + 1;
		var bar2 = bar + 2;
		var bar1p = bar1 + Period;
		var bar2p = bar2 + Period;

		var h1 = Highest(size - bar1 - Period, Period);
		var h2 = Highest(size - bar1p - Period, Period);
		var h3 = Highest(size - bar2 - Period, Period);
		var h4 = Highest(size - bar2p - Period, Period);

		var l1 = Lowest(size - bar1 - Period, Period);
		var l2 = Lowest(size - bar1p - Period, Period);
		var l3 = Lowest(size - bar2 - Period, Period);
		var l4 = Lowest(size - bar2p - Period, Period);

		var buy = h3 <= h4 && h1 > h2;
		var sell = l3 >= l4 && l1 < l2;

		if (buy)
			return 1;
		if (sell)
			return -1;
		return 0;
	}

	private decimal Highest(int start, int length)
	{
		var max = decimal.MinValue;
		for (var i = start; i < start + length; i++)
		{
			var v = _highs[i];
			if (v > max)
				max = v;
		}
		return max;
	}

	private decimal Lowest(int start, int length)
	{
		var min = decimal.MaxValue;
		for (var i = start; i < start + length; i++)
		{
			var v = _lows[i];
			if (v < min)
				min = v;
		}
		return min;
	}
}