Открыть на GitHub

Стратегия Ilan iMA

Обзор

Ilan iMA Strategy — порт MetaTrader 5 советника Ilan iMA.mq5 на StockSharp. В основе — сдвинутая скользящая средняя, которая отфильтровывает направление рынка, и мартингейловая сетка усреднений. Реализация на StockSharp использует высокоуровневый API: при подтверждении тренда по взвешенной скользящей среднй стратегии открывает рыночную позицию и добавляет новые ордера каждый раз, когда цена проходит против позиции заданный шаг. Вся корзина закрывается при достижении профит-таргета, срабатывании трейлинг-стопа или стоп-лосса, повторяя модель управления рисками оригинального советника.

Логика торговли

  1. Подписаться на выбранный таймфрейм (CandleType) и кормить индикатор скользящей средней с настраиваемыми параметрами (MaMethod, MaPeriod, PriceMode). Положительный MaShift сдвигает линию вперёд, поэтому стратегия анализирует значения прошлых баров, имитируя MT5.
  2. Дождаться закрытия свечи — сигналы и защита позиции обрабатываются только на завершённых барах.
  3. Определить тренд, сравнив четыре последовательных значения скользящей средней со сдвигом MaShift:
    • строгий спад (ma0 < ma1 < ma2 < ma3) означает нисходящее движение;
    • строгий рост (ma0 > ma1 > ma2 > ma3) сигнализирует о восходящем тренде.
  4. Если корзина пуста:
    • при нисходящем тренде и закрытии выше линии MA открыть шорт объёмом StartVolume;
    • при восходящем тренде и закрытии ниже линии MA открыть лонг объёмом StartVolume.
  5. Если корзина существует:
    • когда цена проходит против позиции не менее GridStepPips, открыть дополнительный ордер с объёмом, умноженным на LotExponent, но ограниченным LotMaximum и биржевыми лимитами;
    • стратегия хранит минимальную цену покупок, максимальную цену продаж и среднюю цену входа, чтобы повторить поведение MT5.
  6. Условия закрытия:
    • если плавающая прибыль корзины с несколькими ордерами достигла 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;
	}
}