Открыть на GitHub

FT Trend Follower

Обзор

FT Trend Follower — порт стратегии FT_TrendFollower.mq4 с MetaTrader 4 на платформу StockSharp. Алгоритм сопровождает среднесрочные тренды, комбинируя веер из нескольких экспоненциальных средних (GMMA), осциллятор Laguerre, фильтр пересечения быстрых и медленных EMA и подтверждение по основной линии MACD. Сделки открываются только после того, как цена погружается внутрь веера GMMA, разворачивается от экстремума Laguerre и большинство средних снова наклоняются в сторону предполагаемого тренда. Управление позицией полностью повторяет исходный советник: опциональный «свечный» стоп, фиксированный стоп и три взаимоисключающих модуля поэтапного выхода, основанных на дневных пивотах или скользящих каналах.

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

Структура GMMA и определение тренда

  • GMMA строится в диапазоне от StartGmmaPeriod до EndGmmaPeriod. Периоды разбиваются на пять групп по BandsPerGroup линий, что соответствует параметру CountLine в оригинальном EA.
  • Направление тренда определяется сравнением более медленной группы длинных GMMA (индекс CountLine + CountLine от конца массива) и более быстрой длинной группы (индекс CountLine от конца). Если медленные средние находятся выше быстрых — тренд восходящий, ниже — нисходящий.
  • Подтверждение наклона подсчитывает, сколько коротких, средних и длинных GMMA выросли или упали относительно предыдущей свечи. Для входа количество растущих (или падающих) линий должно превышать половину всего веера — это аналог счётчиков controlvverh/controlvverhS в MetaTrader.

Подготовка сигнала

  • Сброс по закрытию — если свеча закрывается ниже самой медленной GMMA, модуль покупок становится «вооружённым», если выше — модуль продаж. Пересечение самой быстрой GMMA в обратную сторону сбрасывает флаги (аналог CloseOk).
  • Триггер Laguerre — фильтр Laguerre (LaguerreGamma) должен сперва опуститься ниже LaguerreOversold (для лонга) или подняться выше LaguerreOverbought (для шорта) при сохранении контакта с длинными средними. После возврата через порог сигнал разрешается.
  • Пересечение EMA — для лонга быстрая EMA (FastSignalLength) должна опуститься ниже медленной (SlowSignalLength), а затем подняться выше неё; для шорта условия зеркальные.
  • Фильтр MACD — основная линия MACD (параметры 5/35/5, как в советнике) должна быть положительной для покупок и отрицательной для продаж.

Правила входа

Покупка выполняется при одновременном выполнении условий:

  1. Тренд признан восходящим, а счётчик положительных наклонов превышает половину всех линий GMMA.
  2. Триггер Laguerre был заранее активирован и текущее значение поднялось выше LaguerreOversold.
  3. Быстрая EMA находится выше медленной после предыдущего «снижения».
  4. Значение MACD больше нуля.

Продажа требует зеркальных условий: Laguerre должен пересечь LaguerreOverbought сверху вниз, MACD — ниже нуля и т.д. При развороте существующей позиции объём заявки автоматически включает объём для закрытия противоположного плеча, поэтому итоговый нетто-объём всегда равен Volume.

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

  • Стопы — можно выбрать «свечный» стоп (UseSwingStop) на расстоянии SwingStopPips пунктов за минимумом/максимумом предыдущей свечи или фиксированный стоп (UseFixedStop) на FixedStopPips пунктов. Одновременное включение обоих вариантов запрещено и приводит к ошибке при запуске — как и в оригинальном советнике.
  • Модуль выхода по пивотам (Quit) — первая половина позиции закрывается при пробое дневного уровня R1/S1 при наличии плавающей прибыли. Остаток закрывается, как только индикатор Hull Moving Average (HmaPeriod) выдаёт валидное значение (аналог проверки буфера hma1).
  • Модуль выхода по диапазону пивотов (Quit1) — первая фиксация происходит на R1/S1, остаток закрывается на R2/S2, если сделка остаётся прибыльной.
  • Модуль выхода по каналу (Quit2) — первая фиксация также на R1/S1, оставшаяся часть закрывается, когда следующая свеча открывается ниже средней по минимумам (ChannelPeriod) для лонга или выше средней по максимумам для шорта, что соответствует волатильностному фильтру исходного EA.

За один запуск можно активировать только один модуль выхода.

Параметры

  • Volume — объём новой позиции.
  • StartGmmaPeriod / EndGmmaPeriod — границы веера GMMA.
  • BandsPerGroup — количество линий в каждой группе (CountLine).
  • FastSignalLength / SlowSignalLength — периоды EMA для фильтра пересечения.
  • TradeShift — наследованный параметр (0 или 1); стратегия работает по закрытым свечам.
  • UseSwingStop / SwingStopPips — включение и настройка «свечного» стопа.
  • UseFixedStop / FixedStopPips — включение фиксированного стопа в пунктах.
  • EnablePivotExit / EnablePivotRangeExit / EnableChannelExit — взаимоисключающие модули частичного выхода.
  • LaguerreOversold / LaguerreOverbought / LaguerreGamma — пороги и сглаживание фильтра Laguerre.
  • HmaPeriod — период Hull MA для модуля Quit.
  • ChannelPeriod — длина скользящих средних по максимумам/минимумам для Quit2.
  • CandleType — рабочий таймфрейм (по умолчанию 1 час).

Дополнительная информация

  • Дневные пивоты вычисляются по последней завершённой дневной свече из отдельной подписки.
  • Перевод пунктов в цену опирается на PriceStep инструмента, поэтому стратегия адаптируется к различным точкам котировки.
  • Используются только высокоуровневые индикаторы и метод Bind, без прямого доступа к буферам индикаторов.
  • Python-реализация отсутствует.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend following strategy using EMA crossover with MACD confirmation.
/// Buys when fast EMA crosses above slow EMA and MACD histogram is positive.
/// Sells when fast EMA crosses below slow EMA and MACD histogram is negative.
/// </summary>
public class FtTrendFollowerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _prevMacd;
	private bool _isReady;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public FtTrendFollowerStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for strategy", "General");

		_fastPeriod = Param(nameof(FastPeriod), 12)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 26)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_prevMacd = 0;
		_isReady = false;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_prevMacd = 0;
		_isReady = false;

		var fast = new EMA { Length = FastPeriod };
		var slow = new EMA { Length = SlowPeriod };
		var macd = new MovingAverageConvergenceDivergence();

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, macd, OnProcess)
			.Start();

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

	private void OnProcess(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal macdVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_isReady)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			_prevMacd = macdVal;
			_isReady = true;
			return;
		}

		// Buy: fast crosses above slow + MACD positive
		if (_prevFast <= _prevSlow && fastVal > slowVal && macdVal > 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		// Sell: fast crosses below slow + MACD negative
		else if (_prevFast >= _prevSlow && fastVal < slowVal && macdVal < 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
		_prevMacd = macdVal;
	}
}