Открыть на GitHub

Стратегия TrueSort 1001

TrueSort 1001 — строгая трендовая система, повторяющая оригинальный экспертный советник MQL. Стратегия отслеживает пять простых скользящих средних и работает только тогда, когда они сохраняют идеальный порядок на трёх подряд закрытых свечах. Дополнительный фильтр — растущий индекс направленного движения (ADX). После входа позиция защищается трейлинг-стопом в шагах цены и закрывается сразу, как только скользящие средние теряют синхронность.

Логика

Фильтр тренда и импульса

  • Рассчитываются пять SMA (по умолчанию периоды 10, 20, 50, 100 и 200) на выбранном таймфрейме.
  • Для покупок быстрые SMA должны строго располагаться выше медленных на каждой из трёх последних завершённых свечей: SMA10 > SMA20 > SMA50 > SMA100 > SMA200.
  • Для продаж требуется обратный порядок на тех же трёх свечах.
  • ADX с периодом AdxPeriod должен находиться выше AdxThreshold, причём текущее значение должно быть больше предыдущего, что подтверждает нарастающую силу тренда.

Условия входа

  1. Открытых позиций нет.
  2. Три исторические свечи удовлетворяют правилу строгой сортировки SMA.
  3. Условие по ADX выполняется.
  4. На закрытии текущей свечи выставляется рыночная заявка объёмом Volume.

Условия выхода

  • Потеря порядка SMA. Если текущая свеча закрывается и набор скользящих средних перестаёт строго соответствовать направлению сделки, позиция закрывается.
  • Трейлинг-стоп. Значение StopLossPoints переводится в абсолютное ценовое расстояние умножением на PriceStep инструмента. Для лонгов стоп инициализируется на максимуме между SMA100 и Close - расстояние. Для шортов — на минимуме между SMA100 и Close + расстояние. После каждой свечи стоп подтягивается к цене, но никогда не отдаляется. При пересечении стопа цена закрывает позицию по рынку.

Дополнительные особенности

  • Обрабатываются только закрытые свечи; незавершённые бары игнорируются.
  • Для воспроизведения логики MQL со смещениями индикаторов хранится последние три значения каждой SMA во внутренних буферах, без вызовов GetValue().
  • ADX обрабатывается через BindEx, торговля запускается только когда данные полностью сформированы и стратегия находится онлайн.

Параметры

Название Значение по умолчанию Описание
Volume 0.1 Объём каждой рыночной заявки.
StopLossPoints 100 Дистанция трейлинг-стопа в шагах цены. 0 отключает трейлинг.
Sma10Length 10 Период самой быстрой SMA.
Sma20Length 20 Период второй SMA.
Sma50Length 50 Период средней SMA.
Sma100Length 100 Период SMA, участвующей в фильтре и инициализации стопа.
Sma200Length 200 Самая медленная SMA для подтверждения тренда.
AdxPeriod 14 Период индикатора ADX.
AdxThreshold 25 Минимальный уровень ADX и требование роста для входа.
CandleType TimeSpan.FromHours(1).TimeFrame() Тип свечей для расчётов индикаторов.

Детали реализации

  • Используется высокоуровневый API StockSharp: подписка на свечи и одновременная привязка пяти SMA и ADX в одном конвейере.
  • Буферы длиной три значения сохраняют последние значения SMA, что позволяет точно повторить сдвиги из MQL без прямого обращения к истории индикаторов.
  • Трейлинг-стоп ведётся вручную; при этом вызывается StartProtection(), чтобы стандартная инфраструктура защиты была готова к расширению.
  • В коде добавлены подробные комментарии на английском языке.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend-following strategy requiring strict moving average alignment
/// and rising volatility (ATR) before entering trades.
/// Exits when alignment is lost.
/// </summary>
public class TrueSort1001Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _midLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevMid;
	private decimal _prevSlow;
	private decimal _prevAtr;
	private decimal _entryPrice;

	public TrueSort1001Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastLength = Param(nameof(FastLength), 10)
			.SetDisplay("Fast SMA", "Fast SMA period.", "Indicators");

		_midLength = Param(nameof(MidLength), 50)
			.SetDisplay("Mid SMA", "Medium SMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 200)
			.SetDisplay("Slow SMA", "Slow SMA period.", "Indicators");

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

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

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int MidLength
	{
		get => _midLength.Value;
		set => _midLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

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

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

		_prevFast = 0;
		_prevMid = 0;
		_prevSlow = 0;
		_prevAtr = 0;
		_entryPrice = 0;
	}

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

		_prevFast = 0;
		_prevMid = 0;
		_prevSlow = 0;
		_prevAtr = 0;
		_entryPrice = 0;

		var fast = new SimpleMovingAverage { Length = FastLength };
		var mid = new SimpleMovingAverage { Length = MidLength };
		var slow = new SimpleMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevFast == 0 || _prevMid == 0 || _prevSlow == 0)
		{
			_prevFast = fastVal;
			_prevMid = midVal;
			_prevSlow = slowVal;
			_prevAtr = atrVal;
			return;
		}

		var close = candle.ClosePrice;
		var bullishAligned = fastVal > midVal && midVal > slowVal;
		var bearishAligned = fastVal < midVal && midVal < slowVal;
		var atrRising = atrVal > _prevAtr;

		// Exit on alignment lost
		if (Position > 0 && !bullishAligned)
		{
			SellMarket();
		}
		else if (Position < 0 && !bearishAligned)
		{
			BuyMarket();
		}

		// Entry on alignment + rising ATR
		if (Position == 0)
		{
			if (bullishAligned && atrRising && close > fastVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (bearishAligned && atrRising && close < fastVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevMid = midVal;
		_prevSlow = slowVal;
		_prevAtr = atrVal;
	}
}