Открыть на GitHub

IFS Fractals

Обзор

IFS Fractals — порт скрипта MetaTrader 5 IFS_Fractals. Исходный эксперт строил bitmap изображения «фрактального слова», многократно применяя 28 аффинных преобразований к облаку точек. Версия на StockSharp превращает тот же хаотический процесс в направленный осциллятор: координата X генерируемых точек масштабируется, сглаживается экспоненциальным скользящим средним (EMA) и используется как индикатор импульса для открытия длинных и коротких позиций.

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

Итерационная система (IFS)

  • Аффинные преобразования – каждая завершённая свеча запускает пакет итераций (количество задаётся параметром). На каждой итерации одно из 28 преобразований выбирается согласно вероятностям из оригинального кода (все по 35). Преобразование обновляет текущую точку (x, y) коэффициентами, перенесёнными из MQL5 без изменений.
  • Таблица вероятностей – при старте стратегия вычисляет накопленный массив вероятностей, что позволяет выбирать следующее преобразование одной случайной выборкой в пределах общей массы вероятности.

Формирование сигнала

  • Нормализация – координата X делится на тот же коэффициент масштабирования (50 по умолчанию), который использовался скриптом при проецировании фрактала на bitmap. Это удерживает сигнал в стабильном диапазоне независимо от цены инструмента.
  • EMA-сглаживание – нормализованный ряд поступает в EMA с настраиваемым периодом. Индикатор действует как фильтр нижних частот, выделяя доминирующее направление хаотических итераций.
  • Входы – когда EMA поднимается выше положительного порога, стратегия открывает или переворачивает позицию в лонг. Симметрично, если EMA опускается ниже отрицательного порога, открывается или переворачивается шорт.
  • Выходы – открытые лонги закрываются, когда EMA возвращается к порогу выхода или ниже; шорты закрываются, когда EMA поднимается выше отрицательного порога выхода. Таким образом формируется зона гистерезиса, предотвращающая частые развороты около нуля.

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

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

Параметры

  • Volume – объём заявок при входе по рынку.
  • Candle Type – тип свечей, по завершении которых запускаются итерации IFS (по умолчанию 5-минутные свечи).
  • Iterations – количество итераций IFS после каждой завершённой свечи.
  • Scale – делитель для координаты X перед передачей в EMA.
  • Entry Threshold – абсолютное значение EMA, необходимое для открытия позиции (положительное для лонгов, отрицательное зеркально для шортов).
  • Exit Threshold – значение EMA, при котором позиция закрывается при возврате сигнала к нулю.
  • EMA Period – период экспоненциального скользящего среднего, применяемого к нормализованному фрактальному сигналу.
  • Take Profit – абсолютная дистанция тейк-профита; 0 отключает.
  • Stop Loss – абсолютная дистанция стоп-лосса; 0 отключает.

Дополнительные замечания

  • Пока исходный код не модифицирован фиксированным сидом, каждая серия итераций даёт уникальную последовательность сделок, что повторяет случайность оригинального скрипта построения bitmap.
  • Стратегия не использует рыночные индикаторы: все данные генерируются из коэффициентов IFS, свечи лишь задают ритм итерациям.
  • В пакете присутствует только C#-реализация (папка CS/). Версия на Python отсутствует.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// IFS Fractals strategy: Williams %R oscillator crossover.
/// Buys when WPR crosses above oversold, sells when crosses below overbought.
/// </summary>
public class IfsFractalsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wprPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevWpr;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public IfsFractalsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_wprPeriod = Param(nameof(WprPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("WPR Period", "Williams %R period", "Indicators");
		_oversold = Param(nameof(Oversold), -85m)
			.SetDisplay("Oversold", "WPR oversold level", "Signals");
		_overbought = Param(nameof(Overbought), -15m)
			.SetDisplay("Overbought", "WPR overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevWpr = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevWpr = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var wpr = new WilliamsR { Length = WprPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(wpr, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevWpr < Oversold && wprValue >= Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevWpr > Overbought && wprValue <= Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevWpr = wprValue;
		_hasPrev = true;
	}
}