Открыть на GitHub

2526 TDI-2 Re-Open

Обзор

Стратегия представляет собой перенос советника MetaTrader 5 Exp_TDI-2_ReOpen на платформу StockSharp. Торговля ведётся по индикатору Trend Direction Index (TDI-2) с сохранением оригинальной логики повторных входов. Версия на C# использует высокоуровневый API StockSharp: она реагирует на пересечения между линией динамики TDI и индексной линией, добавляет позиции при благоприятном движении цены на заданное количество пунктов и может сопровождать сделки защитными стопами.

Индикаторы

  • TDI-2 – пользовательский индикатор, реализованный в проекте. Формирует две линии:
    • DirectionalПериод × сглаженный импульс, где импульс равен разнице между выбранной ценой и ценой Период баров назад.
    • Index|Directional| − (2 × Период × сглаженное |импульса| с длиной 2×Период − |импульс|).
  • Поддерживаемые методы сглаживания: простое, экспоненциальное, сглаженное (RMA) и линейно-взвешенное среднее.
  • Набор типов цен полностью повторяет MQL-реализацию, включая формулы TrendFollow и Demark.

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

  1. После закрытия каждой свечи считываются значения TDI-2 на свече, заданной параметром Signal Bar (по умолчанию предыдущая закрытая свеча), и ещё на одну свечу глубже.
  2. Если линия Directional находилась выше линии Index и перешла ниже:
    • При включённом параметре Allow Long Entries и отсутствии длинной позиции готовится новая покупка.
    • При наличии короткой позиции и разрешённом параметре Allow Short Exits она закрывается.
  3. Если линия Directional находилась ниже линии Index и пересекла её снизу вверх:
    • При включённом параметре Allow Short Entries и отсутствии короткой позиции формируется продажа.
    • При наличии длинной позиции и разрешённом параметре Allow Long Exits она закрывается.
  4. Логика повторных входов (масштабирования):
    • Для длинных позиций контролируется цена последней покупки. Если цена движется в прибыль на величину Re-entry Step (points) и количество совершённых покупок меньше Max Entries, открывается дополнительная покупка базовым объёмом.
    • Для коротких позиций используется аналогичный алгоритм с последней продажей.
  5. При смене направления стратегия отправляет единственную рыночную заявку, достаточную для закрытия встречной позиции и открытия новой позиции требуемого объёма.
  6. Параметры стоп-лосса и тейк-профита, если заданы, активируются через StartProtection с учётом PriceStep инструмента.

Параметры

Имя Описание Значение по умолчанию
Money Management Базовый объём рыночной заявки. 0.1
Max Entries Максимальное число входов в одном направлении (включая первый). 10
Stop Loss (points) Дистанция стоп-лосса в пунктах. 1000
Take Profit (points) Дистанция тейк-профита в пунктах. 2000
Slippage (points) Параметр сохранён для совместимости, в данной реализации не используется. 10
Re-entry Step (points) Минимальное благоприятное движение для добавления к позиции. 300
Allow Long/Short Entries Разрешение на открытие длинных/коротких позиций. true
Allow Long/Short Exits Разрешение на закрытие длинных/коротких позиций. true
Candle Type Тип свечей для расчётов. H4
TDI Smoothing Метод сглаживания индикатора TDI-2. Simple MA
TDI Period Период импульса. 20
TDI Phase Зарезервирован для совместимости, на поддерживаемые методы не влияет. 15
Applied Price Тип цены для TDI-2. Close
Signal Bar Глубина просмотра закрытых свечей при анализе пересечений. 1

Дополнительные замечания

  • Реализованы только те методы сглаживания, которые доступны в StockSharp (SMA, EMA, SMMA, LWMA). Экзотические методы MQL (JJMA, T3 и др.) не поддерживаются.
  • Параметр TDI Phase сохранён для соответствия оригиналу и не влияет на расчёты при используемых методах сглаживания.
  • Параметр Slippage (points) присутствует для полноты, но высокоуровневый API StockSharp его не применяет.
  • Счётчики повторных входов автоматически сбрасываются при выходе из позиции.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trend Direction Index re-entry strategy.
/// Trades based on crossings between the TDI momentum line and the TDI index line.
/// </summary>
public class Tdi2ReOpenStrategy : Strategy
{
	private readonly StrategyParam<int> _tdiPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _lastClose;
	private decimal? _directional;
	private decimal? _index;
	private decimal? _prevDirectional;
	private decimal? _prevIndex;

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

	public Tdi2ReOpenStrategy()
	{
		_tdiPeriod = Param(nameof(TdiPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("TDI Period", "Momentum lookback period", "Indicator")
			.SetOptimize(5, 30, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Data series", "General");
	}

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

		_lastClose = null;
		_directional = null;
		_index = null;
		_prevDirectional = null;
		_prevIndex = null;
	}

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

		_lastClose = null;
		_directional = null;
		_index = null;
		_prevDirectional = null;
		_prevIndex = null;

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

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

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

			var close = candle.ClosePrice;
			if (_lastClose is not decimal lastClose)
			{
				_lastClose = close;
				return;
			}

			var momentum = close - lastClose;
			_lastClose = close;
			var alpha = 2m / (TdiPeriod + 1m);

			if (_directional is not decimal prevDirectionalLine || _index is not decimal prevIndexLine)
			{
				_directional = momentum;
				_index = momentum;
				return;
			}

			var directional = prevDirectionalLine + alpha * (momentum - prevDirectionalLine);
			var index = prevIndexLine + alpha * (directional - prevIndexLine);

			if (_prevDirectional is not decimal prevDir || _prevIndex is not decimal prevIdx)
			{
				_directional = directional;
				_index = index;
				_prevDirectional = prevDirectionalLine;
				_prevIndex = prevIndexLine;
				return;
			}

			var crossUp = prevDir <= prevIdx && directional > index;
			var crossDown = prevDir >= prevIdx && directional < index;

			if (crossUp && Position <= 0)
				BuyMarket();
			else if (crossDown && Position >= 0)
				SellMarket();

			_directional = directional;
			_index = index;
			_prevDirectional = directional;
			_prevIndex = index;
		}
	}
}