Стратегия повторяет логику советника MetaTrader Expert_2EMA_ITF, но реализована на высокоуровневом API StockSharp. В основе лежит пересечение двух экспоненциальных скользящих средних (EMA) и использование индикатора ATR для расчёта уровней входа, стоп-лосса и тейк-профита. Дополнительно встроен внутридневной фильтр, позволяющий исключать отдельные минуты, часы или дни недели.
Логика торговли
Рассчитываются значения быстрой и медленной EMA на выбранных свечах.
Фиксируется бычий сигнал, когда быстрая EMA пересекает медленную снизу вверх, и медвежий сигнал при обратном пересечении.
При бычьем сигнале выставляется отложенный Buy Limit со смещением LimitMultiplier * ATR от медленной EMA (при наличии котировок добавляется текущий спрэд). При медвежьем сигнале выставляется Sell Limit со смещением в противоположную сторону.
Для каждого сигнала сохраняются рассчитанные уровни стоп-лосса и тейк-профита, чтобы отправить защитные заявки сразу после исполнения входа.
Если отложенная заявка не исполняется за ExpirationBars свечей, она снимается автоматически.
Все сигналы проходят через фильтр времени: разрешённые минуты/часы/дни и битовые маски запрещённых периодов.
Используемые индикаторы
Fast EMA — быстрый отклик на изменение цены.
Slow EMA — определяет направление тренда.
Average True Range — оценивает волатильность и масштабирует все ценовые смещения.
Параметры
Параметр
Описание
Значение по умолчанию
CandleType
Тип/таймфрейм свечей для расчётов.
Свечи по 30 минут
FastEmaPeriod
Период быстрой EMA.
5
SlowEmaPeriod
Период медленной EMA (должен быть больше быстрого).
30
AtrPeriod
Период ATR.
7
LimitMultiplier
Множитель ATR для смещения цены входа.
1.2
StopLossMultiplier
Множитель ATR для стоп-лосса.
5
TakeProfitMultiplier
Множитель ATR для тейк-профита.
8
ExpirationBars
Количество свечей до отмены отложенной заявки.
4
GoodMinuteOfHour
Разрешённая минута часа (-1 отключает фильтр).
-1
BadMinutesMask
Битовая маска запрещённых минут (бит n = 1 — запретить минуту n).
0
GoodHourOfDay
Разрешённый час (-1 отключает фильтр).
-1
BadHoursMask
Битовая маска запрещённых часов.
0
GoodDayOfWeek
Разрешённый день недели (-1 отключает фильтр, 0 = воскресенье).
-1
BadDaysMask
Битовая маска запрещённых дней недели (0 = воскресенье).
0
Управление заявками
Вход — лимитные заявки рассчитываются от медленной EMA с учётом ATR; для покупок добавляется спрэд, если доступны лучшие котировки.
Просрочка — при создании заявки запоминается номер текущей свечи; когда число свечей превысит ExpirationBars, заявка снимается.
Защита — после исполнения входа старые стоп/тейк отменяются и выставляются новые уровни, вычисленные по ATR на момент сигнала. Когда позиция обнуляется, защитные заявки отменяются.
Временной фильтр
Единичные разрешения — параметры GoodMinuteOfHour, GoodHourOfDay, GoodDayOfWeek ограничивают торговлю конкретной минутой, часом или днём недели.
Битовые маски — параметры BadMinutesMask, BadHoursMask, BadDaysMask позволяют отключить набор минут, часов или дней (например, BadMinutesMask = (1 << 0) | (1 << 30) запрещает 0-ю и 30-ю минуты каждого часа).
Совместное применение — сигнал выполняется только если текущее время удовлетворяет разрешённым значениям и не попадает под маски.
Отличия от оригинального советника
Применяются высокоуровневые методы StockSharp (BuyLimit, SellLimit, SellStop, BuyStop), а не объекты MetaTrader Expert.
Компенсация спрэда для покупок берёт значения Security.BestBid/Security.BestAsk; при отсутствии котировок добавка равна нулю.
Логика временного фильтра реализована вручную через битовые маски вместо класса CSignalITF.
После исполнения входа защитные заявки выставляются немедленно, используя заранее сохранённые уровни стопа и тейка.
Рекомендации по использованию
Перед запуском стратегии задайте объём Volume, иначе будет зафиксировано предупреждение и заявки не отправятся.
Основные параметры отмечены как пригодные для оптимизации, что облегчает подбор значений.
Для фильтра времени используется момент закрытия свечи.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Two EMA Intraday Filter strategy: EMA crossover.
/// Buys when fast EMA crosses above slow EMA. Sells on cross below.
/// </summary>
public class TwoEmaIntradayFilterStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public TwoEmaIntradayFilterStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
decimal? prevFast = null;
decimal? prevSlow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevSlow.HasValue)
{
if (prevFast.Value <= prevSlow.Value && fastVal > slowVal && Position <= 0)
BuyMarket();
else if (prevFast.Value >= prevSlow.Value && fastVal < slowVal && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
}
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 two_ema_intraday_filter_strategy(Strategy):
def __init__(self):
super(two_ema_intraday_filter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 12) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(two_ema_intraday_filter_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(two_ema_intraday_filter_strategy, self).OnStarted2(time)
self._fast_ind = ExponentialMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = ExponentialMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_slow is not None:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return two_ema_intraday_filter_strategy()