Стратегия является прямым портом советника MetaTrader EA_MARSI_1-02. Торговля ведётся по пересечениям двух копий
пользовательского индикатора EMA_RSI_VA от Integer — это волатильно-адаптивная EMA, управляемая RSI. При каждом пересечении
медленной и быстрой линий стратегия немедленно разворачивает позицию, полностью воспроизводя логику исходного советника и при
этом соблюдая подходы StockSharp к управлению заявками.
Как устроен индикатор
В оригинальном пакете MQL присутствует индикатор EMA_RSI_VA. Он вычисляет EMA по цене, но её эффективный период меняется в
зависимости от удалённости RSI от нейтрального уровня. В порте StockSharp используется класс
EmaRsiVolatilityAdaptiveIndicator, реализующий исходную формулу:
Рассчитать RSI по выбранному типу цены (AppliedPrice) с периодом RSIPeriod.
Измерить расстояние RSI от 50 (|RSI - 50| + 1) — это прокси волатильности.
Получить адаптивный множитель
multi = (5 + 100 / RSIPeriod) / (0.06 + 0.92 * dist + 0.02 * dist^2).
Умножить базовый период EMA на данный множитель и получить динамический период pdsx.
Применить стандартную рекурсию EMA с коэффициентом сглаживания 2 / (pdsx + 1) и ценой из выбранного режима.
Чем дальше RSI уходит от 50, тем быстрее реагирует кривая; когда RSI «плоский», EMA удлиняется и сильнее фильтрует шум.
Обе линии поддерживают весь набор режимов StockSharp.Messages.AppliedPrice.
Правила торговли
Сигналы
Продажа/шорт: ранее медленная < быстрой и сейчас медленная ≥ быстрой.
Покупка/лонг: ранее медленная > быстрой и сейчас медленная ≤ быстрой.
Исполнение
Анализируются только завершённые свечи выбранного типа.
При появлении сигнала отправляется рыночная заявка таким объёмом, чтобы закрыть текущую позицию и открыть новую в нужном
направлении.
Соблюдаются ограничения биржи через Security.MinVolume, Security.VolumeStep и Security.MaxVolume.
Развороты
Объём рассчитывается так, чтобы одна команда SellMarket или BuyMarket перевела позицию через ноль — это соответствует
поведению MQL-советника, где встречный сигнал сразу переворачивает сделку.
Управление рисками
TakeProfitPoints и StopLossPoints повторяют поля TP/SL из советника (в пунктах). Если один из параметров больше нуля,
стратегия запускает встроенный менеджер защит StockSharp с абсолютными отступами и useMarketOrders = true, что имитирует
цикл модификации стопов/тейк-профитов через OrderSend.
UseBalanceMultiplier реализует переключатель use_Multpl. При его включении фактический объём становится равен
Volume * Equity / MaxDrawdown, после чего нормализуется под ограничения инструмента.
Дополнительно вызывается StartProtection(), чтобы внешние модули могли подключать трейлинг или безубыток.
Параметры
Параметр
Значение по умолчанию
Описание
Volume
0.1
Базовый объём рыночной заявки до применения мультипликатора баланса.
TakeProfitPoints
0
Отступ тейк-профита в пунктах инструмента; 0 отключает тейк.
StopLossPoints
0
Отступ стоп-лосса в пунктах инструмента; 0 отключает защитный стоп.
UseBalanceMultiplier
false
Включает пропорциональное балансом управление объёмом (аналог use_Multpl).
MaxDrawdown
10000
Делитель для мультипликатора баланса; соответствует Max_drawdown в советнике.
SlowRsiPeriod
310
Период RSI для медленной линии EMA_RSI_VA.
SlowEmaPeriod
40
Базовый период EMA для медленной линии до адаптации по RSI.
SlowAppliedPrice
Close
Тип цены, который поступает в медленный индикатор.
FastRsiPeriod
200
Период RSI для быстрой линии EMA_RSI_VA.
FastEmaPeriod
50
Базовый период EMA для быстрой линии.
FastAppliedPrice
Close
Тип цены для быстрой линии.
CandleType
TimeFrame(1m)
Свечная серия, используемая в расчётах.
Особенности реализации
Использован высокоуровневый API StockSharp (SubscribeCandles().Bind(...)), поэтому нет ручных циклов обработки буферов.
Обрабатываются только завершённые свечи, что соответствует вызовам CopyBuffer(..., 1, 2, ...) в исходном коде.
Нормализация объёма использует Security.MinVolume, Security.VolumeStep и Security.MaxVolume, что предотвращает
некорректные заявки на реальной бирже.
По запросу не создаётся Python-версия — директория содержит только реализацию на C# и документацию.
Полученная стратегия полностью повторяет поведение исходного советника, но предоставляет параметры и защитные механизмы,
привычные для экосистемы StockSharp (Designer, Runner и т.д.).
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA RSI VA Crossover: Fast/slow EMA crossover with RSI volatility filter.
/// </summary>
public class EmaRsiVaCrossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public EmaRsiVaCrossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 40)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow))
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow))
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 40 && rsiVal < 70)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal > 30 && rsiVal < 60)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class ema_rsi_va_cross_strategy(Strategy):
def __init__(self):
super(ema_rsi_va_cross_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 40) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
def OnStarted2(self, time):
super(ema_rsi_va_cross_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = 14
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or (fv < sv and self._prev_fast >= self._prev_slow):
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or (fv > sv and self._prev_fast <= self._prev_slow):
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 40 and rv < 70:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv > 30 and rv < 60:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(ema_rsi_va_cross_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ema_rsi_va_cross_strategy()