Открыть на GitHub

3207 – Стратегия MA Trend

Обзор

MA Trend Strategy повторяет советник MetaTrader MA Trend.mq5 на высокоуровневом API StockSharp. Алгоритм отслеживает одну линейно-взвешенную скользящую среднюю с настраиваемым сдвигом вперёд. Закрытие цены выше сдвинутой средней даёт сигнал на покупку, а ниже – на продажу. Защитные приёмы (стоп-лосс, тейк-профит и трейлинг) воспроизводят логику оригинального MQL-кода.

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

  1. Подписка на выбранный тип свечей (по умолчанию минутные) и расчёт скользящей средней с заданным методом и источником цены.
  2. Сдвиг значения скользящей на указанное число завершённых свечей перед сравнением с текущим закрытием.
  3. Формирование сигналов:
    • Покупка – закрытие выше сдвинутой средней (при ReverseSignals=true условие инвертируется).
    • Продажа – закрытие ниже сдвинутой средней (при ReverseSignals=true условие инвертируется).
  4. Управление позицией:
    • При CloseOpposite=true перед открытием новой позиции закрывается встречное направление.
    • При OnlyOnePosition=true новые входы блокируются, если позиция уже существует.
  5. Выходы по стоп-лоссу, тейк-профиту и трейлинг-стопу задаются в пунктах. Трейлинг подтягивает стоп только после прохода цены на TrailingStopPips + TrailingStepPips, как в исходном советнике.

Параметры

Имя Тип Значение по умолчанию Описание
OrderVolume decimal 0.1 Объём ордера в лотах/контрактах.
StopLossPips int 50 Стоп-лосс в пунктах. Ноль отключает фиксированный стоп.
TakeProfitPips int 140 Тейк-профит в пунктах. Ноль отключает цель.
TrailingStopPips int 15 Дистанция трейлинг-стопа. Ноль отключает сопровождение.
TrailingStepPips int 5 Дополнительное движение цены перед подтяжкой трейлинга. При включённом трейлинге должно быть > 0.
MaPeriod int 12 Период скользящей средней.
MaShift int 3 Количество завершённых баров для сдвига средней вперёд.
MaMethod MovingAverageKind Weighted Метод расчёта СС (Simple, Exponential, Smoothed, Weighted).
AppliedPrice AppliedPriceMode Weighted Цена свечи для расчёта (Close, Open, High, Low, Median, Typical, Weighted).
OnlyOnePosition bool false Разрешить только одну открытую позицию.
ReverseSignals bool false Инвертировать направления сигналов.
CloseOpposite bool false Закрывать противоположные позиции перед новым входом.
CandleType DataType 1 минута Тип/таймфрейм свечей, используемый в расчёте.

Примечания

  • Размер пункта автоматически корректируется для инструментов с 3/5 десятичными знаками, как в MetaTrader.
  • Проверка трейлинг-стопа идентична MQL: если TrailingStopPips > 0, но TrailingStepPips <= 0, запуск завершается исключением.
  • Все расчёты ведутся только по завершённым свечам, что упрощает повторяемость тестов и оптимизаций.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class MaTrendStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MaTrendStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}