Открыть на GitHub

Стратегия MARE5.1 с смещёнными скользящими средними

Обзор

MARE5.1 с смещёнными скользящими средними — порт оригинального советника MetaTrader 5 «MARE5.1» на высокоуровневый API StockSharp. Стратегия по умолчанию работает с минутными свечами (тип можно поменять) и сравнивает две простые скользящие средние (SMA), которым задан одинаковый сдвиг вперёд. Для подтверждения сигналов используются исторические значения SMA и направление последней закрытой свечи.

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

  • Рассчитываются две SMA: быстрая и медленная. Обе смещаются на одинаковое количество баров вперёд, что повторяет логику оригинального советника.
  • Вход в короткую позицию осуществляется при выполнении всех условий:
    1. На текущей свече медленная SMA минимум на один шаг цены выше быстрой SMA.
    2. Две свечи назад быстрая SMA минимум на один шаг цены была выше медленной.
    3. Пять свечей назад быстрая SMA минимум на один шаг цены была выше медленной.
    4. Последняя завершённая свеча (предыдущий бар) медвежья.
  • Вход в длинную позицию — зеркальная ситуация:
    1. На текущей свече быстрая SMA минимум на один шаг цены выше медленной.
    2. Две свечи назад медленная SMA минимум на один шаг цены была выше быстрой.
    3. Пять свечей назад медленная SMA минимум на один шаг цены была выше быстрой.
    4. Предыдущая свеча бычья.
  • Одновременно может быть открыта только одна позиция. Объём заявки задаётся параметром TradeVolume.
  • Торговля разрешена только внутри заданного часового окна (границы включительно), что полностью повторяет проверку времени из исходного эксперта.

Управление рисками

Фиксированные уровни тейк-профита и стоп-лосса сохранены. Значения задаются в «пунктах» (для инструментов с тремя и пятью знаками после запятой пункт автоматически увеличивается в 10 раз) и переводятся в абсолютные ценовые величины при запуске стратегии. Защитные ордера обслуживаются через StartProtection с выходом рыночными заявками.

Индикаторы и данные

  • Быстрая SMA — длина задаётся параметром FastPeriod.
  • Медленная SMA — длина задаётся параметром SlowPeriod.
  • Источник данных — по умолчанию минутные свечи, но через CandleType можно выбрать любой поддерживаемый тип свечей.

Параметры

Имя Значение по умолчанию Описание
TradeVolume 0.01 Объём сделки при входе в позицию.
TakeProfitPips 35 Расстояние до тейк-профита в корректированных пунктах. Ноль отключает тейк-профит.
StopLossPips 55 Расстояние до стоп-лосса в корректированных пунктах. Ноль отключает стоп-лосс.
FastPeriod 14 Период быстрой SMA.
SlowPeriod 79 Период медленной SMA.
MovingAverageShift 4 Сдвиг (в барах), применяемый к обеим SMA.
SessionOpenHour 2 Час начала торгового окна (0–23).
SessionCloseHour 3 Час окончания торгового окна (0–23), должен быть больше SessionOpenHour.
CandleType минутные свечи Тип свечей, используемый в расчётах.

Примечания

  • Сигналы оцениваются только после закрытия свечи. Исторические значения SMA буферизуются внутри стратегии, чтобы воспроизвести чтение индикатора по индексам, как в MQL.
  • Для проверки минимального расстояния между SMA используется шаг цены инструмента, что гарантирует разницу минимум в один тик.
  • Стоп-лосс и тейк-профит рассчитываются от шага цены. Для инструментов с тремя и пятью знаками после запятой пункт автоматически увеличивается в десять раз, как и в MetaTrader.
  • Стратегия не усредняет позиции и не строит сетки: новая сделка возможна только после полного закрытия текущей позиции.
  • В данный каталог включена только C#-версия; Python-реализация для этой стратегии отсутствует.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MARE5.1 strategy that trades shifted SMA crossovers with time filtering.
