Открыть на GitHub

FitFul 13 с временными фильтрами

Обзор

FitFul 13 с временными фильтрами — порт эксперта MetaTrader 4 «FitFul_13» на платформу StockSharp. Стратегия строит лестницу недельных пивотов (PP, R0.5, R1, R1.5, R2, R2.5, R3 и симметричные уровни поддержки) по максимуму, минимуму и закрытию предыдущей недели. Торговые решения принимаются на основном таймфрейме (по умолчанию 1 час) и подтверждаются более быстрым таймфреймом (по умолчанию 15 минут). Новые позиции разрешены только в заданные минуты часа, что повторяет логику исходного советника.

Логика сигналов

  1. Расчёт недельных пивотов
    • После закрытия каждой недельной свечи пересчитывается весь набор уровней.
    • Стоп и тейк рассчитываются от базовых уровней с учётом настраиваемого отступа в пунктах.
  2. Условия на основном таймфрейме
    • Последняя завершённая свеча должна быть бычьей для поиска лонга или медвежьей для поиска шорта.
    • Предыдущая свеча обязана пересекать соответствующий уровень пивота (для лонга — открыть ниже и закрыть выше, для шорта наоборот).
  3. Условия на подтверждающем таймфрейме
    • При бычьей текущей подтверждающей свече минимумы двух предыдущих свечей должны пробивать уровень и закрываться выше него.
    • При медвежьей подтверждающей свече максимумы двух предыдущих свечей должны пробивать уровень и закрываться ниже него.
  4. Временные фильтры
    • Сделка открывается только если минута открытия завершённой основной свечи равна одному из четырёх настроенных значений (по умолчанию 0, 15, 30 или 45).
    • Чистая позиция ограничена значением MaxNetPositions × Volume, что имитирует максимум три ордера у версии MT4.

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

  • Стоп и тейк — назначаются сразу после открытия позиции, используя ближайшие уровни пивотов.
  • Трейлинг-стоп — при движении цены на заданное количество пунктов стоп подтягивается в сторону прибыли.
  • Ограничение по времени — прибыльные позиции закрываются, если время удержания превысило установленный порог (по умолчанию 48 часов).
  • Правило пятницы — в пятницу все позиции закрываются в выбранном интервале времени (по умолчанию 21:50–21:59).

Параметры

Имя Описание
PrimaryCandleType Таймфрейм для поиска пересечения пивотов.
ConfirmationCandleType Быстрый таймфрейм для подтверждения реакции на уровень.
Volume Объём рыночной заявки.
MaxNetPositions Максимальная чистая позиция в кратных значениях Volume.
OffsetPoints Отступ в пунктах для расчёта стопов и тейков.
TrailingStopPoints Размер трейлинг-стопа в пунктах.
CloseAfter Максимальное время удержания прибыльной позиции.
CloseHour, CloseMinuteFrom, CloseMinuteTo Временное окно принудительного закрытия в пятницу.
EntryMinute0..3 Допустимые минуты часа для открытия новых позиций.

Примечания

  • В портированной версии сохранены недельные пивоты и дискретный тайминг входов оригинального советника.
  • Управление капиталом упрощено: параметр Volume напрямую задаёт объём сделки вместо сложного расчёта лота в MT4.
  • Все комментарии в коде написаны на английском языке согласно требованиям репозитория.
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>
/// FitFul 13 Time Gated strategy - channel midpoint crossover.
/// Buys when close crosses above the midpoint of Highest/Lowest channel.
/// Sells when close crosses below the midpoint.
/// </summary>
public class FitFul13TimeGatedStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	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 EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

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

		_emaPeriod = Param(nameof(EmaPeriod), 13)
			.SetDisplay("EMA Period", "EMA trend filter", "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 ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ema, ProcessCandle)
			.Start();
	}

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

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

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

		// Cross above midpoint with EMA confirmation
		if (_prevClose <= _prevMid && close > mid && close > ema && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Cross below midpoint with EMA confirmation
		else if (_prevClose >= _prevMid && close < mid && close < ema && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevClose = close;
		_prevMid = mid;
	}
}