Открыть на GitHub

Стратегия Fractured Fractals (MT4)

Подробный порт на C# классического советника MetaTrader 4 MQL/7696/Fractured_fractals.mq4. Стратегия отслеживает подтверждённые фракталы Билла Вильямса, выставляет стоп-ордера на пробой и сопровождает позицию по предыдущим фрактальным экстремумам. Размер позиции рассчитывается по риску на сделку с учётом параметра DecreaseFactor, уменьшающего объём после серии убытков.

Подробности

  • Источник: конвертация MQL/7696/Fractured_fractals.mq4.
  • Рыночный режим: продолжение пробоя, применимо к любым инструментам с устойчивыми фрактальными паттернами.
  • Типы ордеров: стоп-ордера для входа и стоп-ордера для защиты позиции.
  • Манименеджмент: процент риска через MaximumRiskPercent, адаптивное снижение объёма при просадке через DecreaseFactor.
  • Параметры по умолчанию:
    • MaximumRiskPercent = 2%
    • DecreaseFactor = 3
    • CandleType = часовой таймфрейм
  • Основные индикаторы: встроенная реализация пятисвечных фракталов Вильямса.
  • Тип стратегии: симметричный лонг/шорт на пробой с фрактальным трейлинг-стопом.

Логика стратегии

Обнаружение фракталов

  • Поддерживается скользящее окно из пяти максимумов и минимумов свечей, повторяющее поведение iFractals в MetaTrader.
  • Вверх направленный фрактал фиксируется, когда центральный максимум выше соседних максимумов; вниз направленный фрактал — когда центральный минимум ниже соседних минимумов.
  • Новые значения сохраняются вместе с тремя предыдущими уровнями, что соответствует буферам cfu, pfu и pfu.1 оригинального эксперта и используется при сравнении и перемещении стопов.

Условия входа

  • Для лонга требуется новый верхний фрактал выше предыдущего и актуальный нижний фрактал, определяющий уровень риска. Создаётся buy stop с надбавкой на спред и защитным стопом ниже противоположного фрактала.
  • Шорт зеркален: более низкий нижний фрактал и более высокий верхний фрактал дают sell stop и защитный стоп выше фрактала плюс спред.
  • В каждый момент активен только один отложенный ордер в направлении. При нарушении фрактальной структуры ордер немедленно снимается.

Управление стопами

  • После открытия позиции защитный стоп переносится на предыдущий фрактал со стороны входа с поправкой на текущий спред. Стоп двигается только в прибыльную сторону.
  • При развороте или закрытии позиции неиспользуемый стоп-ордер отменяется, чтобы избежать зависших заявок.

Управление риском

  • CalculateOrderVolume повторяет расчёт из советника: объём равен отношению допустимого денежного риска к расстоянию между ценой входа и защитным стопом.
  • В первую очередь используется Portfolio.CurrentValue; при отсутствии оценки портфеля берётся Volume, умноженный на цену.
  • После двух и более убыточных сделок объём уменьшается на величину losses / DecreaseFactor, полностью воспроизводя поведение DecreaseFactor в MT4.

Учёт торговых циклов

  • OnOwnTradeReceived агрегирует исполнения в торговые циклы, отслеживает плавающий PnL и обновляет счётчик убытков после полной фиксации позиции. Это заменяет вызовы HistoryTotal, использовавшиеся в оригинальном коде.

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

  1. Подключите стратегию к нужной паре инструмент/портфель и подберите CandleType, соответствующий настройкам эксперта в MT4.
  2. Обеспечьте получение стакана/котировок первого уровня — оценка спреда использует лучшие bid/ask и при их отсутствии возвращается к PriceStep.
  3. Предполагается поддержка серверных стоп-ордеров. При необходимости замените BuyStop/SellStop на рыночные заявки в нужных методах.
  4. Обработка ведётся по закрытию свечей, поэтому внутрисвечные фракталы будут отрабатываться только на закрытии бара, аналогично оригинальному советнику.
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>
/// Fractured Fractals strategy - Highest/Lowest channel breakout with ATR filter.
/// Buys when close crosses above channel midpoint and ATR confirms volatility.
/// Sells when close crosses below channel midpoint.
/// </summary>
public class FracturedFractalsMql4Strategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<DataType> _candleType;

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

	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FracturedFractalsMql4Strategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 15)
			.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR lookback", "Indicators");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevMid = 0m; _hasPrev = false; }

	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;

		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;

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

		// Close crosses above midpoint = buy
		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Close crosses below midpoint = sell
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevClose = close;
		_prevMid = mid;
	}
}