Открыть на GitHub

Стратегия AbsolutelyNoLagLWMA Range Channel TM Plus

Общее описание

Стратегия является портом эксперт-советника MetaTrader «Exp_AbsolutelyNoLagLwma_Range_Channel_Tm_Plus». Она торгует канал, построенный на двойном сглаживании максимумов и минимумов свечей линейно-взвешенным скользящим средним (LWMA). В версии для StockSharp полностью сохранена исходная логика: сигналы рассчитываются только по закрывшимся свечам выбранного таймфрейма, состояние канала кодируется так же, как в оригинальном индикаторе, а управление позицией выполняется в порядке «выход по времени → выход по индикатору → открытие новой позиции».

Построение индикатора

  1. После закрытия каждой свечи значения High и Low подаются в первую LWMA с длиной Length.
  2. Результат первой LWMA повторно сглаживается второй LWMA той же длины, что воспроизводит фильтр AbsolutelyNoLagLWMA.
  3. Итоговые верхняя и нижняя границы сравниваются с ценой закрытия:
    • Закрытие выше верхней границы → состояние «бычий пробой».
    • Закрытие ниже нижней границы → состояние «медвежий пробой».
    • Закрытие внутри канала → нейтральное состояние.
  4. Стратегия хранит последнюю последовательность состояний канала. Параметр SignalBar задаёт, по какой свече анализировать сигнал (0 — последняя закрытая свеча), полностью повторяя одноимённый параметр из MQL.

Интерпретация сигналов

  • Открытие лонга (EnableBuyEntries) — срабатывает, когда на свече SignalBar + 1 фиксируется бычий пробой, а свеча с индексом SignalBar уже вернулась внутрь канала. Это копирует проверку «пробой на предыдущей свече» оригинального эксперта.
  • Открытие шорта (EnableSellEntries) — зеркальная логика для медвежьего пробоя.
  • Закрытие лонга (EnableBuyExits) — медвежий пробой на контрольной свече закрывает лонговые позиции, если текущая свеча ещё не инициировала выход по времени.
  • Закрытие шорта (EnableSellExits) — бычий пробой закрывает короткие позиции, если они не были закрыты из-за ограничения по времени.

Управление позицией

  • Объём заявок задаётся параметром OrderVolume. При развороте стратегия добавляет абсолютное значение текущей позиции, чтобы не оставлять остаточного объёма.
  • Стоп-лосс / тейк-профит задаются параметрами StopLossPoints и TakeProfitPoints в пунктах инструмента. Положительные значения пересчитываются в абсолютный ценовой сдвиг через PriceStep и передаются в StartProtection; нулевые значения отключают соответствующую защиту.
  • Ограничение по времени повторяет логику TimeTrade/nTime: параметры UseTimeExit и HoldingLimit задают необходимость и длительность удержания позиции. Проверка выполняется на каждой закрывшейся свече до обработки индикаторных сигналов.
  • Отметка времени позиции — стратегия сохраняет время последней сделки, приведшей к появлению лонга или шорта, и использует эти значения при расчёте ограничения по времени.

Параметры

Параметр Назначение
Length Длина обеих LWMA, формирующих канал.
SignalBar Индекс свечи для анализа сигнала (0 — последняя закрытая).
CandleType Таймфрейм, по которому строятся индикатор и сигналы.
OrderVolume Объём новых входов.
StopLossPoints Размер стоп-лосса в шагах цены (0 — отключено).
TakeProfitPoints Размер тейк-профита в шагах цены (0 — отключено).
EnableBuyEntries Разрешение на открытие длинных позиций.
EnableSellEntries Разрешение на открытие коротких позиций.
EnableBuyExits Разрешение на закрытие лонгов по сигналу индикатора.
EnableSellExits Разрешение на закрытие шортов по сигналу индикатора.
UseTimeExit Включение выхода по времени удержания позиции.
HoldingLimit Максимальное время удержания позиции.

Замечания

  • Канал рассчитывается строго по максимумам и минимумам свечей, идентично индикатору AbsolutelyNoLagLwma_Range_Channel из комплекта MQL.
  • Стратегия игнорирует незакрытые свечи, что исключает ложные срабатывания.
  • Значение SignalBar = 0 соответствует типовой конфигурации MT5; значение 1 (по умолчанию) повторяет задержку в одну свечу из оригинального эксперта.
  • Если у инструмента отсутствует PriceStep, параметры стоп-лосса и тейк-профита не применяются, что полностью повторяет поведение исходного скрипта при нулевых значениях.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// LWMA range channel breakout strategy.
/// Uses fast/slow WMA crossover with mid-channel exit logic.
/// </summary>
public class AbsolutelyNoLagLwmaRangeChannelTmPlusStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public AbsolutelyNoLagLwmaRangeChannelTmPlusStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast WMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow WMA period", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = null;
		_prevSlow = null;

		var fastWma = new WeightedMovingAverage { Length = FastPeriod };
		var slowWma = new WeightedMovingAverage { Length = SlowPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;

		_prevFast = fast;
		_prevSlow = slow;

		// Crossover: fast crosses above slow → buy
		if (!prevAbove && currAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		// Crossover: fast crosses below slow → sell
		else if (prevAbove && !currAbove)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}