Открыть на GitHub

Стратегия Get Trend

Обзор

Стратегия является портом советника MetaTrader Get trend на платформу StockSharp. Алгоритм работает на 15-минутных свечах, а подтверждение тренда берёт с часового таймфрейма. Для поиска точек входа используется сочетание сглаженных скользящих средних и стохастического осциллятора.

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

  • Рабочий таймфрейм: 15-минутные свечи отвечают за генерацию сигналов и исполнение сделок.
  • Подтверждающий таймфрейм: Часовые свечи предоставляют сглаженную среднюю и цену закрытия, которые задают направление тренда.
  • Фильтр тренда: Цена закрытия на обоих таймфреймах должна находиться по одну сторону от соответствующих сглаженных средних. Дополнительно цена на M15 должна подходить к средней ближе указанного порога, чтобы вход осуществлялся от отката.
  • Импульсный триггер: Для покупок требуется пересечение %K вверх через %D в зоне перепроданности (ниже 20). Для продаж — зеркальное условие в зоне перекупленности (выше 80).
  • Управление позицией: Стоп-лосс и тейк-профит задаются в пунктах от цены входа. Опциональный трейлинг-стоп подтягивается вслед за ценой при достаточном движении в прибыль.

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

Покупка

  1. Закрытие свечи M15 ниже своей сглаженной средней.
  2. Закрытие свечи H1 ниже своей сглаженной средней.
  3. Расстояние между ценой закрытия M15 и её средней не превышает параметр Price Threshold.
  4. Стохастические линии %K и %D находятся ниже 20.
  5. Предыдущее значение %K было ниже %D, а текущее пересекает %D сверху.
  6. Нет открытой длинной позиции (при наличии шорта он закрывается и позиция разворачивается).

Продажа

  1. Закрытие свечи M15 выше своей сглаженной средней.
  2. Закрытие свечи H1 выше своей сглаженной средней.
  3. Расстояние между ценой закрытия M15 и её средней не превышает Price Threshold.
  4. Стохастические линии %K и %D находятся выше 80.
  5. Предыдущее значение %K было выше %D, а текущее пересекает %D снизу.
  6. Нет открытой короткой позиции (при наличии лонга он закрывается и позиция разворачивается).

Выход из позиции

  • Стоп-лосс: Фиксированное расстояние в пунктах от цены входа.
  • Тейк-профит: Фиксированное расстояние в пунктах от цены входа.
  • Трейлинг-стоп: При достижении заданного профита стоп подтягивается к цене, сохраняя заданный отступ.

Параметры

Параметр Описание Значение по умолчанию
M15CandleType Тип свечей для основного расчёта. 15 минут
H1CandleType Тип свечей для подтверждения тренда. 1 час
MaM15Length Период сглаженной средней на M15. 99
MaH1Length Период сглаженной средней на H1. 184
StochasticLength Период %K стохастика. 27
StochasticSignalLength Период сглаживания %D. 3
ThresholdPoints Допустимое отклонение цены от средней (в пунктах). 10
TakeProfitPoints Дистанция тейк-профита (в пунктах). 540
StopLossPoints Дистанция стоп-лосса (в пунктах). 90
TrailingStopPoints Дистанция трейлинг-стопа (в пунктах). 20
TradeVolume Базовый объём заявки. 0.1

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

Особенности реализации

  • Используется высокоуровневый API StockSharp: подписка на свечи и связывание индикаторов через BindEx, что избавляет от ручной работы с буферами.
  • Логика трейлинг-стопа повторяет оригинал: активируется при накоплении прибыли и постепенно подтягивает уровень стопа.
  • Перед разворотом позиции активные заявки снимаются, чтобы избежать конфликтов на бирже.
  • На графике отображаются 15-минутные свечи со сглаженной средней и отдельная панель со стохастическим осциллятором.

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

  • Подбирайте подходящий тип свечей в зависимости от поставщика данных (возможна замена на объёмные или другие типы при наличии соответствующего DataType).
  • Настраивайте порог, стопы и тейк-профит под волатильность конкретного инструмента и размер тика.
  • Стратегия лучше всего работает на трендовых инструментах с регулярными откатами к скользящей средней.
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>
/// Trend-following strategy converted from the MetaTrader "Get trend" expert.
/// </summary>
public class GetTrendStrategy : Strategy
{
	private readonly StrategyParam<DataType> _m15CandleType;
	private readonly StrategyParam<DataType> _h1CandleType;
	private readonly StrategyParam<int> _maM15Length;
	private readonly StrategyParam<int> _maH1Length;
	private readonly StrategyParam<int> _stochasticLength;
	private readonly StrategyParam<int> _stochasticSignalLength;
	private readonly StrategyParam<decimal> _thresholdPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _tradeVolume;

