MasterExitPlanStrategy переносит в StockSharp логику метатрейдеровского советника «Master Exit Plan». Стратегия не инициирует сделки – она управляет уже открытыми позициями: отслеживает несколько типов стопов, подтягивает отложенные заявки и закрывает все позиции при достижении заданной цели по капиталу.
Для воспроизведения вызовов iOpen(symbol, PERIOD_M1, 1) используется подписка на минутные свечи. Проверки выполняются раз в секунду через таймер стратегии, что соответствует EventSetTimer(1) в MQL4.
Возможности
Целевая прибыль по капиталу – закрывает все позиции при достижении заданного процента роста капитала.
Статические и динамические стопы – контролирует расстояние от цены входа и уровни, привязанные к открытию последней минутной свечи.
Скрытые стопы – закрывает позицию рыночными ордерами без выставления биржевых заявок.
Блок трейлинг-стопа – активируется после фиксации минимальной прибыли и учитывает актуальный спред.
Трейлинг отложенных заявок – переставляет buy stop и sell stop ближе к рынку.
Параметры
Имя
Описание
Значение по умолчанию
EnableTargetEquity
Включить закрытие по целевой доходности.
false
TargetEquityPercent
Процент роста капитала для срабатывания.
1
EnableStopLoss
Активировать «жёсткий» стоп-лосс.
false
StopLossPoints
Расстояние жёсткого стопа в пунктах.
2000
EnableDynamicStopLoss
Привязать стоп к последней минутной свече.
false
DynamicStopLossPoints
Дистанция динамического стопа (пункты).
2000
EnableHiddenStopLoss
Включить скрытый статический стоп.
false
HiddenStopLossPoints
Дистанция скрытого статического стопа.
800
EnableHiddenDynamicStopLoss
Включить скрытый динамический стоп.
false
HiddenDynamicStopLossPoints
Дистанция скрытого динамического стопа.
800
EnableTrailingStop
Активировать блок трейлинг-стопа.
false
TrailingStopPoints
Дистанция трейлинга (пункты).
5
TrailingTargetPercent
Минимальный процент прибыли для активации.
0.2
SureProfitPoints
Дополнительный запас пунктов перед включением трейлинга.
30
EnableTrailPendingOrders
Переставлять активные stop-заявки.
false
TrailPendingOrderPoints
Отступ для трейлинга отложенных ордеров.
10
Рекомендации по использованию
Подключите стратегию к инструменту, где позиции открываются внешними модулями или вручную. Поле Volume должно соответствовать объёму, который следует закрывать при срабатывании защитных правил.
Портфель должен предоставлять Portfolio.CurrentValue. Это значение заменяет AccountBalance и AccountEquity из MetaTrader. Если оценка капитала недоступна, блок закрытия по цели не работает.
Для расчёта спреда стратегия использует лучшие котировки, поэтому необходимы данные уровня Level1.
Все защитные действия выполняются рыночными заявками. Биржевые стоп-приказы не выставляются, что повторяет «скрытый» характер оригинального советника.
Отличия от версии MQL
Вместо OrderModify стопы реализованы через постоянный контроль и принудительное закрытие позиции по рынку.
Динамические уровни рассчитываются по последней завершённой минутной свече из подписки SubscribeCandles.
Трейлинг отложенных ордеров не затрагивает внешние защитные заявки – стратегия их не создаёт.
Для оценки капитала используется Portfolio.CurrentValue (резерв – Portfolio.BeginValue).
Тестирование
Автоматические тесты не поставляются. Перед реальной торговлей прогоните стратегию в тестере StockSharp на исторических данных выбранного инструмента.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Master Exit Plan strategy: EMA trend following with ATR-based trailing stop exit.
/// Enters on EMA crossover, exits when price retraces by ATR multiple.
/// </summary>
public class MasterExitPlanStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private decimal _entryPrice;
private decimal _trailStop;
private bool _wasBullish;
private bool _hasTrendState;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public MasterExitPlanStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for trailing stop", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
.SetDisplay("ATR Multiplier", "ATR multiplier for trailing distance", "Risk");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_trailStop = 0;
_wasBullish = false;
_hasTrendState = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_trailStop = 0;
_wasBullish = false;
_hasTrendState = false;
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var range = candle.HighPrice - candle.LowPrice;
if (range <= 0) return;
var trailDist = range * AtrMultiplier;
var isBullish = fastValue > slowValue;
var crossedUp = _hasTrendState && !_wasBullish && isBullish;
var crossedDown = _hasTrendState && _wasBullish && !isBullish;
if (Position > 0)
{
var newStop = close - trailDist;
if (newStop > _trailStop)
_trailStop = newStop;
if (close < _trailStop)
{
SellMarket();
_trailStop = 0;
_entryPrice = 0;
_wasBullish = isBullish;
_hasTrendState = true;
return;
}
else if (crossedDown)
{
SellMarket();
_trailStop = 0;
_entryPrice = 0;
_wasBullish = isBullish;
_hasTrendState = true;
return;
}
}
else if (Position < 0)
{
var newStop = close + trailDist;
if (_trailStop == 0 || newStop < _trailStop)
_trailStop = newStop;
if (close > _trailStop)
{
BuyMarket();
_trailStop = 0;
_entryPrice = 0;
_wasBullish = isBullish;
_hasTrendState = true;
return;
}
else if (crossedUp)
{
BuyMarket();
_trailStop = 0;
_entryPrice = 0;
_wasBullish = isBullish;
_hasTrendState = true;
return;
}
}
if (Position == 0)
{
if (crossedUp)
{
BuyMarket();
_entryPrice = close;
_trailStop = close - trailDist;
}
else if (crossedDown)
{
SellMarket();
_entryPrice = close;
_trailStop = close + trailDist;
}
}
_wasBullish = isBullish;
_hasTrendState = true;
}
}