Early Bird Range Breakout — порт торгового советника MetaTrader 4 earlyBird1 на C#. Стратегия измеряет экстремумы заданного «ночного» диапазона, применяет 14-периодный RSI по ценам открытия для определения допускаемого направления и входит по первому пробою диапазона после начала торговой сессии. Перенос сохранил дневные ограничения на количество сделок по каждому направлению, волатильностный трейлинг и принудительное закрытие в конце дня.
Логика стратегии
Формирование диапазона
Временное окно – максимум и минимум рассчитываются между Range Start Hour и Range End Hour (с учётом автоматической корректировки на летнее/зимнее время). Каждая свеча, пересекающая окно, обновляет границы диапазона.
Буфер входа – к максимуму добавляется, а от минимума вычитается заданное количество пунктов, что повторяет MT4-параметр ±2/Fakt.
Сброс каждый день – границы диапазона, уровни входа и счётчики сделок обнуляются с первой завершённой свечи нового торгового дня.
Фильтр направления
RSI по ценам открытия – индикатор питается значениями открытия свечи, полностью повторяя вызов iRSI(..., PRICE_OPEN) в исходном советнике.
Выбор стороны – если RSI > 50, активируется только длинный сценарий, если RSI ≤ 50 – только короткий. Это гарантирует, что за свечу генерируется максимум один торговый сигнал, как и в оригинале.
Правила входа
Торговая сессия – сделки открываются только в рабочие дни, когда текущее время находится между Session Start и Session End, и диапазон уже построен.
Одна попытка на направление – после открытия длинной (или короткой) позиции соответствующий триггер отключается до конца дня. Логика полностью повторяет проверку истории сделок в MQL.
Переключатель хеджа – параметр Allow Hedging позволяет при наличии обратной позиции сразу перевернуться, докупив/допродав нужный объём. При отключённом хедже новые заявки подаются только при нулевой позиции.
Правила выхода
Фиксированные стоп и цель – расстояния в пунктах. Итоговый take-profit ограничивается стопом и шириной диапазона, что соответствует формуле MathMin из MT4.
Трейлинг по волатильности – если текущая свеча шире среднего диапазона за 16 свечей, умноженного на Trailing Risk, и цена прошла не меньше Trailing Trigger, стоп подтягивается на расстояние первоначального стопа, а take-profit сдвигается на половину трейлингового шага.
Закрытие дня – в час, заданный параметром Closing Hour, прибыльные сделки закрываются, убыточные переводятся в безубыток (take-profit на цену входа), что повторяет поведение оригинального советника.
Параметры
Auto Trading – глобальный переключатель автоторговли.
Allow Hedging – разрешает переворот позиции при открытой противоположной сделке.
Trade Direction – доступное направление: 0 – обе стороны, 1 – только лонг, 2 – только шорт.
Volume – объём рыночных заявок.
Take Profit (pips) – максимальная дистанция цели (ограничивается стопом и диапазоном).
Stop Loss (pips) – фиксированная дистанция защитного стопа.
Trailing Trigger (pips) – минимальный профит для активации трейлинга.
Trailing Risk – коэффициент, умножаемый на средний диапазон 16 свечей при оценке волатильности.
Entry Buffer (pips) – буфер в пунктах, добавляемый к границам диапазона.
Session Start Hour / Minute – начало рабочей сессии (время графика до учёта сезонного сдвига).
Session End Hour – окончание окна для новых сделок.
Closing Hour – время принудительного завершения позиций.
Range Start Hour / Range End Hour – границы расчёта утреннего диапазона.
Summer Time Start / Winter Time Start – дни года, в которые используется двухчасовой и одночасовой сдвиг (аналог полей Sommerzeit/Winterzeit).
RSI Length – период RSI (по умолчанию 14).
Candle Type – основной таймфрейм для подписки на свечи (по умолчанию 15 минут).
Дополнительные сведения
Размер пункта вычисляется по текущей цене (≥ 10 → 0.01, иначе 0.0001), точно как в расчёте Fakt у MT4.
Для оценки волатильности используется очередь из 16 завершённых свечей без учёта текущей.
StockSharp ведёт нетто-позицию, поэтому «хеджирование» реализуется через отправку объёма, который закрывает текущую позицию и тут же открывает противоположную.
В репозитории присутствует только C# версия; Python-реализация не создавалась.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Early Bird Range Breakout: N-bar high/low breakout with EMA filter and ATR stops.
/// </summary>
public class EarlyBirdRangeBreakoutStrategy : 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 EarlyBirdRangeBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.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 || atrVal <= 0) { _prevHigh = candle.HighPrice; _prevLow = candle.LowPrice; return; }
if (Position > 0)
{
if (close <= _entryPrice - atrVal * 1.5m || close >= _entryPrice + atrVal * 2.5m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close >= _entryPrice + atrVal * 1.5m || close <= _entryPrice - atrVal * 2.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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class early_bird_range_breakout_strategy(Strategy):
def __init__(self):
super(early_bird_range_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.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 CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(early_bird_range_breakout_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_high == 0 or av <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position > 0:
if close <= self._entry_price - av * 1.5 or close >= self._entry_price + av * 2.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close >= self._entry_price + av * 1.5 or close <= self._entry_price - av * 2.5:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position == 0:
if close > self._prev_high and close > ev:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_low and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def OnReseted(self):
super(early_bird_range_breakout_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
def CreateClone(self):
return early_bird_range_breakout_strategy()