	private SmoothedMovingAverage _maM15;
	private SmoothedMovingAverage _maH1;
	private StochasticOscillator _stochastic;

	private decimal? _maH1Value;
	private decimal? _lastH1Close;

	private decimal? _prevStochFast;
	private decimal? _prevStochSlow;

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;

	/// <summary>
	/// Initializes a new instance of the <see cref="GetTrendStrategy"/> class.
	/// </summary>
	public GetTrendStrategy()
	{
		_m15CandleType = Param(nameof(M15CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("M15 Candle", "Primary timeframe", "General");

		_h1CandleType = Param(nameof(H1CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("H1 Candle", "Higher timeframe", "General");

		_maM15Length = Param(nameof(MaM15Length), 99)
			.SetGreaterThanZero()
			.SetDisplay("M15 MA Length", "Smoothed MA length on M15", "Indicators")
			;

		_maH1Length = Param(nameof(MaH1Length), 184)
			.SetGreaterThanZero()
			.SetDisplay("H1 MA Length", "Smoothed MA length on H1", "Indicators")
			;

		_stochasticLength = Param(nameof(StochasticLength), 27)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %K", "%K period", "Indicators")
			;

		_stochasticSignalLength = Param(nameof(StochasticSignalLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %D", "%D smoothing period", "Indicators");

		_thresholdPoints = Param(nameof(ThresholdPoints), 10m)
			.SetGreaterThanZero()
			.SetDisplay("Price Threshold", "Maximum distance from MA", "Filters")
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 540m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit distance", "Risk")
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 90m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss distance", "Risk")
			;

		_trailingStopPoints = Param(nameof(TrailingStopPoints), 20m)
			.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk")
			;

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume", "Risk");
	}

	/// <summary>
	/// Primary trading timeframe (M15 by default).
	/// </summary>
	public DataType M15CandleType
	{
		get => _m15CandleType.Value;
		set => _m15CandleType.Value = value;
	}

	/// <summary>
	/// Confirmation timeframe (H1 by default).
	/// </summary>
	public DataType H1CandleType
	{
		get => _h1CandleType.Value;
		set => _h1CandleType.Value = value;
	}

	/// <summary>
	/// Smoothed moving average length on the 15-minute chart.
	/// </summary>
	public int MaM15Length
	{
		get => _maM15Length.Value;
		set => _maM15Length.Value = value;
	}

	/// <summary>
	/// Smoothed moving average length on the hourly chart.
	/// </summary>
	public int MaH1Length
	{
		get => _maH1Length.Value;
		set => _maH1Length.Value = value;
	}

	/// <summary>
	/// %K period of the stochastic oscillator.
	/// </summary>
	public int StochasticLength
	{
		get => _stochasticLength.Value;
		set => _stochasticLength.Value = value;
	}

	/// <summary>
	/// %D period of the stochastic oscillator.
	/// </summary>
	public int StochasticSignalLength
	{
		get => _stochasticSignalLength.Value;
		set => _stochasticSignalLength.Value = value;
	}

	/// <summary>
	/// Maximum allowed distance between price and the M15 moving average in points.
	/// </summary>
	public decimal ThresholdPoints
	{
		get => _thresholdPoints.Value;
		set => _thresholdPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Trailing stop distance expressed in points.
	/// </summary>
	public decimal TrailingStopPoints
	{
		get => _trailingStopPoints.Value;
		set => _trailingStopPoints.Value = value;
	}

	/// <summary>
	/// Default order volume.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, M15CandleType), (Security, H1CandleType)];

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

		_maH1Value = null;
		_lastH1Close = null;
		_prevStochFast = null;
		_prevStochSlow = null;
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		Volume = TradeVolume;

		_maM15 = new SmoothedMovingAverage { Length = MaM15Length };
		_maH1 = new SmoothedMovingAverage { Length = MaH1Length };
		_stochastic = new StochasticOscillator();
		_stochastic.K.Length = StochasticLength;
		_stochastic.D.Length = StochasticSignalLength;

		// Subscribe to 15-minute candles and bind the required indicators.
		var m15Subscription = SubscribeCandles(M15CandleType);
		m15Subscription
			.BindEx(_maM15, _stochastic, ProcessM15Candle)
			.Start();

		// Subscribe to hourly candles for trend confirmation.
		var h1Subscription = SubscribeCandles(H1CandleType);
		h1Subscription
			.Bind(_maH1, ProcessH1Candle)
			.Start();

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, m15Subscription);
			DrawIndicator(priceArea, _maM15);
			DrawOwnTrades(priceArea);

			var oscillatorArea = CreateChartArea();
			if (oscillatorArea != null)
			{
				oscillatorArea.Title = "Stochastic";
				DrawIndicator(oscillatorArea, _stochastic);
			}
		}
	}

	private void ProcessH1Candle(ICandleMessage candle, decimal maValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Store the latest H1 moving average and close for trend checks.
		_maH1Value = maValue;
		_lastH1Close = candle.ClosePrice;
	}

	private void ProcessM15Candle(ICandleMessage candle, IIndicatorValue maValue, IIndicatorValue stochasticValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!maValue.IsFinal || !stochasticValue.IsFinal)
			return;

		var ma = maValue.ToDecimal();
		var stochTyped = (StochasticOscillatorValue)stochasticValue;

		if (stochTyped.K is not decimal stochFast || stochTyped.D is not decimal stochSlow)
			return;

		// Manage protective levels before looking for new entries.
		ManageOpenPosition(candle);

		if (_maH1Value is not decimal maH1 || _lastH1Close is not decimal priceH1)
			goto UpdateStochastic;

		var priceM15 = candle.ClosePrice;
		var priceStep = Security?.PriceStep ?? 1m;
		var threshold = ThresholdPoints * priceStep;

		var nearLowerBand = priceM15 < ma && priceH1 < maH1 && ma - priceM15 <= threshold;
		var nearUpperBand = priceM15 > ma && priceH1 > maH1 && priceM15 - ma <= threshold;

		var crossUp = _prevStochFast is decimal prevFastUp && _prevStochSlow is decimal prevSlowUp && prevFastUp < prevSlowUp && stochFast > stochSlow;
		var crossDown = _prevStochFast is decimal prevFastDown && _prevStochSlow is decimal prevSlowDown && prevFastDown > prevSlowDown && stochFast < stochSlow;

		if (nearLowerBand && stochSlow < 20m && stochFast < 20m && crossUp && Position <= 0)
		{
			EnterLong(candle.ClosePrice, priceStep);
		}
		else if (nearUpperBand && stochSlow > 80m && stochFast > 80m && crossDown && Position >= 0)
		{
			EnterShort(candle.ClosePrice, priceStep);
		}

	UpdateStochastic:
		_prevStochFast = stochFast;
		_prevStochSlow = stochSlow;
	}

	private void EnterLong(decimal entryPrice, decimal priceStep)
	{
		// Cancel opposite orders and flip the position if needed.
		CancelActiveOrders();

		BuyMarket();

		_entryPrice = entryPrice;
		_takePrice = entryPrice + TakeProfitPoints * priceStep;
		_stopPrice = entryPrice - StopLossPoints * priceStep;
	}

	private void EnterShort(decimal entryPrice, decimal priceStep)
	{
		// Cancel opposite orders and flip the position if needed.
		CancelActiveOrders();

		SellMarket();

		_entryPrice = entryPrice;
		_takePrice = entryPrice - TakeProfitPoints * priceStep;
		_stopPrice = entryPrice + StopLossPoints * priceStep;
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position == 0)
			return;

		var priceStep = Security?.PriceStep ?? 1m;
		var trailingDistance = TrailingStopPoints * priceStep;

		if (Position > 0)
		{
			if (_entryPrice is decimal entry && TrailingStopPoints > 0 && candle.ClosePrice - entry >= trailingDistance)
			{
				var candidate = candle.ClosePrice - trailingDistance;
				_stopPrice = _stopPrice.HasValue ? Math.Max(_stopPrice.Value, candidate) : candidate;
			}

			if (_takePrice is decimal take && candle.HighPrice >= take)
			{
				SellMarket();
				ResetProtection();
				return;
			}

			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
				ResetProtection();
			}
		}
		else if (Position < 0)
		{
			if (_entryPrice is decimal entry && TrailingStopPoints > 0 && entry - candle.ClosePrice >= trailingDistance)
			{
				var candidate = candle.ClosePrice + trailingDistance;
				_stopPrice = _stopPrice.HasValue ? Math.Min(_stopPrice.Value, candidate) : candidate;
			}

			if (_takePrice is decimal take && candle.LowPrice <= take)
			{
				BuyMarket();
				ResetProtection();
				return;
			}

			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
				ResetProtection();
			}
		}
	}

	private void ResetProtection()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takePrice = null;
	}
}