Early Bird Range Break — порт MetaTrader-советника "earlyBird3" на C#. Стратегия отслеживает утренний боковой диапазон, формирующийся перед европейской сессией, и торгует его пробой. Для подтверждения используется RSI с периодом 14. При появлении сигнала выставляются три рыночных ордера, каждый со своим тейк-профитом, общим стоп-лоссом и опциональным трейлингом, который активируется только при росте волатильности.
Требования к данным
Поток свечей одного таймфрейма (по умолчанию 5 минут) по торгуемому инструменту.
У инструмента должен быть задан PriceStep, поскольку все дистанции указаны в пунктах.
Временные фильтры используют метки времени свечей (то есть серверное время поставщика данных).
Торговая сессия
Построение диапазона — между RangeStartHour и RangeEndHour фиксируются максимумы и минимумы.
Окно торговли — после TradingStartHour:TradingStartMinute и до TradingEndHour работает логика пробоя.
Принудительное закрытие — в ClosingHour стратегия закрывает все позиции независимо от результата.
Рабочие дни — сигналы учитываются только с понедельника по пятницу.
Логика входа
Уровень покупки = максимум диапазона + EntryBufferPoints; уровень продажи = минимум диапазона - EntryBufferPoints (в пунктах).
Для длинной позиции RSI должен быть > 50, для короткой — ≤ 50.
В день допускается по одному пробою в каждую сторону. При срабатывании выставляются три рыночных ордера (по умолчанию по 0.1 лота).
Если открыта позиция в противоположную сторону и HedgeTrading = false, сигнал игнорируется. При HedgeTrading = true стратегия сначала закрывает текущую позицию и затем открывает новую — это имитирует хеджирование оригинального советника в условиях неттингового учёта StockSharp.
Управление позицией
Стоп-лосс — общий уровень StopLossPoints, пересчитанный в цену. При его достижении оставшийся объём закрывается.
Лестница тейк-профитов — значения TakeProfit1Points, TakeProfit2Points, TakeProfit3Points закрывают по одной части позиции. Последняя часть остаётся в рынке до срабатывания стоп-лосса/трейлинга или конца сессии.
Трейлинг-стоп — активируется только когда осталась одна часть позиции. Текущий диапазон свечи должен превышать ATR * TrailingRiskMultiplier, а цена должна пройти не меньше TrailingStopPoints. После этого стоп подтягивается, сохраняя исходное расстояние.
Закрытие дня — по наступлении ClosingHour стратегия полностью выходит из рынка.
Параметры
Параметр
Описание
Значение по умолчанию
AutoTrading
Включение/выключение торговли.
true
HedgeTrading
Разрешить разворот при противоположном сигнале (реализовано через закрытие и открытие в обратную сторону).
true
OrderType
0 — обе стороны, 1 — только long, 2 — только short.
0
TradeVolume
Объём каждого рыночного ордера.
0.1
StopLossPoints
Дистанция стоп-лосса в пунктах.
60
TakeProfit1Points
Тейк-профит для первой части позиции.
10
TakeProfit2Points
Тейк-профит для второй части.
20
TakeProfit3Points
Тейк-профит для третьей части.
30
TrailingStopPoints
Минимальный прогресс цены для включения трейлинга.
15
TrailingRiskMultiplier
Множитель ATR при проверке роста волатильности.
1.0
EntryBufferPoints
Дополнительный буфер к уровням пробоя.
2
RangeStartHour
Начало формирования диапазона (час).
3
RangeEndHour
Окончание формирования диапазона (час).
7
TradingStartHour
Час начала торговли пробоев.
7
TradingStartMinute
Минута начала торговли пробоев.
15
TradingEndHour
Час остановки новых входов.
15
ClosingHour
Час принудительного закрытия позиций.
17
RsiPeriod
Период RSI.
14
VolatilityPeriod
Период ATR.
16
CandleType
Используемый тип свечей (по умолчанию 5 минут).
TimeSpan.FromMinutes(5)
Особенности реализации
Свечи подписываются через высокоуровневый API StockSharp, RSI и ATR подключаются через Bind.
Индикаторы обрабатываются прямо в ProcessCandle без вызовов GetValue() и без дополнительных буферов.
Используются только завершённые свечи.
Перевод пунктов в цену выполняется через Security.PriceStep, поэтому необходимо корректно настроить шаг цены инструмента.
В оригинальном советнике каждое направление велось отдельными ордерами. В StockSharp применяется неттинговый учёт, поэтому при HedgeTrading = true выполняется последовательное закрытие и открытие позиции в обратную сторону.
Рекомендации по использованию
Настройте RangeStartHour, RangeEndHour и торговое окно в соответствии с тайм-зоной вашего поставщика данных, чтобы диапазон соответствовал европейскому утру.
При оптимизации уделяйте внимание буферу пробоя, ступеням тейк-профита и фильтру волатильности — именно они определяют баланс между ложными пробоями и упущенными движениями.
Чтобы сделать трейлинг более агрессивным, уменьшите TrailingRiskMultiplier или StopLossPoints, тогда стоп будет пододвигаться чаще.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Early Bird RangeBreak: EMA trend following with ATR stops.
/// </summary>
public class EarlyBirdRangeBreakStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevClose;
private decimal _entryPrice;
public EarlyBirdRangeBreakStrategy()
{
_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();
_prevClose = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 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 (_prevClose == 0 || atrVal <= 0) { _prevClose = close; return; }
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || close < emaVal) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || close > emaVal) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (close > emaVal && _prevClose <= emaVal) { _entryPrice = close; BuyMarket(); }
else if (close < emaVal && _prevClose >= emaVal) { _entryPrice = close; SellMarket(); }
}
_prevClose = close;
}
}
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_break_strategy(Strategy):
def __init__(self):
super(early_bird_range_break_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_close = 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_break_strategy, self).OnStarted2(time)
self._prev_close = 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_close == 0 or av <= 0:
self._prev_close = close
return
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or close < ev:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or close > ev:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > ev and self._prev_close <= ev:
self._entry_price = close
self.BuyMarket()
elif close < ev and self._prev_close >= ev:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(early_bird_range_break_strategy, self).OnReseted()
self._prev_close = 0.0
self._entry_price = 0.0
def CreateClone(self):
return early_bird_range_break_strategy()