Стратегия Range Follower переносит эксперт MetaTrader 5 «Range Follower» на высокоуровневый API StockSharp. Система отслеживает дневной ценовой диапазон относительно эталона в виде средTrue Range (ATR) и открывает единственную сделку на пробой, когда цена уходит достаточно далеко от дневного минимума или максимума. ATR разделяется на две части: триггерную (условие входа) и остаточную (цель по прибыли), что полностью повторяет оригинальную логику советника.
Логика торговли
Дневной базовый диапазон
На дневных свечах рассчитывается ATR с периодом 20, который задаёт целевой диапазон текущей сессии.
Параметр TriggerPercent делит ATR на две части: расстояние для входа и остаток, используемый как take-profit.
Мониторинг диапазона
Из дневной подписки постоянно обновляются текущие максимум и минимум дня.
Данные Level1 предоставляют лучшие цены Bid и Ask, по которым измеряется удалённость текущих котировок от экстремумов.
Одна сделка в день
Если лучшая Bid находится выше дневного минимума больше, чем на триггерное расстояние, и сделок в текущем дне ещё не было, выполняется покупка по рынку.
Если лучшая Ask находится ниже дневного максимума больше, чем на триггерное расстояние, и сделок ещё не было, выполняется продажа по рынку.
После первой сделки флаг блокирует повторные входы до начала следующей сессии.
Стоп-лосс и тейк-профит
Для длинной позиции стоп выставляется на одну триггерную дистанцию ниже входа, а тейк-профит — на остаточную дистанцию выше.
Для короткой позиции стоп — на одну триггерную дистанцию выше входа, тейк-профит — на остаточную дистанцию ниже.
Контроль уровней выполняется и по тикам Level1, и по закрытым свечам, что позволяет закрывать сделку сразу при достижении любого уровня.
Сброс состояния по окончании дня
На первой свече нового дня стратегия закрывает открытую позицию, очищает состояние и запрашивает новый ATR.
Если на момент инициализации дневной диапазон уже превышает триггерное расстояние, торговля в этот день пропускается — аналогично поведению оригинального советника.
Параметры
Название
Значение по умолчанию
Описание
CandleType
Свечи 15 минут
Рабочий таймфрейм, по которому определяется граница торгового дня.
TriggerPercent
60
Процент ATR, используемый как триггерная дистанция (10–90).
Volume
0.1
Объём рыночных заявок для входа в длинные и короткие позиции.
Управление риском
Стоп и цель рассчитываются от одного и того же ATR, поэтому соотношение риск/прибыль всегда равно (100 - TriggerPercent) : TriggerPercent.
Одновременно может существовать только одна позиция; при достижении стопа или цели она закрывается немедленно, что исключает наложение сделок.
ATR вычисляется на отдельной подписке дневных свечей и индикаторе AverageTrueRange, подключённом через метод Bind.
Level1 необходим для точного воспроизведения тиковой логики входа/выхода оригинального советника — лучшие Bid/Ask служат основой для принятия решений.
Границы торгового дня определяются рабочим таймфреймом, поэтому стратегия корректно перезапускается при любой календарной конфигурации.
В конверсии не используются собственные буферы индикаторов или циклы по истории; состояние хранится в полях класса в соответствии с проектными требованиями.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Range Follower strategy: ATR-based range breakout.
/// Tracks high/low range and enters when price breaks out by ATR threshold.
/// </summary>
public class RangeFollowerStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _rangePeriod;
private decimal _rangeHigh;
private decimal _rangeLow;
private int _barCount;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public int RangePeriod { get => _rangePeriod.Value; set => _rangePeriod.Value = value; }
public RangeFollowerStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Indicators");
_rangePeriod = Param(nameof(RangePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Range Period", "Bars for range calculation", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rangeHigh = 0m;
_rangeLow = decimal.MaxValue;
_barCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rangeHigh = 0;
_rangeLow = decimal.MaxValue;
_barCount = 0;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
_barCount++;
if (candle.HighPrice > _rangeHigh) _rangeHigh = candle.HighPrice;
if (candle.LowPrice < _rangeLow) _rangeLow = candle.LowPrice;
if (_barCount < RangePeriod) return;
var threshold = atrValue * 0.5m;
if (close > _rangeHigh - threshold && Position <= 0)
{
BuyMarket();
ResetRange();
}
else if (close < _rangeLow + threshold && Position >= 0)
{
SellMarket();
ResetRange();
}
// Slide the range window
if (_barCount > RangePeriod * 2)
ResetRange();
}
private void ResetRange()
{
_rangeHigh = 0;
_rangeLow = decimal.MaxValue;
_barCount = 0;
}
}