Expert610 Breakout — это порт на C# советника MetaTrader 4 Expert610.mq4. Исходный робот отслеживает широкую свечу и
выставляет симметричные отложенные заявки Buy Stop и Sell Stop вокруг максимумов и минимумов предыдущего бара. Размер позиции
рассчитывается как доля свободного капитала, который трейдер готов рискнуть, а параметры стоп-лосса и тейк-профита задаются в
пунктах. Реализация на StockSharp повторяет эту логику с использованием высокоуровневого API и предоставляет все настройки через
параметры стратегии.
Торговая логика
Сбор данных
Стратегия подписывается на выбранный тип свечей и сохраняет последнюю закрытую свечу.
Из подписки на стакан рассчитывается текущий спрэд. Если актуальных котировок нет, спрэд считается равным нулю — как и в
оригинальном советнике при отсутствии онлайн-спрэда.
Фильтр волатильности
Разница между максимумом предыдущей свечи и текущей ценой закрытия, а также между текущим закрытием и минимумом
предыдущей свечи должна быть не меньше ThresholdPips (переведённого в цену).
Открытие текущей свечи должно находиться ниже предыдущего максимума для лонга и выше предыдущего минимума для шорта. При
выполнении обеих условий стратегия размещает обе заявки.
Выставление ордеров
Buy Stop устанавливается по формуле предыдущий максимум + BreakoutOffset + спрэд, что соответствует MT4, где применяется
цена Ask.
Sell Stop размещается по формуле предыдущий минимум - BreakoutOffset — без учёта спрэда, как и в оригинальном коде.
Одновременно может существовать только одна пара отложенных ордеров. Если активный ордер ещё висит, новый сигнал
игнорируется.
Управление риском
Объём сделки = (свободный капитал Portfolio.CurrentValue - Portfolio.BlockedValue) × RiskPercent / 100, округлённый до
RoundingDigits и переведённый в лоты по формуле MT4 lot = risk / stopPips * 0.1. Формула предполагает, что 0.1 лота даёт
стоимость одного пункта, равную одной единице валюты счёта.
Объём дополнительно нормируется по VolumeStep, ограничивается MinVolume/MaxVolume инструмента и параметром
MinimumVolume, чтобы заказ всегда соответствовал биржевым требованиям.
StartProtection автоматически привязывает стоп и цель (в ценовых единицах) к каждой открытой позиции, моментально включая
заданные StopLossPips и TakeProfitPips.
Параметры
Имя
Описание
Значение по умолчанию
Примечания
RoundingDigits
Количество знаков после запятой при расчёте риска и объёма.
2
Значение ≥ 0.
RiskPercent
Процент свободного капитала на одну сделку.
1
При 0 стратегия использует MinimumVolume.
MinimumVolume
Минимально допустимый объём отложенного ордера.
0.1
Также учитываются MinVolume и VolumeStep инструмента.
ThresholdPips
Минимальная дистанция от цены закрытия до экстремумов предыдущей свечи.
5
Указывается в пунктах.
BreakoutOffsetPips
Дополнительное смещение выше/ниже экстремумов при постановке ордера.
2
Применяется для обеих сторон.
StopLossPips
Дистанция стоп-лосса.
5
Передаётся в StartProtection.
TakeProfitPips
Дистанция тейк-профита.
10
Можно установить 0, чтобы отключить.
CandleType
Тип свечей для расчёта уровней.
Таймфрейм 1 час
Поддерживаются любые DataType StockSharp.
Особенности реализации
Размер пункта вычисляется на основе PriceStep и Decimals инструмента (для 3- и 5-знаковых валютных пар используется множитель 10) —
так же, как в MQL4.
Нормализация объёма учитывает VolumeStep, ограничивает значения в пределах MinVolume/MaxVolume, а затем применяет порог
MinimumVolume, чтобы исключить невыполнимые заявки.
Компенсация спрэда строится на лучших котировках из стакана. Если данные недоступны, входные цены совпадают с MT4-версией без
учёта спрэда.
После смены состояния ордера на исполненный/отменённый/ошибочный он удаляется из внутреннего состояния, что позволяет сразу
подготовить новую пару ордеров на следующую подходящую свечу.
Отличия от версии MQL
Помимо округления по Digits2Round, реализация на StockSharp дополнительно подгоняет объём под шаг объёма биржи.
Вместо передачи стопов и тейков в момент создания ордера используется StartProtection, автоматически выставляющий защитные
заявки после фактического открытия позиции.
В качестве источника свободного капитала используются поля Portfolio.CurrentValue и Portfolio.BlockedValue. При отсутствии
данных стратегия возвращается к фиксированному минимальному объёму.
Все вычисления выполняются только по закрытым свечам, что исключает внутридневные перерисовки и соответствует логике вызова
start() в конце бара.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Expert610 Breakout: Previous candle high/low breakout with ATR stops.
/// </summary>
public class Expert610BreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _entryPrice;
public Expert610BreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0;
_prevLow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_prevHigh == 0 || _prevLow == 0 || atrVal <= 0)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
return;
}
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > _prevHigh && close > emaVal)
{
_entryPrice = close;
BuyMarket();
}
else if (close < _prevLow && close < emaVal)
{
_entryPrice = close;
SellMarket();
}
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class expert610_breakout_strategy(Strategy):
"""
Expert610 Breakout: Previous candle high/low breakout with EMA trend filter and ATR stops.
"""
def __init__(self):
super(expert610_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "Trend filter", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(expert610_breakout_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(expert610_breakout_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema_val)
atr_val = float(atr_val)
if self._prev_high == 0.0 or self._prev_low == 0.0 or atr_val <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position > 0:
if close >= self._entry_price + atr_val * 2.5 or close <= self._entry_price - atr_val * 1.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - atr_val * 2.5 or close >= self._entry_price + atr_val * 1.5:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close > self._prev_high and close > ema_val:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_low and close < ema_val:
self._entry_price = close
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return expert610_breakout_strategy()