Ilan iMA Strategy — порт MetaTrader 5 советника Ilan iMA.mq5 на StockSharp. В основе — сдвинутая скользящая средняя,
которая отфильтровывает направление рынка, и мартингейловая сетка усреднений. Реализация на StockSharp использует
высокоуровневый API: при подтверждении тренда по взвешенной скользящей среднй стратегии открывает рыночную позицию и добавляет
новые ордера каждый раз, когда цена проходит против позиции заданный шаг. Вся корзина закрывается при достижении профит-таргета,
срабатывании трейлинг-стопа или стоп-лосса, повторяя модель управления рисками оригинального советника.
Логика торговли
Подписаться на выбранный таймфрейм (CandleType) и кормить индикатор скользящей средней с настраиваемыми параметрами
(MaMethod, MaPeriod, PriceMode). Положительный MaShift сдвигает линию вперёд, поэтому стратегия анализирует значения
прошлых баров, имитируя MT5.
Дождаться закрытия свечи — сигналы и защита позиции обрабатываются только на завершённых барах.
Определить тренд, сравнив четыре последовательных значения скользящей средней со сдвигом MaShift:
строгий рост (ma0 > ma1 > ma2 > ma3) сигнализирует о восходящем тренде.
Если корзина пуста:
при нисходящем тренде и закрытии выше линии MA открыть шорт объёмом StartVolume;
при восходящем тренде и закрытии ниже линии MA открыть лонг объёмом StartVolume.
Если корзина существует:
когда цена проходит против позиции не менее GridStepPips, открыть дополнительный ордер с объёмом, умноженным на
LotExponent, но ограниченным LotMaximum и биржевыми лимитами;
стратегия хранит минимальную цену покупок, максимальную цену продаж и среднюю цену входа, чтобы повторить поведение MT5.
Условия закрытия:
если плавающая прибыль корзины с несколькими ордерами достигла ProfitMinimum (в валюте счёта), закрыть все сделки в этом
направлении;
закрыть корзину при достижении TakeProfitPips или при убытке в StopLossPips;
трейлинг включается после движения на TrailingStopPips + TrailingStepPips пунктов и смещается ступенями по TrailingStepPips.
Управление рисками и объёмом
StartVolume соответствует параметру MT5 StartLots. Каждый следующий ордер умножает предыдущий объём на LotExponent,
учитывая LotMaximum и ограничения площадки (Security.MinVolume, Security.VolumeStep, Security.MaxVolume).
ProfitMinimum сохраняет механику «снятия лока»: как только сетка отработала просадку и показала заданную прибыль, все сделки
соответствующего направления закрываются.
Стоп-лосс и тейк-профит задаются в пунктах (StopLossPips, TakeProfitPips). Конвертация в цену выполняется через
Security.PriceStep.
Блок трейлинга повторяет реализацию MT5: он активируется только после превышения порога TrailingStopPips + TrailingStepPips
и перемещается дискретно, чтобы избежать преждевременных подтяжек стопа.
Параметры
Название
Тип
Значение по умолчанию
Аналог в MT5
Описание
MaPeriod
int
15
Inp_MA_ma_period
Период фильтрующей скользящей средней.
MaShift
int
5
Inp_MA_ma_shift
Сдвиг линии скользящей средней вперёд.
MaMethod
MovingAverageMethod
Weighted
Inp_MA_ma_method
Метод усреднения (SMA, EMA, SMMA, LWMA).
PriceMode
CandlePrice
Weighted
Inp_MA_applied_price
Тип цены свечи для индикатора.
StartVolume
decimal
1
InpStartLots
Базовый объём первого ордера в корзине.
GridStepPips
decimal
30
InpStep
Шаг сетки усреднений в пунктах.
LotExponent
decimal
1.6
InpLotExponent
Множитель объёма для каждой следующей сделки.
LotMaximum
decimal
15
InpLotMaximum
Максимально допустимый объём одного ордера.
ProfitMinimum
decimal
15
InpProfitMinimum
Минимальная плавающая прибыль для закрытия корзины.
StopLossPips
decimal
0
InpStopLoss
Дистанция стоп-лосса в пунктах (0 отключает стоп).
TakeProfitPips
decimal
100
InpTakeProfit
Дистанция тейк-профита в пунктах.
TrailingStopPips
decimal
15
InpTrailingStop
Порог прибыли для активации трейлинга.
TrailingStepPips
decimal
5
InpTrailingStep
Минимальное дополнительное движение для смещения трейлинга.
CandleType
DataType
таймфрейм 15 минут
период графика
Таймфрейм расчёта сигналов.
Отличия от оригинала
StockSharp работает в неттинговой модели, поэтому существует только одна совокупная позиция. Стратегия ведёт внутренний список
входов и объёмов, чтобы воспроизвести MT5-подход к корзинам.
При округлении объёмов учитываются биржевые ограничения (MinVolume, VolumeStep, MaxVolume), тогда как в MT5 проверка
выполнялась вручную. Это исключает заявки, которые может отклонить коннектор.
Стоп-лосс, тейк-профит и трейлинг реализованы через рыночные выходы, а не через модификацию ордеров, как в MT5. Функционально
логика сохранена, но управление поручается StockSharp.
Рекомендации
Перед запуском убедитесь, что в инструменте заполнены параметры шага цены и объёма (PriceStep, StepPrice, MinVolume,
VolumeStep, MaxVolume). Иначе пересчёт пунктов и округление объёма могут работать некорректно.
Для инструментов с нетипичным шагом цены скорректируйте GridStepPips, StopLossPips, TrailingStopPips.
Мартингейловые сетки несут повышенный риск. Обязательно протестируйте стратегию на истории и учитывайте комиссии/проскальзывание.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class IlanImaStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public IlanImaStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ilan_ima_strategy(Strategy):
def __init__(self):
super(ilan_ima_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(ilan_ima_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(ilan_ima_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return ilan_ima_strategy()