Открыть на GitHub

Стратегия TST Pullback Reversal

Обзор

TST Pullback Reversal — порт оригинального эксперта MetaTrader 4 TST.mq4, переписанный на высокоуровневом API StockSharp. Стратегия отслеживает свечи, где после формирования экстремума цена резко отошла от открытия, и пытается взять возврат к среднему. Работает в обе стороны и использует фиксированные стоп-лосс и тейк-профит в шагах цены.

Логика сигналов

  • Покупка

    1. Свеча закрылась ниже цены открытия (Open > Close).
    2. Разница между максимумом свечи и ценой закрытия превышает GapPoints * PriceStep.
    3. На текущей свече еще не было сделок. При выполнении условий закрывается короткая позиция (если была) и выставляется рыночная покупка на объем OrderVolume плюс объем, необходимый для переворота из шорта в лонг.
  • Продажа

    1. Свеча закрылась выше цены открытия (Close > Open).
    2. Разница между ценой закрытия и минимумом свечи превышает GapPoints * PriceStep.
    3. На текущей свече еще не было сделок. При выполнении условий закрывается длинная позиция (если была) и отправляется рыночная продажа на объем OrderVolume плюс объем для переворота из лонга в шорт.

Управление позицией

  • После входа сразу рассчитываются уровни стоп-лосса и тейк-профита на основе параметров StopLossPoints и TakeProfitPoints.
  • На каждой завершенной свече проверяются экстремумы: если минимум пробил стоп или максимум достиг тейка, позиция закрывается. Приоритет у стоп-лосса.
  • После выхода уровни очищаются, но сохраняется время свечи, чтобы не допустить повторного входа в ту же свечу (аналог функции NevBar() в исходнике MT4).

Параметры

  • StopLossPoints (по умолчанию 500): расстояние до защитного стопа в шагах цены.
  • TakeProfitPoints (по умолчанию 100): расстояние до тейк-профита в шагах цены.
  • GapPoints (по умолчанию 500): минимальный откат от экстремума до закрытия для формирования сигнала.
  • OrderVolume (по умолчанию 0.1): объем новой рыночной заявки.
  • CandleType (по умолчанию 1 час): таймфрейм свечей, которые подписывает стратегия через SubscribeCandles.

Все расстояния умножаются на PriceStep инструмента; если шаг цены не задан, используется значение 1.

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

  • Используется только высокоуровневый API StockSharp, без собственных коллекций данных.
  • Обработка идет по завершенным свечам, что обеспечивает совместимость с Designer и моделирует внутрисвечные решения советника.
  • Флаг _lastSignalBarTime повторяет механику NevBar() и ограничивает стратегию одним входом на свечу.
  • При смене направления стратегия автоматически закрывает противоположную позицию и открывает новую в одном рыночном приказе.
  • Стоп и тейк реализованы внутри кода стратегии и срабатывают при достижении уровней по значениям High/Low свечи.

Практические советы

  • Подбирайте GapPoints в зависимости от волатильности инструмента: большие значения уменьшают количество сигналов, но фильтруют мелкие откаты.
  • Если нужны более точные срабатывания стопов и тейков, используйте более короткий таймфрейм CandleType.
  • Рассмотрите добавление фильтров тренда, торговых сессий или объема перед запуском на реальном счете, чтобы снизить количество ложных входов.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// TST Pullback Reversal: buys after deep pullback from candle high,
/// sells after rally from candle low. Uses ATR for thresholds.
/// </summary>
public class TstStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _pullbackMultiplier;
	private readonly StrategyParam<decimal> _stopMultiplier;
	private readonly StrategyParam<decimal> _takeMultiplier;

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

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

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

		_pullbackMultiplier = Param(nameof(PullbackMultiplier), 0.5m)
			.SetDisplay("Pullback Mult", "ATR multiplier for pullback threshold.", "Signals");

		_stopMultiplier = Param(nameof(StopMultiplier), 2.0m)
			.SetDisplay("Stop Mult", "ATR multiplier for stop loss.", "Risk");

		_takeMultiplier = Param(nameof(TakeMultiplier), 1.0m)
			.SetDisplay("Take Mult", "ATR multiplier for take profit.", "Risk");
	}

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

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

	public decimal PullbackMultiplier
	{
		get => _pullbackMultiplier.Value;
		set => _pullbackMultiplier.Value = value;
	}

	public decimal StopMultiplier
	{
		get => _stopMultiplier.Value;
		set => _stopMultiplier.Value = value;
	}

	public decimal TakeMultiplier
	{
		get => _takeMultiplier.Value;
		set => _takeMultiplier.Value = value;
	}

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

		_entryPrice = 0;
		_stopPrice = 0;
		_takePrice = 0;

		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (atrVal <= 0)
			return;

		var close = candle.ClosePrice;
		var open = candle.OpenPrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;
		var threshold = atrVal * PullbackMultiplier;
		var stopDist = atrVal * StopMultiplier;
		var takeDist = atrVal * TakeMultiplier;

		// Risk management
		if (Position > 0)
		{
			if (_stopPrice > 0 && close <= _stopPrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_takePrice = 0;
				return;
			}
			if (_takePrice > 0 && close >= _takePrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_takePrice = 0;
				return;
			}
		}
		else if (Position < 0)
		{
			if (_stopPrice > 0 && close >= _stopPrice)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_takePrice = 0;
				return;
			}
			if (_takePrice > 0 && close <= _takePrice)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
				_takePrice = 0;
				return;
			}
		}

		// Entry: deep pullback from high = buy reversal
		if (Position == 0)
		{
			if (open > close && high - close > threshold)
			{
				_entryPrice = close;
				_stopPrice = close - stopDist;
				_takePrice = close + takeDist;
				BuyMarket();
			}
			else if (close > open && close - low > threshold)
			{
				_entryPrice = close;
				_stopPrice = close + stopDist;
				_takePrice = close - takeDist;
				SellMarket();
			}
		}
	}
}