Открыть на GitHub

Стратегия Exp X2MA Candle MM Recovery

Общее описание

Данный алгоритм — порт эксперта Exp_X2MACandle_MMRec с платформы MetaTrader на C#. Логика построена на наблюдении за цветом свечи, полученной после двойного сглаживания исходных OHLC-данных индикатором X2MA. Цветовые переходы определяют моменты открытия и закрытия позиций, а блок риск-менеджмента уменьшает рабочий объём после серии убыточных сделок.

Стратегия обрабатывает только завершённые свечи. Для выбранного таймфрейма формируются две последовательные скользящие средние (методы и длины каждой ступени задаются параметрами). На основе сглаженных значений рассчитывается «цвет» свечи: зелёный (код 2), красный (0) или нейтральный (1). Сигналы формируются по окончании свечи, расположенной на смещении SignalBar + 1, чтобы предотвратить повторные входы при текущем изменении цвета.

Логика индикатора

  1. Все четыре компоненты свечи (Open, High, Low, Close) проходят через две ступени сглаживания.
  2. Методы сглаживания сопоставлены со встроенными индикаторами StockSharp:
    • SimpleSimpleMovingAverage;
    • ExponentialExponentialMovingAverage;
    • SmoothedSmoothedMovingAverage (RMA);
    • WeightedWeightedMovingAverage;
    • JurikJurikMovingAverage (при наличии свойства Phase параметр учитывается).
  3. Если модуль разницы между сглаженными Open и Close меньше GapPoints * StepPrice, тело свечи приравнивается к предыдущему закрытию.
  4. Цвет определяется сравнением сглажённых Open и Close.
  5. Бар для оценки сигналов задаётся параметром SignalBar, что соответствует использованию истории в MetaTrader.

Управление объёмом

  • Оригинальный эксперт анализировал историю сделок через сервис MetaTrader. В StockSharp прямой доступ к этой статистике отсутствует, поэтому используется внутренняя очередь из последних закрытых сделок.
  • Размер очереди задаётся параметром HistoryDepth. Как только количество убытков в окне достигает LossTrigger, объём следующей сделки уменьшаетcя до ReducedVolume.
  • Для определения результата сделки используется цена закрытия свечи, по сигналу которой позиция была ликвидирована. Автоматические стопы и тейк-профиты из MQL-версии не переносятся — при необходимости добавьте собственные правила (например, через StartProtection).

Параметры

Имя Описание
CandleType Таймфрейм свечей для расчётов.
FirstMethod, FirstLength, FirstPhase Метод, длина и фаза первой ступени сглаживания.
SecondMethod, SecondLength, SecondPhase Метод, длина и фаза второй ступени.
GapPoints Порог выравнивания тела свечи в шагах цены инструмента.
SignalBar Смещение по истории (0 — последняя завершённая свеча).
AllowLongEntry / AllowShortEntry Разрешение на открытие длинных/коротких позиций.
AllowLongExit / AllowShortExit Разрешение на закрытие длинных/коротких позиций.
NormalVolume Базовый торговый объём.
ReducedVolume Объём после серии убытков.
HistoryDepth Глубина истории для учёта убытков (0 — отключить).
LossTrigger Количество убытков в окне, при котором включается уменьшенный объём (0 — отключить).

Практические замечания

  • Стратегия работает с единственным инструментом, возвращаемым GetWorkingSecurities().
  • Обработка происходит один раз на завершённой свече, поэтому повторные заявки исключены.
  • Чтобы деактивировать снижение объёма, установите ReducedVolume, равный NormalVolume, либо обнулите LossTrigger.
  • Из-за использования цен закрытия свечевая оценка убытков может отличаться от MetaTrader, особенно при проскальзываниях или частичном исполнении. При необходимости скорректируйте параметры.
  • Для реализации стоп-лоссов и тейк-профитов воспользуйтесь механизмами риск-менеджмента StockSharp.
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 ExpX2MaCandleMmRecStrategy : 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 ExpX2MaCandleMmRecStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 8).SetGreaterThanZero().SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 25).SetGreaterThanZero().SetDisplay("Slow WMA", "Slow WMA 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 WeightedMovingAverage { Length = FastPeriod };
		var slow = new WeightedMovingAverage { 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(); }
	}
}