Nextbar — это точная адаптация эксперта MetaTrader 4 nextbar.mq4. Советник сравнивает закрытие последней завершённой свечи с закрытием свечи, находящейся на заданном числе баров назад. Если расстояние между ценами превышает допустимый порог, система либо следует импульсу, либо работает против него (в зависимости от параметра направления). После входа выставляются симметричные тейк-профит и стоп-лосс, а также срабатывает принудительное закрытие по истечении фиксированного числа баров.
Реализация на StockSharp повторяет ту же логику, опираясь на высокоуровневый API стратегий. Обработка выполняется только на завершённых свечах, поэтому расчёты полностью соответствуют баровому режиму исходного эксперта.
Логика исходного MQL-советника
Расстояние импульса — сравнивается Close[1] и Close[bars2check+1]. Если модуль разницы не меньше minbar * Point, фиксируется торговый сигнал.
Флаг направления — входной параметр direction: значение 1 означает торговлю по тренду (покупка после роста, продажа после падения), 2 — контртрендовый подход (покупка после падения, продажа после роста).
Ограничение по сделкам — одновременно допускается только одна позиция. Новый вход возможен лишь на следующей свече после формирования сигнала.
Выход из позиции — лонг закрывается при достижении ценой тейк-профита выше точки входа либо стоп-лосса ниже неё; для шорта условия зеркальные. Если ни один уровень не сработал, позиция принудительно закрывается спустя bars2hold завершённых свечей.
Особенности реализации в StockSharp
Используются SubscribeCandles() и Bind для получения завершённых свечей на выбранном таймфрейме.
Поддерживается кратковременный буфер закрытий, позволяющий обращаться к свече с индексом bars2check + 1, как в MetaTrader.
Все параметры в пунктах переводятся в абсолютные цены через Security.PriceStep, что повторяет работу константы Point.
Рыночные ордера отправляются с объёмом Volume, а режим входа (Follow либо Reverse) задаётся параметром Direction.
Проверка тейк-профита, стоп-лосса и лимита по количеству баров выполняется один раз на каждую завершённую свечу, полностью воспроизводя оригинальный алгоритм.
Параметры
Параметр
Описание
Значение по умолчанию
Примечания
CandleType
Таймфрейм, по которому вычисляются сигналы.
Часовые свечи
Инструмент должен предоставлять свечи указанного типа.
BarsToCheck
Количество завершённых свечей между опорным закрытием и текущим.
8
Аналог параметра bars2check.
BarsToHold
Максимум завершённых свечей, на протяжении которых удерживается позиция.
10
Соответствует bars2hold. Закрытие происходит на свече, где счётчик достигает значения.
MinMovePoints
Минимальное расстояние между закрытиями в пунктах MetaTrader.
77
Аналог minbar. Значение умножается на Security.PriceStep.
TakeProfitPoints
Дистанция тейк-профита в пунктах.
115
Соответствует входному параметру profit. Ноль отключает цель.
StopLossPoints
Дистанция стоп-лосса в пунктах.
115
Соответствует параметру loss. Ноль отключает защиту.
Direction
Режим торговли: Follow (по тренду) или Reverse (контртренд).
Follow
Полностью повторяет direction (1 = тренд, 2 = контртренд).
Volume
Объём рыночных ордеров.
Значение Strategy.Volume
Настраивается через стандартное свойство стратегии.
Рабочий процесс
Дождаться завершения свечи и сохранить её цену закрытия.
Получить закрытие свечи, расположенной на BarsToCheck баров раньше, и вычислить разницу.
Если модуль разницы меньше MinMovePoints * PriceStep, сигнал игнорируется.
Иначе:
В режиме Follow открыть лонг при росте и шорт при падении.
В режиме Reverse открыть лонг при падении и шорт при росте.
На каждой последующей завершённой свече при открытой позиции:
Закрыть лонг, если закрытие выше входа на TakeProfitPoints или ниже на StopLossPoints.
Закрыть шорт, если закрытие ниже входа на TakeProfitPoints или выше на StopLossPoints.
Принудительно закрыть позицию после BarsToHold завершённых свечей с момента входа.
Практические рекомендации
Для корректного перевода пунктов в цену инструмент должен иметь заданный Security.PriceStep (при необходимости также StepPrice и настройки объёмов).
Стратегия работает с единственной позицией; заранее задайте Volume, соответствующий размеру ордера в MetaTrader.
Все решения принимаются по завершённым свечам, поэтому в тестах и реальной торговле необходимо использовать данные, которые предоставляют закрытые бары выбранного таймфрейма.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class NextbarStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevOpen;
private decimal _prevClose;
private bool _hasPrev;
private int _cooldownRemaining;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public NextbarStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA filter", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevOpen = default;
_prevClose = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevOpen = 0;
_prevClose = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevOpen = candle.OpenPrice; _prevClose = close; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevOpen = candle.OpenPrice;
_prevClose = close;
return;
}
var prevBullish = _prevClose > _prevOpen;
var prevBearish = _prevClose < _prevOpen;
if (prevBullish && close > ema && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (prevBearish && close < ema && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevOpen = candle.OpenPrice;
_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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class nextbar_strategy(Strategy):
def __init__(self):
super(nextbar_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA filter", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 200) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(nextbar_strategy, self).OnReseted()
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(nextbar_strategy, self).OnStarted2(time)
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown_remaining = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.process_candle).Start()
def process_candle(self, candle, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
ema_val = float(ema)
if not self._has_prev:
self._prev_open = open_price
self._prev_close = close
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_open = open_price
self._prev_close = close
return
prev_bullish = self._prev_close > self._prev_open
prev_bearish = self._prev_close < self._prev_open
if prev_bullish and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif prev_bearish and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_open = open_price
self._prev_close = close
def CreateClone(self):
return nextbar_strategy()