Открыть на GitHub

Стратегия MoStAsHaR15 Pivot Line

Обзор

Стратегия реализует эксперта MetaTrader 4 «MoStAsHaR15 FoReX - Pivot Line» на высокоуровневом API StockSharp. Сохраняется классическая сетка дневных уровней поддержки/сопротивления, усиленная индикаторами ADX, спредом между EMA по ценам закрытия и открытия, а также гистограммой MACD (OsMA). Торговые решения принимаются по часовым свечам, а параллельная подписка на дневной таймфрейм каждый раз пересчитывает сетку pivot до обработки очередной свечи.

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

  • Расчёт pivot – максимумы, минимумы и закрытие предыдущего дня формируют классический pivot (P), уровни сопротивления R1–R3, уровни поддержки S1–S3 и промежуточные отметки M0–M5. Текущее закрытие сравнивается с этой лестницей для определения актуального диапазона. Сохраняется оригинальная особенность, когда диапазон между M5 и R3 сопоставляется с парой S3/M0.
  • Фильтр расстояния – сделка открывается только при расстоянии до целевого уровня больше MinimumDistancePips (по умолчанию 14 пунктов), что соответствует проверкам dif1/dif2 в исходном коде.
  • Условия для покупки:
    • Линия ADX выше порога AdxThreshold (20), +DI растёт и превосходит −DI.
    • EMA по закрытиям минимум на EmaSpreadPips (5 пунктов) выше EMA по открытиям, и на предыдущей свече соблюдался такой же бычий порядок.
    • Гистограмма MACD увеличилась относительно предыдущей свечи.
  • Условия для продажи – зеркальное отражение бычьих правил: усиление −DI, медвежий спред EMA и падающая гистограмма MACD.
  • Одновременно разрешён только один чистый объём. Сделки открываются рыночными приказами BuyMarket()/SellMarket().

Управление позицией

  • Стоп-лосс – опциональный, располагается на расстоянии StopLossPips от цены входа. Значение 0 отключает исходный стоп, как и в EA.
  • Тейк-профит – фиксируется на границе диапазона (поддержка или сопротивление), в котором находилась цена при входе.
  • Трейлинг-стоп – после прохождения ценой расстояния TrailingStopPips + TrailingStepPips стоп подтягивается, сохраняя расстояние TrailingStopPips. При включении трейлинга параметр шага обязан быть положительным.
  • При касании стопа, трейлинга или цели в пределах свечи позиция закрывается в обработке этой свечи.

Параметры стратегии

Параметр Описание Значение по умолчанию
HourlyCandleType Часовой поток свечей для торговой логики. 1 час
DailyCandleType Дневной поток свечей для расчёта pivot. 1 день
StopLossPips Размер стартового стоп-лосса в пунктах (0 — отключить). 20
TrailingStopPips Размер трейлинг-стопа в пунктах. 10
TrailingStepPips Минимальное движение в пунктах для обновления трейлинга (должно быть > 0 при включении). 5
MinimumDistancePips Минимальное расстояние до целевого уровня перед входом. 14
EmaSpreadPips Требуемый спред между EMA по закрытиям и открытиям. 5
AdxThreshold Минимальное значение ADX для активации сигнала. 20
AdxPeriod Период индикатора ADX. 14
EmaClosePeriod Период EMA по ценам закрытия. 5
EmaOpenPeriod Период EMA по ценам открытия. 8
MacdFastPeriod Быстрый период EMA в MACD (числитель OsMA). 12
MacdSlowPeriod Медленный период EMA в MACD. 26
MacdSignalPeriod Период сигнальной EMA в MACD. 9

Примечания по конвертации

  • Все индикаторы рассчитываются только на завершённых свечах; коллекции не создаются, состояние хранится в полях, как требует руководство репозитория.
  • Размер пункта выводится из PriceStep и количества знаков у инструмента. Для котировок с 3 или 5 знаками применяется «мини-пункт», как в MetaTrader.
  • Специфическое сопоставление диапазона M5→R3 с парой S3/M0 сохранено для точного соответствия оригинальному эксперту.
  • Комментарии в коде выполнены на английском языке согласно проектным правилам.

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

  • Подбирайте таймфреймы под торговый режим инструмента, особенно если дневной клиринг отличается от стандартного.
  • Поскольку проверка стопов происходит по закрытию свечи, в быстрых рынках возможно большее проскальзывание по сравнению с тиковым исполнением в MetaTrader.
  • Для инструментов с другой волатильностью подстраивайте фильтры MinimumDistancePips и EmaSpreadPips.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MoStAsHaR15 Pivot Line strategy - EMA crossover with ADX trend filter.
/// Buys when fast EMA crosses above slow EMA and ADX confirms trend.
/// Sells when fast EMA crosses below slow EMA and ADX confirms trend.
/// </summary>
public class MoStAsHaR15PivotLineStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<decimal> _adxThreshold;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int AdxPeriod { get => _adxPeriod.Value; set => _adxPeriod.Value = value; }
	public decimal AdxThreshold { get => _adxThreshold.Value; set => _adxThreshold.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MoStAsHaR15PivotLineStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 100)
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_adxPeriod = Param(nameof(AdxPeriod), 14)
			.SetDisplay("ADX Period", "ADX lookback", "Indicators");
		_adxThreshold = Param(nameof(AdxThreshold), 20m)
			.SetDisplay("ADX Threshold", "Min ADX for trending", "Levels");
		_cooldownCandles = Param(nameof(CooldownCandles), 100)
			.SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevFast = fast; _prevSlow = slow;
	}
}