Открыть на GitHub

FiftyFiveMedianSlopeStrategy

Происхождение

  • Стратегия создана на основе советника MetaTrader 4 55_MA_med_FIN.mq4.
  • Основной сигнал строится на наклоне 55-периодной скользящей средней, рассчитанной по медианной цене свечи.

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

  • Подписывается на выбранный таймфрейм (по умолчанию 1 час) и обрабатывает только завершённые свечи.
  • Рассчитывает скользящую среднюю заданного типа (SMA, EMA, SMMA или LWMA) по медианной цене ((High + Low) / 2).
  • Сохраняет последние значения средней во внутреннем буфере, чтобы сравнивать значение на одну свечу назад со значением MaShift свечей назад.
  • Если значение на одну свечу назад выше, чем MaShift свечей назад:
    • Сначала закрывает открытую короткую позицию.
    • Затем открывает длинную позицию, если не превышен предел MaxOrders.
  • Если значение на одну свечу назад ниже, чем MaShift свечей назад, выполняется зеркальная логика для коротких позиций.
  • Внутренние флаги блокируют повторное открытие в ту же сторону, пока не появится противоположный сигнал, что полностью повторяет механику оригинального советника.
  • Торговля разрешена только когда час открытия свечи удовлетворяет неравенству StartHour < hour < EndHour (границы строго исключаются).

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

  • FixedVolume задаёт базовый объём сделки. Если он равен нулю, стратегия рассчитывает объём по доле капитала RiskPercentage.
  • MaxOrders ограничивает, сколько раз базовый объём можно наращивать в одном направлении. Значение 0 снимает ограничение.
  • Параметры StopLossPoints и TakeProfitPoints задают расстояние до стоп-лосса и тейк-профита в шагах цены через StartProtection.

Параметры

  • FixedVolume – фиксированный объём заявки (0 включает режим по риску).
  • RiskPercentage – процент капитала для расчёта объёма, если используется режим по риску.
  • TakeProfitPoints / StopLossPoints – дистанции защитных уровней в шагах цены.
  • MaPeriod – период скользящей средней (по умолчанию 55).
  • MaShift – сдвиг для сравнения исторических значений (по умолчанию 13).
  • MaMethod – тип скользящей средней: Simple, Exponential, Smoothed, LinearWeighted.
  • StartHour / EndHour – границы торгового окна в часах платформы (0–23, исключая границы).
  • MaxOrders – максимальное число одновременно открытых позиций в одном направлении.
  • CandleType – таймфрейм, используемый для расчёта сигналов.

Рекомендации

  • Убедитесь, что инструмент содержит корректные значения шага цены и минимального шага объёма, иначе выравнивание объёма может быть отклонено биржей.
  • Режим по риску опирается на текущее значение портфеля и последнюю цену закрытия. При отсутствии данных стратегия не будет открывать сделки.
  • Перед открытием новой позиции стратегия всегда закрывает противоположное плечо, как и в оригинальном MQL4-советнике.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Fifty Five Median Slope: EMA slope direction with ATR stops.
/// </summary>
public class FiftyFiveMedianSlopeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _slopeShift;

	private decimal _entryPrice;
	private decimal _prevEma;
	private int _barCount;
	private readonly decimal[] _emaHistory = new decimal[20];

	public FiftyFiveMedianSlopeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 55)
			.SetDisplay("EMA Length", "Moving average period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_slopeShift = Param(nameof(SlopeShift), 13)
			.SetDisplay("Slope Shift", "Bars between slope comparison.", "Indicators");
	}

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public int SlopeShift
	{
		get => _slopeShift.Value;
		set => _slopeShift.Value = value;
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_barCount = 0;
		Array.Clear(_emaHistory, 0, _emaHistory.Length);
	}

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

		_entryPrice = 0;
		_prevEma = 0;
		_barCount = 0;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, atr, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var len = Math.Min(SlopeShift + 1, _emaHistory.Length);
		var idx = _barCount % len;
		_emaHistory[idx] = emaVal;
		_barCount++;

		if (_barCount < len || atrVal <= 0)
			return;

		var shiftIdx = (_barCount - SlopeShift) % len;
		if (shiftIdx < 0) shiftIdx += len;
		var shiftedEma = _emaHistory[shiftIdx];

		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 1.5m || emaVal < shiftedEma)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 1.5m || emaVal > shiftedEma)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (emaVal > shiftedEma && _prevEma <= shiftedEma)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (emaVal < shiftedEma && _prevEma >= shiftedEma)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevEma = emaVal;
	}
}