Открыть на GitHub

Стратегия "One-Two-Three Pattern"

Обзор

Стратегия повторяет советник MetaTrader 4 «1-2-3_forCodeBase_v01.mq4» автора Martes. Алгоритм анализирует закрытые свечи в поисках разворотной модели 1-2-3: два последовательных трендовых участка и завершающая коррекция. В портированной версии сохранены все правила исходного эксперта, включая кастомные индикаторы длины трендов (RelDownTrLen_forCodeBase_v01 и RelUpTrLen_forCodeBase_v01) и фильтрацию по MACD.

Для входа в лонг нужен свежий минимум (точка 3) возле текущей цены, предыдущий максимум (точка 2) и более древний минимум (точка 1). Предыдущий нисходящий тренд обязан быть как минимум в TrendRatio раз длиннее текущей восходящей коррекции, а MACD должен пересечь сигнал (или нулевую линию) снизу вверх и оставаться положительным в точке 3. Правила для шорта зеркальны. Стоп выставляется на один пункт за точкой 3, тейк-профит равен высоте предыдущего свинга, а опциональный трейлинг по пунктам подтягивает стоп при движении позиции в прибыль.

Правила торговли

  1. Подписка на заданный тип свечей (CandleType) и расчёт MACD (fast/slow/signal) по ценам закрытия.
  2. Поддержание кольцевого буфера тел свечей для поиска структуры 1-2-3. Минимумы определяются как локальные минимумы тела, максимумы — как локальные максимумы.
  3. Расчёт относительной длины трендов с помощью выпуклой оболочки, как в оригинальных индикаторах. Новейший нисходящий участок (нормирован к [0,1]) обязан превышать предыдущий восходящий участок не менее чем в TrendRatio раз (для шорта наоборот).
  4. Подтверждение сигнала MACD:
    • Лонг: MACD пересекает сигнал (или ноль) снизу вверх и значение MACD в точке 3 положительное.
    • Шорт: MACD пересекает сигнал (или ноль) сверху вниз и значение MACD в точке 3 отрицательное.
  5. Дополнительные фильтры:
    • Расстояние от текущей цены до точки 2 не превышает пяти пунктов.
    • Потенциальный стоп |point2 - point3| минимум 13 пунктов.
    • TakeProfitPips должен быть ≥ 10 — при меньшем значении торговля блокируется, как в оригинале.
  6. Управление ордерами:
    • Вход BuyMarket/SellMarket объёмом TradeVolume (прибавляется к текущей позиции при развороте).
    • Стоп-лосс = точка 3 ± один шаг цены.
    • Тейк-профит = цена входа ± |point2 - point3|.
    • Если TrailingStopPips > 0, стоп подтягивается на указанную дистанцию после того, как плавающая прибыль превысит эту величину.
  7. Закрытие по стопу, тейку или трейлингу. Одновременно может быть открыта только одна позиция.

Параметры

Параметр Тип Значение по умолчанию Описание
TakeProfitPips decimal 60 Наследованный параметр советника. Если установить меньше 10, торговля отключается.
TradeVolume decimal 0.5 Объём сделок в лотах MetaTrader.
TrailingStopPips decimal 30 Дистанция трейлинг-стопа в пунктах MetaTrader. 0 — отключить трейлинг.
TrendRatio decimal 4 Минимальное соотношение длины основного тренда к коррекции.
CandleType DataType H1 Тип свечей для расчёта паттерна и MACD.
MacdFast int 12 Период быстрой EMA в MACD.
MacdSlow int 26 Период медленной EMA в MACD.
MacdSignal int 9 Период сигнальной EMA.
PatternLookback int 100 Максимальное число свечей, сканируемых при поиске точек 1-2-3.

Особенности реализации

  • Логика индикаторов RelDownTrLen и RelUpTrLen перенесена дословно: выпуклая оболочка на телах свечей находит самый длинный монотонный сегмент и возвращает его относительную длину в диапазоне [0,1].
  • История свечей и значений MACD хранится в ограниченных буферах (до 600 элементов), что предотвращает рост памяти и обеспечивает достаточную глубину для анализа.
  • Стопы и цели сопровождаются вручную, как в MetaTrader: проверяются экстремумы свечей, а трейлинг подтягивается только при достаточном продвижении цены.
  • Свойство Volume синхронизируется с TradeVolume при сбросе и старте, поэтому оптимизация использует стандартный механизм стратегии.

Ссылки

  • Исходный советник: MQL/8131/1-2-3_forCodeBase_v01.mq4.
  • Индикаторы: RelDownTrLen_forCodeBase_v01.mq4, RelUpTrLen_forCodeBase_v01.mq4.
using System;







using StockSharp.Algo.Indicators;



using StockSharp.Algo.Strategies;



using StockSharp.BusinessEntities;



using StockSharp.Messages;







namespace StockSharp.Samples.Strategies;







public class OneTwoThreePatternStrategy : Strategy



{



	private readonly StrategyParam<int> _channelPeriod;



	private readonly StrategyParam<DataType> _candleType;







	private decimal _prevClose; private decimal _prevMid; private bool _hasPrev;



	private int _cooldown;







	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }



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







	public OneTwoThreePatternStrategy()



	{



		_channelPeriod = Param(nameof(ChannelPeriod), 20).SetDisplay("Channel Period", "Pattern lookback", "Indicators");



		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");



	}







	/// <inheritdoc />



	protected override void OnReseted()



	{



		base.OnReseted();



		_prevClose = default;



		_prevMid = default;



		_hasPrev = default;



		_cooldown = default;



	}







	/// <inheritdoc />



	/// <inheritdoc />

	protected override void OnStarted2(DateTime time)



	{



		base.OnStarted2(time);



		_hasPrev = false;



		var highest = new Highest { Length = ChannelPeriod };



		var lowest = new Lowest { Length = ChannelPeriod };



		var subscription = SubscribeCandles(CandleType);



		subscription.Bind(highest, lowest, ProcessCandle).Start();



	}







	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)



	{



		if (candle.State != CandleStates.Finished) return;



		if (!IsFormedAndOnlineAndAllowTrading()) return;



		var close = candle.ClosePrice;



		var mid = (highest + lowest) / 2;



		if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }



		if (_cooldown > 0)



		{



			_cooldown--;



			_prevClose = close; _prevMid = mid;



			return;



		}







		if (_prevClose <= _prevMid && close > mid && Position <= 0)



		{



			var volume = Volume + Math.Abs(Position);



			BuyMarket(volume);



			_cooldown = 6;



		}



		else if (_prevClose >= _prevMid && close < mid && Position >= 0)



		{



			var volume = Volume + Math.Abs(Position);



			SellMarket(volume);



			_cooldown = 6;



		}



		_prevClose = close; _prevMid = mid;



	}



}