Стратегия воспроизводит советник "iMA iSAR EA" из MetaTrader 5 с использованием высокоуровневого API StockSharp. Она сочетает три взвешенные скользящие средние и две кривые Parabolic SAR, чтобы отбирать импульсные прорывы. Длинная позиция открывается, когда быстрая скользящая средняя находится выше двух остальных, а обе линии SAR располагаются ниже цены закрытия. Зеркальное условие формирует короткий сигнал. Управление рисками выполняется в пунктах (pips): фиксированные стоп-лоссы, тейк-профиты и опциональный трейлинг-стоп.
Реализация использует единственный поток свечей, выбираемый параметром CandleType. Все индикаторы рассчитываются в этом таймфрейме. В оригинальном MQ5-файле индикаторы брались с разных периодов, поэтому в StockSharp добавлены отдельные сдвиги для каждой средней, что позволяет читать значения через указанное число закрытых баров.
Торговые правила
Индикаторы
Три взвешенные скользящие средние (быстрая, базовая и медленная) на выбранном свечном потоке. Параметры сдвига имитируют задержку буферов из MetaTrader.
Две линии Parabolic SAR (быстрая и базовая) используют тот же поток свечей, но имеют независимые шаги ускорения и максимальные значения.
Входы
Покупка: быстрая средняя выше базовой и медленной, а обе SAR ниже цены закрытия.
Продажа: быстрая средняя ниже базовой и медленной, а обе SAR выше цены закрытия.
При появлении обратного сигнала стратегия закрывает противоположную позицию и разворачивается одним рыночным ордером.
Риск-менеджмент
Стоп-лосс и тейк-профит заданы в пунктах (кратных шагу цены) и проверяются по завершённым свечам.
При включённом трейлинг-стопе уровень подтягивается за ценой с заданной дистанцией и шагом обновления.
Объём заявок автоматически приводится к ограничениям инструмента (VolumeStep, MinVolume, MaxVolume).
Параметры
Имя
Тип
Значение по умолчанию
Описание
Volume
decimal
0.1
Базовый объём заявки; при развороте добавляется объём для закрытия противоположной позиции.
StopLossPips
decimal
50
Дистанция стоп-лосса в пунктах. 0 — без стопа.
TakeProfitPips
decimal
50
Дистанция тейк-профита в пунктах. 0 — без цели.
UseTrailing
bool
true
Включает сопровождение трейлинг-стопом.
TrailingStopPips
decimal
25
Дистанция между ценой и трейлинг-стопом (в пунктах).
TrailingStepPips
decimal
5
Минимальное благоприятное движение (в пунктах) перед подтягиванием трейлинг-стопа.
CandleType
DataType
Свечи 15 минут
Свечной поток для всех расчётов.
FastMaPeriod
int
10
Период быстрой взвешенной средней.
FastMaShift
int
0
Сдвиг (количество закрытых баров) для быстрой средней.
NormalMaPeriod
int
30
Период базовой взвешенной средней.
NormalMaShift
int
3
Сдвиг для базовой средней.
SlowMaPeriod
int
60
Период медленной взвешенной средней.
SlowMaShift
int
6
Сдвиг для медленной средней.
FastSarStep
decimal
0.02
Шаг ускорения для быстрой SAR.
FastSarMax
decimal
0.2
Максимальное ускорение для быстрой SAR.
NormalSarStep
decimal
0.02
Шаг ускорения для базовой SAR.
NormalSarMax
decimal
0.2
Максимальное ускорение для базовой SAR.
Примечания
Проверка трейлинг-стопа выполняется на закрытии свечи; для внутридневного контроля можно подключить компонент, работающий с тиковыми данными.
При наличии PriceStep он используется как величина пункта, иначе предполагается стандартный шаг 0.0001 для валютных пар.
Как и в оригинале, сигналы формируются только на закрытых барах; отложенные заявки не используются — подаются рыночные ордера.
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 ImaIsarEaStrategy : 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 ImaIsarEaStrategy()
{
_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 ima_isar_ea_strategy(Strategy):
def __init__(self):
super(ima_isar_ea_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(ima_isar_ea_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(ima_isar_ea_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 ima_isar_ea_strategy()