Открыть на GitHub

Стратегия Exp TEMA

Exp TEMA — это порт на StockSharp экспертного советника MetaTrader Exp_TEMA.mq5. Оригинальная система анализирует несколько валютных пар и отслеживает знак наклона Triple Exponential Moving Average (TEMA). При смене знака наклона советник либо открывает позицию по направлению нового тренда, либо закрывает противоположную. В C# версии сохранена логика индикатора, но торговля выполняется по одному инструменту, который назначается стратегии в StockSharp.

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

Стратегия работает с завершёнными свечами, полученными от выбранного параметра CandleType. На закрытии каждой свечи рассчитывается TEMA с периодом TemaPeriod. Для воспроизведения схемы определения наклона из MQL5 сравниваются три подряд идущих значения индикатора:

  1. tema[0] — значение на текущей свече, tema[1] — на предыдущей, tema[2] — два бара назад.
  2. Краткосрочный наклон — d1 = tema[1] - tema[2], более старый наклон — d2 = tema[2] - tema[3].
  3. Покупка срабатывает, когда наклон разворачивается вверх (d2 < 0 и d1 > 0). Перед входом закрываются короткие позиции, затем отправляется рыночная заявка объёмом Volume + |Position|.
  4. Продажа выполняется при развороте наклона вниз (d2 > 0 и d1 < 0). Сначала закрываются длинные позиции, после чего выставляется рыночная продажа объёмом Volume + |Position|.
  5. Защитные выходы повторяют оригинальные флаги остановки: отрицательный d1 закрывает лонг, положительный d1 закрывает шорт.

Такое построение позволяет сохранить момент генерации сигналов, не обращаясь напрямую к историческим буферам и соблюдая требования к использованию высокоуровневого API StockSharp.

Параметры

Параметр Значение по умолчанию Описание
TemaPeriod 15 Период Triple Exponential Moving Average.
TradeVolume 1 Базовый торговый объём. При реверсе фактический объём равен TradeVolume + |Position|.
StopLossPoints 1000 Дистанция стоп-лосса в шагах цены. Передаётся в StartProtection, если значение положительное.
TakeProfitPoints 2000 Дистанция тейк-профита в шагах цены. Передаётся в StartProtection, если значение положительное.
CandleType 15-минутные свечи Тип свечей для расчёта индикатора. Выберите таймфрейм, соответствующий графику из MetaTrader.

Все параметры созданы через StrategyParam<T>, поэтому их можно оптимизировать в Designer.

Отличия от MQL5-версии

  • В MT5 советник обрабатывает до двенадцати символов одновременно. В StockSharp стратегия привязана к конкретному Security, поэтому порт работает с одним инструментом. Для мультивалютной торговли запустите несколько экземпляров стратегии.
  • Управление ордерами реализовано через BuyMarket/SellMarket и StartProtection, что соответствует рыночным заявкам, стопам и тейк-профитам оригинала, но в терминах высокоуровневого API.
  • Индикатор подключается через SubscribeCandles().Bind(...), без ручного копирования буферов, что соответствует внутренним рекомендациям репозитория.

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

  1. Назначьте стратегии нужный инструмент и установите CandleType, совпадающий с рабочим таймфреймом.
  2. Подберите расстояния StopLossPoints и TakeProfitPoints исходя из волатильности инструмента.
  3. При необходимости используйте оптимизацию параметров TemaPeriod, StopLossPoints и TakeProfitPoints, чтобы повторить подбор настроек из MetaTrader.
  4. Контролируйте работу стратегии через встроенную область графика с ценой, индикатором TEMA и отмеченными сделками.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that trades TEMA slope reversals from the original Exp_TEMA expert advisor.
/// Enters long when TEMA slope turns positive, short when negative.
/// </summary>
public class ExpTemaStrategy : Strategy
{
	private readonly StrategyParam<int> _temaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _tema;
	private decimal? _prev1;
	private decimal? _prev2;
	private decimal? _prev3;

	public int TemaPeriod
	{
		get => _temaPeriod.Value;
		set => _temaPeriod.Value = value;
	}

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

	public ExpTemaStrategy()
	{
		_temaPeriod = Param(nameof(TemaPeriod), 40)
			.SetGreaterThanZero()
			.SetDisplay("TEMA Period", "Length of Triple Exponential Moving Average", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for TEMA calculation", "General");
	}

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

		_tema = new ExponentialMovingAverage { Length = TemaPeriod };
		_prev1 = null;
		_prev2 = null;
		_prev3 = null;

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

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

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

		if (!_tema.IsFormed)
		{
			_prev1 = temaValue;
			return;
		}

		if (_prev1 is null)
		{
			_prev1 = temaValue;
			return;
		}

		if (_prev2 is null)
		{
			_prev2 = _prev1;
			_prev1 = temaValue;
			return;
		}

		if (_prev3 is null)
		{
			_prev3 = _prev2;
			_prev2 = _prev1;
			_prev1 = temaValue;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var dtema1 = _prev1.Value - _prev2.Value;
		var dtema2 = _prev2.Value - _prev3.Value;

		// Entry on slope reversal
		var turnedUp = dtema2 < 0 && dtema1 > 0;
		var turnedDown = dtema2 > 0 && dtema1 < 0;

		if (turnedUp && Position <= 0)
		{
			BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (turnedDown && Position >= 0)
		{
			SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prev3 = _prev2;
		_prev2 = _prev1;
		_prev1 = temaValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_tema = null;
		_prev1 = null;
		_prev2 = null;
		_prev3 = null;

		base.OnReseted();
	}
}