Стратегия EMA (редакция barabashkakvn)
Конверсия экспертного советника MetaTrader 5 "EMA (barabashkakvn's edition)". Система торгует пересечение двух экспоненциальных средних, рассчитанных по медианной цене, и использует виртуальные уровни take-profit/stop-loss в пунктах. Сделка открывается только после подтверждённого пересечения и небольшого отката к экстремуму предыдущей свечи.
Суть подхода
- Отслеживаются EMA с периодами 5 и 10 (по медианной цене) на выбранном таймфрейме.
- При пересечении быстрой и медленной EMA формируется флаг сигнала вместо немедленного входа.
- Цена должна откатить на
MoveBackPips пунктов от экстремума предыдущей свечи, при этом разница между EMA превышает 2 * pipSize.
- После выполнения условий открывается позиция в сторону пересечения.
- Управление позицией осуществляется виртуальными целями и стопами, измеряемыми в пунктах от цены входа.
Такое поведение полностью повторяет оригинал на MQL: советник ждал установки флага check, а затем требовал достаточного спреда EMA и отката цены относительно предыдущей свечи. Закрытие также идёт по "виртуальным" уровням, проверяя, достигли ли Bid/Ask заданных расстояний.
Индикаторы и данные
- EMA(5) по медианной цене
(High + Low) / 2.
- EMA(10) по медианной цене.
- Высота/минимум предыдущей завершённой свечи для расчёта отката.
- Обработка ведётся по завершённым свечам источника
CandleType.
Параметры
| Параметр |
Значение по умолчанию |
Описание |
OrderVolume |
0.1 |
Объём сделки в лотах/контрактах. |
VirtualProfitPips |
5 |
Расстояние (в пунктах) от входа до виртуального take-profit. |
MoveBackPips |
3 |
Откат после пересечения, измеряется от экстремума предыдущей свечи. |
StopLossPips |
20 |
Расстояние (в пунктах) от входа до виртуального stop-loss. |
PipSize |
0.0001 |
Размер пункта в ценовых единицах. Нужно менять для инструментов с другой котировкой. |
FastLength |
5 |
Период быстрой EMA. |
SlowLength |
10 |
Период медленной EMA. |
CandleType |
TimeFrame(1m) |
Тип свечей, используемых в расчётах. |
Пипсовые параметры переводятся в ценовые расстояния через pipValue = PipSize. Если указать ноль или отрицательное значение, стратегия использует Security.PriceStep, если биржа предоставляет шаг цены.
Логика торговли
Вход
- Формирование сигнала: при любом пересечении EMA сохраняется флаг, но сделка ещё не открывается.
- Шорт возможен при выполнении условий:
- Флаг сигнала установлен.
SlowEMA - FastEMA > 2 * pipSize.
- Максимум текущей свечи ≥ минимум предыдущей свечи +
MoveBackPips * pipSize (цена откатила вверх от прежнего минимума).
- Лонг возможен при выполнении условий:
- Флаг сигнала установлен.
FastEMA - SlowEMA > 2 * pipSize.
- Минимум текущей свечи ≤ максимум предыдущей свечи -
MoveBackPips * pipSize (цена откатила вниз от прежнего максимума).
После открытия позиции флаг сбрасывается, чтобы исключить повторные входы.
Выход
Виртуальные уровни повторяют механику MQL и сравнивают экстремумы свечи с нужными дистанциями:
- Длинная позиция:
- Закрыть, если максимум свечи ≥ цена входа +
VirtualProfitPips * pipSize.
- Закрыть, если минимум свечи ≤ цена входа -
StopLossPips * pipSize.
- Короткая позиция:
- Закрыть, если минимум свечи ≤ цена входа -
VirtualProfitPips * pipSize.
- Закрыть, если максимум свечи ≥ цена входа +
StopLossPips * pipSize.
После выхода виртуальные уровни сбрасываются, стратегия ждёт нового пересечения.
Особенности реализации
- Используется высокоуровневое подписывание на свечи (
SubscribeCandles) и отрисовка EMA и сделок на необязательном графике.
- Медианная цена вычисляется из High/Low, что полностью соответствует
PRICE_MEDIAN в MetaTrader.
- Флаг
_hasCrossSignal воспроизводит переменную check, гарантируя торговлю только после пересечения и отката.
StartProtection() вызывается в OnStarted, что активирует встроенный мониторинг рисков даже при ручном закрытии сделок.
- Все комментарии в коде написаны на английском, исходные буферы индикаторов напрямую не используются.
Советы по применению
- Обязательно адаптируйте
PipSize для инструментов с иной точкой котировки (JPY-пары, индексы, криптовалюты и т.д.).
- Поскольку выходы ориентируются на экстремумы свечи, короткие таймфреймы (1–5 минут) лучше воспроизводят поведение оригинального тикового советника.
- Параметры легко оптимизируются: периоды EMA, величины виртуальных уровней и отката.
- Стратегия ведёт только одну позицию; внешние сделки по тому же инструменту могут нарушить расчёт виртуальных уровней.
Риски
- Работа по закрытию свечей может не зафиксировать внутридневные касания виртуальных уровней; для точности используйте более мелкие интервалы.
- Виртуальные стопы не выставляют реальные защитные заявки, поэтому при сбоях связи или проскальзывании убыток может превысить ожидаемый.
- Как и любая стратегия на пересечениях EMA, система чувствительна к боковым участкам рынка; при необходимости добавляйте фильтры.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _virtualProfitPips;
private readonly StrategyParam<int> _moveBackPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<decimal> _pipSize;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private bool _hasCrossSignal;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _entryPrice;
private decimal? _virtualTarget;
private decimal? _virtualStop;
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Virtual take profit distance in pips.
/// </summary>
public int VirtualProfitPips
{
get => _virtualProfitPips.Value;
set => _virtualProfitPips.Value = value;
}
/// <summary>
/// Retracement distance after a crossover in pips.
/// </summary>
public int MoveBackPips
{
get => _moveBackPips.Value;
set => _moveBackPips.Value = value;
}
/// <summary>
/// Virtual stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Pip size in price units.
/// </summary>
public decimal PipSize
{
get => _pipSize.Value;
set => _pipSize.Value = value;
}
/// <summary>
/// Fast EMA length applied to median price.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length applied to median price.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
/// </summary>
public EmaBarabashkakvnEditionStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "Trading")
.SetOptimize(0.05m, 1m, 0.05m);
_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
.SetGreaterThanZero()
.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
.SetOptimize(2, 20, 1);
_moveBackPips = Param(nameof(MoveBackPips), 3)
.SetGreaterThanZero()
.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
.SetOptimize(1, 10, 1);
_stopLossPips = Param(nameof(StopLossPips), 20)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
.SetOptimize(10, 60, 2);
_pipSize = Param(nameof(PipSize), 0.0001m)
.SetGreaterThanZero()
.SetDisplay("Pip Size", "Instrument pip size in price units", "General");
_fastLength = Param(nameof(FastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
.SetOptimize(3, 15, 1);
_slowLength = Param(nameof(SlowLength), 10)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
.SetOptimize(8, 40, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = default;
_slowEma = default;
_hasCrossSignal = false;
_prevFast = default;
_prevSlow = default;
_prevHigh = default;
_prevLow = default;
_entryPrice = default;
_virtualTarget = default;
_virtualStop = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new EMA { Length = FastLength };
_slowEma = new EMA { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate median price as in the original expert (PRICE_MEDIAN).
var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
// Update EMA values using the median price.
var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });
if (!_fastEma.IsFormed || !_slowEma.IsFormed)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevFast = fastValue.ToDecimal();
_prevSlow = slowValue.ToDecimal();
return;
}
var fast = fastValue.ToDecimal();
var slow = slowValue.ToDecimal();
if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
{
var bullishCross = prevFast <= prevSlow && fast > slow;
var bearishCross = prevFast >= prevSlow && fast < slow;
if (bullishCross || bearishCross)
_hasCrossSignal = true;
}
_prevFast = fast;
_prevSlow = slow;
var pipValue = PipSize;
if (pipValue <= 0m)
pipValue = Security?.PriceStep ?? 0.0001m;
var moveBackPrice = MoveBackPips * pipValue;
var profitDistance = VirtualProfitPips * pipValue;
var stopDistance = StopLossPips * pipValue;
if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
{
var bearishSpread = slow - fast;
var bullishSpread = fast - slow;
var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;
if (bearishReady)
{
// Enter short after bearish cross and retracement above the previous low.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice - profitDistance;
_virtualStop = _entryPrice + stopDistance;
SellMarket();
_hasCrossSignal = false;
}
else if (bullishReady)
{
// Enter long after bullish cross and retracement below the previous high.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice + profitDistance;
_virtualStop = _entryPrice - stopDistance;
BuyMarket();
_hasCrossSignal = false;
}
}
else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
{
if (Position > 0)
{
// Long position: use high for profit target and low for stop.
var hitTarget = candle.HighPrice >= target;
var hitStop = candle.LowPrice <= stop;
if (hitTarget || hitStop)
{
SellMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
else if (Position < 0)
{
// Short position: use low for profit target and high for stop.
var hitTarget = candle.LowPrice <= target;
var hitStop = candle.HighPrice >= stop;
if (hitTarget || hitStop)
{
BuyMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
}
if (Position == 0)
{
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class ema_barabashkakvn_edition_strategy(Strategy):
def __init__(self):
super(ema_barabashkakvn_edition_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1)
self._virtual_profit_pips = self.Param("VirtualProfitPips", 5)
self._move_back_pips = self.Param("MoveBackPips", 3)
self._stop_loss_pips = self.Param("StopLossPips", 20)
self._pip_size = self.Param("PipSize", 0.0001)
self._fast_length = self.Param("FastLength", 5)
self._slow_length = self.Param("SlowLength", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def VirtualProfitPips(self):
return self._virtual_profit_pips.Value
@VirtualProfitPips.setter
def VirtualProfitPips(self, value):
self._virtual_profit_pips.Value = value
@property
def MoveBackPips(self):
return self._move_back_pips.Value
@MoveBackPips.setter
def MoveBackPips(self, value):
self._move_back_pips.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def PipSize(self):
return self._pip_size.Value
@PipSize.setter
def PipSize(self, value):
self._pip_size.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(ema_barabashkakvn_edition_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
median_price = (high + low) / 2.0
fast_result = process_float(self._fast_ema, median_price, candle.OpenTime, True)
slow_result = process_float(self._slow_ema, median_price, candle.OpenTime, True)
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed:
self._prev_high = high
self._prev_low = low
self._prev_fast = float(fast_result)
self._prev_slow = float(slow_result)
return
fast = float(fast_result)
slow = float(slow_result)
if self._prev_fast is not None and self._prev_slow is not None:
bullish_cross = self._prev_fast <= self._prev_slow and fast > slow
bearish_cross = self._prev_fast >= self._prev_slow and fast < slow
if bullish_cross or bearish_cross:
self._has_cross_signal = True
self._prev_fast = fast
self._prev_slow = slow
pip_value = float(self.PipSize)
if pip_value <= 0.0:
pip_value = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
move_back_price = int(self.MoveBackPips) * pip_value
profit_distance = int(self.VirtualProfitPips) * pip_value
stop_distance = int(self.StopLossPips) * pip_value
if self.Position == 0 and self._has_cross_signal and self._prev_high is not None and self._prev_low is not None:
bearish_spread = slow - fast
bullish_spread = fast - slow
bearish_ready = bearish_spread > 2.0 * pip_value and high >= self._prev_low + move_back_price
bullish_ready = bullish_spread > 2.0 * pip_value and low <= self._prev_high - move_back_price
if bearish_ready:
self._entry_price = close
self._virtual_target = self._entry_price - profit_distance
self._virtual_stop = self._entry_price + stop_distance
self.SellMarket()
self._has_cross_signal = False
elif bullish_ready:
self._entry_price = close
self._virtual_target = self._entry_price + profit_distance
self._virtual_stop = self._entry_price - stop_distance
self.BuyMarket()
self._has_cross_signal = False
elif self.Position != 0 and self._entry_price is not None and self._virtual_target is not None and self._virtual_stop is not None:
if self.Position > 0:
hit_target = high >= self._virtual_target
hit_stop = low <= self._virtual_stop
if hit_target or hit_stop:
self.SellMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
elif self.Position < 0:
hit_target = low <= self._virtual_target
hit_stop = high >= self._virtual_stop
if hit_target or hit_stop:
self.BuyMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
if self.Position == 0:
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
self._prev_high = high
self._prev_low = low
def OnReseted(self):
super(ema_barabashkakvn_edition_strategy, self).OnReseted()
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
def CreateClone(self):
return ema_barabashkakvn_edition_strategy()