Exp TEMA — это порт на StockSharp экспертного советника MetaTrader Exp_TEMA.mq5. Оригинальная система анализирует несколько валютных пар и отслеживает знак наклона Triple Exponential Moving Average (TEMA). При смене знака наклона советник либо открывает позицию по направлению нового тренда, либо закрывает противоположную. В C# версии сохранена логика индикатора, но торговля выполняется по одному инструменту, который назначается стратегии в StockSharp.
Логика торговли
Стратегия работает с завершёнными свечами, полученными от выбранного параметра CandleType. На закрытии каждой свечи рассчитывается TEMA с периодом TemaPeriod. Для воспроизведения схемы определения наклона из MQL5 сравниваются три подряд идущих значения индикатора:
tema[0] — значение на текущей свече, tema[1] — на предыдущей, tema[2] — два бара назад.
Краткосрочный наклон — d1 = tema[1] - tema[2], более старый наклон — d2 = tema[2] - tema[3].
Покупка срабатывает, когда наклон разворачивается вверх (d2 < 0 и d1 > 0). Перед входом закрываются короткие позиции, затем отправляется рыночная заявка объёмом Volume + |Position|.
Продажа выполняется при развороте наклона вниз (d2 > 0 и d1 < 0). Сначала закрываются длинные позиции, после чего выставляется рыночная продажа объёмом Volume + |Position|.
Такое построение позволяет сохранить момент генерации сигналов, не обращаясь напрямую к историческим буферам и соблюдая требования к использованию высокоуровневого 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(...), без ручного копирования буферов, что соответствует внутренним рекомендациям репозитория.
Рекомендации по использованию
Назначьте стратегии нужный инструмент и установите CandleType, совпадающий с рабочим таймфреймом.
Подберите расстояния StopLossPoints и TakeProfitPoints исходя из волатильности инструмента.
При необходимости используйте оптимизацию параметров TemaPeriod, StopLossPoints и TakeProfitPoints, чтобы повторить подбор настроек из MetaTrader.
Контролируйте работу стратегии через встроенную область графика с ценой, индикатором 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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_tema_strategy(Strategy):
def __init__(self):
super(exp_tema_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._tema_period = self.Param("TemaPeriod", 40)
self._prev1 = None
self._prev2 = None
self._prev3 = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def TemaPeriod(self):
return self._tema_period.Value
@TemaPeriod.setter
def TemaPeriod(self, value):
self._tema_period.Value = value
def OnReseted(self):
super(exp_tema_strategy, self).OnReseted()
self._prev1 = None
self._prev2 = None
self._prev3 = None
def OnStarted2(self, time):
super(exp_tema_strategy, self).OnStarted2(time)
self._prev1 = None
self._prev2 = None
self._prev3 = None
tema = ExponentialMovingAverage()
tema.Length = self.TemaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(tema, self._process_candle).Start()
def _process_candle(self, candle, tema_value):
if candle.State != CandleStates.Finished:
return
val = float(tema_value)
if self._prev1 is None:
self._prev1 = val
return
if self._prev2 is None:
self._prev2 = self._prev1
self._prev1 = val
return
if self._prev3 is None:
self._prev3 = self._prev2
self._prev2 = self._prev1
self._prev1 = val
return
dtema1 = self._prev1 - self._prev2
dtema2 = self._prev2 - self._prev3
turned_up = dtema2 < 0 and dtema1 > 0
turned_down = dtema2 > 0 and dtema1 < 0
if turned_up and self.Position <= 0:
self.BuyMarket()
elif turned_down and self.Position >= 0:
self.SellMarket()
self._prev3 = self._prev2
self._prev2 = self._prev1
self._prev1 = val
def CreateClone(self):
return exp_tema_strategy()