Открыть на GitHub

Стратегия JFatl Candle MMRec

Стратегия переносит логику советника Exp_JFatlCandle_MMRec.mq5 в инфраструктуру StockSharp. Она отслеживает смену цветов свечей, построенных с помощью фильтра JFatl, и дополняет их блоком управления рисками MMRec, который уменьшает объём позиции после серии убыточных сделок.

Торговая идея

  • Формируются синтетические свечи: исходные OHLC значения сглаживаются ядром Fast Adaptive Trend Line (FATL). Используются оригинальные 39 коэффициентов фильтра; затем применяется экспоненциальное сглаживание, приближающее Jurik Moving Average из версии MT5.
  • Цвета свечей трактуются по величине разницы между фильтрованными open и close:
    • значение 2 — бычья свеча (close выше open);
    • значение 0 — медвежья свеча (close ниже open);
    • значение 1 — нейтральное тело.
  • Бычий цвет на свече SignalBar + 1 бара назад приводит к закрытию коротких позиций и подготовке к новому входу в лонг, когда свеча SignalBar баров назад перестает быть бычьей.
  • Аналогично, медвежий цвет закрывает лонги и разрешает открытие короткой позиции, если более свежая свеча уже не медвежья.
  • Объём сделок вычисляется через MMRecounter: если из последних TotalTrigger сделок нужного направления не менее LossTrigger завершились убытком, используется уменьшенный лот, иначе — нормальный.

Параметры

Параметр Описание
CandleType Таймфрейм свечей, поступающих на вход фильтра FATL (по умолчанию 12 часов).
SignalBar Сдвиг для поиска сигнальной свечи. 0 — текущая закрытая свеча, 1 — настройка, соответствующая MT5.
SmoothingLength Длина экспоненциального сглаживания после ядра FATL.
NormalVolume Базовый объём позиции при стабильных результатах.
ReducedVolume Уменьшенный объём, активируется при серии убыточных сделок.
BuyTotalTrigger / SellTotalTrigger Количество последних сделок каждого направления, которые анализирует MMRecounter.
BuyLossTrigger / SellLossTrigger Минимум убыточных сделок в окне анализа, после которого включается ReducedVolume.
EnableBuyEntries / EnableSellEntries Разрешение на открытие длинных/коротких позиций.
EnableBuyExits / EnableSellExits Разрешение на закрытие позиций при появлении противоположного сигнала.
StopLossPoints Необязательный стоп-лосс в шагах цены инструмента. 0 отключает защиту.
TakeProfitPoints Тейк-профит в шагах цены. 0 отключает цель.

Правила торговли

  1. После накопления достаточной истории (не менее 39 свечей) рассчитываются фильтрованные OHLC и цвет свечи.
  2. Пусть C1 — цвет свечи SignalBar + 1 баров назад, C0 — цвет свечи SignalBar баров назад (при SignalBar = 0 используется текущая свеча как C0, предыдущая — как C1).
  3. Если C1 == 2 (бычий цвет):
    • закрыть все короткие позиции, если EnableSellExits включён;
    • открыть длинную позицию рассчитанным объёмом, если EnableBuyEntries включён и C0 != 2.
  4. Если C1 == 0 (медвежий цвет):
    • закрыть все длинные позиции, если активирован EnableBuyExits;
    • открыть короткую позицию, если EnableSellEntries включён и C0 != 0.
  5. Позиции также закрываются при достижении уровней стоп-лосса или тейк-профита в пределах диапазона свечи.

Деньги-менеджмент

После каждой закрытой позиции стратегия сохраняет результат отдельно для лонгов и шортов. Перед входом она анализирует до TotalTrigger прошлых сделок выбранного направления. Если количество убыточных результатов достигает LossTrigger, новая позиция открывается уменьшенным объёмом, в противном случае — стандартным.

Примечания

  • Расчёт стоп-лосса и тейк-профита ведётся в шагах цены (Security.PriceStep). При отсутствии данных используется шаг 1.
  • Пока фильтр FATL не накопит 39 значений, сигналы и сделки не формируются.
  • История сделок для MMRecounter ограничена 100 записями — старые результаты автоматически удаляются.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

public class JfatlCandleMmRecStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private decimal? _prevFast, _prevSlow;

	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 JfatlCandleMmRecStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 7).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 21).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = null; _prevSlow = null;
		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
		if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;
		_prevFast = fast; _prevSlow = slow;
		if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
	}
}