Открыть на GitHub

Стратегия ComFracti Fractal RSI

Обзор

ComFracti Fractal RSI — это порт советника MetaTrader ComFracti на платформу StockSharp. Алгоритм определяет направление с помощью фракталов Билла Уильямса на двух таймфреймах и фильтрует сигналы быстрым RSI, рассчитанным по дневным свечам. При появлении условий стратегия открывает единственную позицию, защищает её настраиваемыми уровнями стоп-лосса и тейк-профита и может дополнительно закрывать сделки при смене сигнала или по тайм-ауту удержания.

Параметры по умолчанию соответствуют оригинальному эксперту: торговый таймфрейм 15 минут, подтверждающий таймфрейм 1 час и RSI с периодом 3 по дневным свечам и цене открытия.

Логика торговли

  1. Определение направления по фракталам
    • Завершённые свечи торгового и старшего таймфреймов попадают в скользящее окно из пяти баров.
    • Параметры Primary*Shift и Higher*Shift задают, на сколько баров назад нужно смотреть при поиске последнего подтверждённого фрактала (значение 3 соответствует фракталу, подтверждённому три свечи назад).
    • Наличие только нижнего фрактала трактуется как бычий сигнал (+1), наличие только верхнего — как медвежий сигнал (-1).
  2. Фильтр дневным RSI
    • Индикатор RelativeStrengthIndex с периодом RsiPeriod (по умолчанию 3) работает на дневном таймфрейме и получает в качестве входа цену открытия, что повторяет настройку PRICE_OPEN в MetaTrader.
    • Для входа в лонг RSI должен быть ниже 50 - RsiBuyOffset, для входа в шорт — выше 50 + RsiSellOffset.
  3. Условия входа
    • Покупка: фракталы обоих таймфреймов дают значение +1, а RSI удовлетворяет условиям на покупку. Если стратегия находится в шорте или без позиции, она покупает требуемый объём, чтобы стать в лонг.
    • Продажа: фракталы обоих таймфреймов дают -1, а RSI подтверждает продажу. Если стратегия в лонге или без позиции, она продаёт объём, достаточный для перехода в шорт.
  4. Управление позицией
    • После изменения позиции рассчитываются уровни стоп-лосса и тейк-профита: StopLossPips и TakeProfitPips умножаются на размер пункта инструмента.
    • Позиция закрывается при достижении стопа/тейка, по истечении ExpiryMinutes или при включённом CloseOnOppositeSignal, если сигнал сменился на противоположный.

Параметры

Имя Описание Значение по умолчанию
Volume Объём заявки при каждом входе. 0.1
TakeProfitPips Дистанция тейк-профита в пунктах (0 — без тейка). 700
StopLossPips Дистанция стоп-лосса в пунктах (0 — без стопа). 2500
ExpiryMinutes Максимальное время удержания позиции в минутах (0 — без ограничения). 5555
CloseOnOppositeSignal Закрывать ли позицию при смене торгового сигнала. false
PrimaryBuyShift Смещение по барам для поиска бычьего фрактала на торговом таймфрейме. 3
HigherBuyShift Смещение по барам для поиска бычьего фрактала на старшем таймфрейме. 3
PrimarySellShift Смещение по барам для поиска медвежьего фрактала на торговом таймфрейме. 3
HigherSellShift Смещение по барам для поиска медвежьего фрактала на старшем таймфрейме. 3
RsiBuyOffset Необходимое отклонение RSI ниже 50 для допуска лонгов. 3
RsiSellOffset Необходимое отклонение RSI выше 50 для допуска шортов. 3
RsiPeriod Период дневного RSI. 3
CandleType Тип свечей торгового таймфрейма. 15-минутные свечи
HigherTimeFrame Тип свечей старшего таймфрейма для подтверждения тренда. 1-часовые свечи
DailyTimeFrame Тип свечей для расчёта дневного RSI. Дневные свечи

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

  • Используется высокоуровневое API подписки на свечи (SubscribeCandles().Bind(...)); индикаторы ведутся внутри стратегии и не добавляются в Strategy.Indicators.
  • Фракталы рассчитываются вспомогательным классом, который хранит скользящее окно из пяти свечей и обновляет сигнал только после подтверждения экстремума.
  • Значения RSI получаются через RelativeStrengthIndex.Process(...) по цене открытия, что полностью повторяет режим PRICE_OPEN в MetaTrader.
  • Стратегия всегда держит только одну позицию; рыночные заявки автоматически переворачивают позицию при необходимости.
  • Размер пункта определяется по Security.PriceStep и Security.Decimals; для инструментов с тремя и более знаками после запятой используется множитель ×10, имитирующий перевод Point в пункты (pip) в MetaTrader.

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

  • Значения смещения фракталов должны обеспечивать наличие достаточного количества завершённых свечей. При смещении 3 требуется минимум пять свечей на каждом таймфрейме.
  • Для инструментов с нестандартным шагом цены (индексы, акции) корректируйте TakeProfitPips и StopLossPips в соответствии с реальным пунктом.
  • Отключённый параметр CloseOnOppositeSignal полностью повторяет поведение оригинального советника — выход выполняется только по стопу, тейку или таймеру удержания.
  • Оригинальная логика расчёта лота на основе свободной маржи в StockSharp недоступна. При необходимости динамического управления объёмом используйте внешние модули или настраивайте параметр Volume вручную.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// ComFracti Fractal RSI: Fractal breakout with RSI filter and ATR stops.
/// </summary>
public class ComFractiFractalRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private decimal _prevHigh5;
	private decimal _prevLow5;
	private decimal _high1, _high2, _high3, _high4, _high5;
	private decimal _low1, _low2, _low3, _low4, _low5;
	private int _barCount;

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

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

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

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh5 = 0;
		_prevLow5 = 0;
		_high1 = _high2 = _high3 = _high4 = _high5 = 0;
		_low1 = _low2 = _low3 = _low4 = _low5 = 0;
	}

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

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh5 = 0;
		_prevLow5 = 0;
		_high1 = _high2 = _high3 = _high4 = _high5 = 0;
		_low1 = _low2 = _low3 = _low4 = _low5 = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		// Shift fractal window
		_high5 = _high4; _high4 = _high3; _high3 = _high2; _high2 = _high1;
		_high1 = candle.HighPrice;
		_low5 = _low4; _low4 = _low3; _low3 = _low2; _low2 = _low1;
		_low1 = candle.LowPrice;
		_barCount++;

		if (_barCount < 5 || atrVal <= 0)
			return;

		var close = candle.ClosePrice;

		// Detect fractal high (center bar _high3 is highest)
		var fractalUp = _high3 > _high1 && _high3 > _high2 && _high3 > _high4 && _high3 > _high5;
		// Detect fractal low (center bar _low3 is lowest)
		var fractalDown = _low3 < _low1 && _low3 < _low2 && _low3 < _low4 && _low3 < _low5;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || fractalDown)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || fractalUp)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (fractalDown && rsiVal < 45)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (fractalUp && rsiVal > 55)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevHigh5 = _high5;
		_prevLow5 = _low5;
	}
}