Открыть на GitHub

My TS15 Moving Average Trailing Stop

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

Стратегия повторяет логику советника my_ts15.mq5: она не открывает сделки самостоятельно, а занимается только управлением защитного стоп-ордера уже существующей позиции. В качестве «направляющей» используется скользящая средняя (по умолчанию линейно-взвешенная), вокруг которой формируется динамический трейлинг. Алгоритм:

  • Получает значение скользящей средней по завершённым свечам.
  • Сравнивает прогресс цены и положение скользящей, вычисляя две альтернативные точки для стопа.
  • Передвигает стоп только тогда, когда новое значение лучше предыдущего не менее чем на TrailStepPoints.
  • При необходимости ограничивает максимальный убыток: стоп «прижимается» к границе, а при пробое позиция закрывается немедленно.

Таким образом, модуль можно запускать параллельно с любым сигнализатором или ручной торговлей на том же инструменте.

Логика сопровождения

  1. Подписаться на выбранный тип свечей и привязать индикатор скользящей средней через BindEx, избегая ручного доступа к буферам.
  2. После закрытия свечи сохранить значение индикатора и взять выборку, отстоящую на MaBarsTrail + MaShift баров назад.
  3. Пересчитать все значения, заданные в пунктах, в абсолютные цены через шаг цены инструмента.
  4. Для длинной позиции взять минимум из двух кандидатов: «МА минус отступ» и «цена минус прибыльный отступ». Дополнительно ограничить уровень отрицательным отступом и, при необходимости, максимальным убытком.
  5. Для короткой позиции взять максимум из «МА плюс отступ» и «цена плюс прибыльный отступ», далее применить негативный и максимальный пороги.
  6. Передвигать стоп только при улучшении больше либо равном TrailStepPoints (если значение равно нулю — любое улучшение).
  7. При включённом EnforceMaxStopLoss и пробое цены за предел MaxStopLossPoints позиция закрывается сразу же.

Используемая цена определяется параметром MaPrice, что соответствует оригинальному советнику, в котором LWMA рассчитывалась по PRICE_WEIGHTED.

Параметры

Параметр Значение по умолчанию Описание
MaPeriod 50 Длина скользящей средней, управляющей трейлингом.
MaShift 0 Дополнительный сдвиг (в барах) при запросе значения МА.
MaMethod LinearWeighted Метод сглаживания МА (простая, экспоненциальная, сглаженная, линейно-взвешенная).
MaPrice Weighted Цена свечи, подаваемая на вход индикатора.
MaBarsTrail 1 Количество завершённых баров между текущей свечой и значением МА.
TrailBehindMaPoints 5 Отступ в пунктах между стопом и скользящей средней.
TrailBehindPricePoints 30 Отступ в пунктах от текущей цены при движении в прибыль.
TrailBehindNegativePoints 60 Отступ в пунктах от текущей цены при движении в убыток.
TrailStepPoints 0 Минимальное улучшение (в пунктах) для смещения стопа. Ноль — обновлять всегда.
EnforceMaxStopLoss false Включает контроль максимального убытка.
MaxStopLossPoints 100 Максимальная допустимая дистанция убытка в пунктах.
ShowIndicator true Рисовать индикатор и сделки на графике при наличии UI.
CandleType M1 Тип свечей, управляющий расчётами.

Все величины в пунктах пересчитываются в абсолютные цены через шаг цены Security.PriceStep.

Особенности конвертации

  • В MQL-версии скользящая средняя обновлялась через ручной вызов Refresh и доступ к буферу. Здесь индикатор обрабатывается автоматически через BindEx, что соответствует требованиям проекта.
  • В оригинале использовались Bid/Ask. В StockSharp для закрытых свечей доступен только выбранный тип цены, поэтому сравнительный анализ выполняется на основе значения MaPrice. Это обеспечивает сопоставимость с исходным алгоритмом, поскольку индикатор строится по той же цене.
  • Операции PositionModify заменены отменой и повторной регистрацией стоп-ордеров (SellStop для лонга, BuyStop для шорта). Храним последнюю цену стопа, чтобы воспроизвести проверку trail - sl >= step.
  • Флаг pre_init реализован как EnforceMaxStopLoss: если цена ушла дальше допустимого максимума, позиция закрывается немедленно.
  • Логика входа отсутствует — стратегия рассчитана на совместную работу с другими модулями.

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

  1. Запускайте стратегию на том же инструменте, где открываются позиции.
  2. Проверяйте соответствие значений в пунктах шагу цены. Для большинства валютных пар pip равен десятикратному шагу.
  3. Чтобы уменьшить количество модификаций на малоликвидных инструментах, задайте положительное TrailStepPoints.
  4. Если риск-менеджмент решается другой системой, оставьте EnforceMaxStopLoss выключенным.
  5. При подборе параметров держите ShowIndicator включённым, чтобы визуально оценивать работу трейлинга.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// My TS15 strategy: WMA trend following with trailing stop management.
/// Enters on price crossing WMA, exits with trailing stop logic.
/// </summary>
public class MyTs15Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _trailMultiplier;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _entryPrice;
	private decimal _bestPrice;
	private bool _wasBullish;
	private bool _hasPrevSignal;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal TrailMultiplier { get => _trailMultiplier.Value; set => _trailMultiplier.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public MyTs15Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_maPeriod = Param(nameof(MaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "WMA period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for trailing", "Indicators");
		_trailMultiplier = Param(nameof(TrailMultiplier), 3m)
			.SetDisplay("Trail Multiplier", "ATR multiplier for trailing stop", "Risk");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 12)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_bestPrice = 0m;
		_wasBullish = false;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_entryPrice = 0;
		_bestPrice = 0;
		_hasPrevSignal = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var wma = new WeightedMovingAverage { Length = MaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(wma, atr, ProcessCandle).Start();
	}

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

		var close = candle.ClosePrice;
		var trailDist = atrValue * TrailMultiplier;
		var isBullish = close > wmaValue;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		// Trailing stop check
		if (Position > 0)
		{
			if (close > _bestPrice) _bestPrice = close;
			if (_bestPrice - close > trailDist)
			{
				SellMarket();
				_entryPrice = 0;
				_bestPrice = 0;
				_candlesSinceTrade = 0;
				return;
			}
		}
		else if (Position < 0)
		{
			if (close < _bestPrice) _bestPrice = close;
			if (close - _bestPrice > trailDist)
			{
				BuyMarket();
				_entryPrice = 0;
				_bestPrice = 0;
				_candlesSinceTrade = 0;
				return;
			}
		}

		// Entry signals
		if (_hasPrevSignal && isBullish != _wasBullish && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (isBullish && Position <= 0)
			{
				BuyMarket();
				_entryPrice = close;
				_bestPrice = close;
				_candlesSinceTrade = 0;
			}
			else if (!isBullish && Position >= 0)
			{
				SellMarket();
				_entryPrice = close;
				_bestPrice = close;
				_candlesSinceTrade = 0;
			}
		}

		_wasBullish = isBullish;
		_hasPrevSignal = true;
	}
}