/// </summary>
public class Mare51Strategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _movingAverageShift;
	private readonly StrategyParam<int> _sessionOpenHour;
	private readonly StrategyParam<int> _sessionCloseHour;
	private readonly StrategyParam<DataType> _candleType;

	private SMA _fastSma;
	private SMA _slowSma;
	private decimal?[] _fastBuffer;
	private decimal?[] _slowBuffer;
	private ICandleMessage _previousCandle;
	private decimal _pipSize;

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

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

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

	public int MovingAverageShift
	{
		get => _movingAverageShift.Value;
		set => _movingAverageShift.Value = value;
	}

	public int SessionOpenHour
	{
		get => _sessionOpenHour.Value;
		set => _sessionOpenHour.Value = value;
	}

	public int SessionCloseHour
	{
		get => _sessionCloseHour.Value;
		set => _sessionCloseHour.Value = value;
	}

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

	public Mare51Strategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 0.01m)
			.SetDisplay("Volume", "Default order volume", "Trading")
			.SetGreaterThanZero();

		_takeProfitPips = Param(nameof(TakeProfitPips), 35m)
			.SetDisplay("Take Profit (pips)", "Take profit distance in adjusted pips", "Risk")
			.SetNotNegative();

		_stopLossPips = Param(nameof(StopLossPips), 55m)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance in adjusted pips", "Risk")
			.SetNotNegative();

		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators")
			.SetGreaterThanZero();

		_slowPeriod = Param(nameof(SlowPeriod), 20)
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators")
			.SetGreaterThanZero();

		_movingAverageShift = Param(nameof(MovingAverageShift), 1)
			.SetDisplay("MA Shift", "Forward shift applied to both SMAs", "Indicators")
			.SetNotNegative();

		_sessionOpenHour = Param(nameof(SessionOpenHour), 0)
			.SetDisplay("Session Open Hour", "Inclusive start hour for trading", "Session")
			.SetRange(0, 23);

		_sessionCloseHour = Param(nameof(SessionCloseHour), 23)
			.SetDisplay("Session Close Hour", "Inclusive end hour for trading", "Session")
			.SetRange(0, 23);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Candle data type", "Data");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_fastSma = null;
		_slowSma = null;
		_fastBuffer = null;
		_slowBuffer = null;
		_previousCandle = null;
		_pipSize = 0m;
	}

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

		if (SessionOpenHour >= SessionCloseHour)
			throw new InvalidOperationException("SessionOpenHour must be less than SessionCloseHour.");

		Volume = TradeVolume;

		_fastSma = new SMA { Length = FastPeriod };
		_slowSma = new SMA { Length = SlowPeriod };

		_fastBuffer = new decimal?[MovingAverageShift + 6];
		_slowBuffer = new decimal?[MovingAverageShift + 6];

		_pipSize = CalculatePipSize();
		var takeProfitUnit = TakeProfitPips > 0m
			? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);
		var stopLossUnit = StopLossPips > 0m
			? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
			: new Unit(0m);

		StartProtection(stopLossUnit, takeProfitUnit);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastSma, _slowSma, ProcessCandle)
			.Start();

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

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

		if (_fastBuffer == null || _slowBuffer == null)
			return;

		// Shift raw SMA values so we can later access shifted indexes.
		for (var i = _fastBuffer.Length - 1; i > 0; i--)
		{
			_fastBuffer[i] = _fastBuffer[i - 1];
			_slowBuffer[i] = _slowBuffer[i - 1];
		}

		_fastBuffer[0] = fastValue;
		_slowBuffer[0] = slowValue;

		var previousCandle = _previousCandle;
		_previousCandle = candle;

		//if (!IsFormedAndOnlineAndAllowTrading())
		//	return;

		if (previousCandle == null)
			return;

		if (_fastSma == null || _slowSma == null)
			return;

		if (!_fastSma.IsFormed || !_slowSma.IsFormed)
			return;

		var fast0 = GetShiftedValue(_fastBuffer, 0);
		var fast2 = GetShiftedValue(_fastBuffer, 2);
		var fast5 = GetShiftedValue(_fastBuffer, 5);
		var slow0 = GetShiftedValue(_slowBuffer, 0);
		var slow2 = GetShiftedValue(_slowBuffer, 2);
		var slow5 = GetShiftedValue(_slowBuffer, 5);

		if (fast0 is not decimal f0 || fast2 is not decimal f2 || fast5 is not decimal f5 ||
			slow0 is not decimal s0 || slow2 is not decimal s2 || slow5 is not decimal s5)
		{
			return;
		}

		if (!IsWithinSession(candle.OpenTime))
			return;

		var bearishPrevious = previousCandle.ClosePrice < previousCandle.OpenPrice;
		var bullishPrevious = previousCandle.ClosePrice > previousCandle.OpenPrice;

		var sellSignal = f5 >= s5 && f2 < s2 && f0 < s0 && bearishPrevious;
		var buySignal = f5 <= s5 && f2 > s2 && f0 > s0 && bullishPrevious;

		if (Position != 0)
			return;

		if (sellSignal)
		{
			// Enter short when slow SMA overtakes the fast SMA and previous bars confirm the reversal.
			SellMarket();
		}
		else if (buySignal)
		{
			// Enter long when fast SMA overtakes the slow SMA and previous bars confirm the reversal.
			BuyMarket();
		}
	}

	private decimal? GetShiftedValue(decimal?[] buffer, int index)
	{
		var targetIndex = index + MovingAverageShift;
		if (buffer == null)
			return null;
		if (targetIndex < 0 || targetIndex >= buffer.Length)
			return null;
		return buffer[targetIndex];
	}

	private bool IsWithinSession(DateTimeOffset time)
	{
		var hour = time.Hour;
		return hour >= SessionOpenHour && hour <= SessionCloseHour;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 1m;

		var scale = GetDecimalScale(step);
		return (scale == 3 || scale == 5) ? step * 10m : step;
	}

	private static int GetDecimalScale(decimal value)
	{
		var bits = decimal.GetBits(value);
		return (bits[3] >> 16) & 0xFF;
	}
}