Second Easiest — это перенос на StockSharp эксперта MetaTrader Second_Easiest.mq4. Изначальный робот отслеживает дневную свечу
текущей торговой сессии и открывает единственную внутридневную сделку, когда цена демонстрирует движение от открытия дня. По
достижении времени закрытия рынка позиция закрывается и советник готовится к следующей сессии. Реализация на StockSharp сохраняет
эту логику дневного пробоя, используя высокоуровневые возможности платформы для подписки на свечи, управления заявками и
контроля позиций.
Стратегии не нужны дополнительные индикаторы — достаточно текущих значений открытия, максимума и минимума дня. Благодаря этому
код остаётся компактным и оперативно реагирует на появление направленного движения. Одновременно может существовать только одна
позиция: после закрытия сделки открытие новой возможно лишь при появлении следующего сигнала.
Торговая логика
Выполняется подписка на внутридневной ряд свечей, задаваемый параметром CandleType. По умолчанию это минутный таймфрейм,
который позволяет быстро зафиксировать экстремумы дня и совместим с логикой оригинального эксперта.
После завершения каждой свечи обновляются значения открытия, максимума и минимума текущей сессии. Первая обработанная свеча
нового дня задаёт все три ориентира; последующие свечи меняют только максимум или минимум, если появляются новые экстремумы.
Как только наступает час, указанный в EntryCutoffHour, новые сигналы игнорируются. В MQL-версии сделки перестают открываться
после 16:00 серверного времени — порт повторяет то же правило.
Сделка на покупку возможна лишь в том случае, если текущая цена закрытия выше открытия дня и расстояние между открытием и
минимумом превышает RangePointsThreshold. Это точное соответствие условиям «Bid > open» и «open - low > 15 points» из MQL.
Сделка на продажу допускается, если цена закрытия ниже открытия и максимум дня находится не менее чем на тот же порог выше
открытия.
Когда сигнал сформирован и открытых позиций нет, отправляется рыночная заявка объёмом TradeVolume. Базовые методы класса
Strategy автоматически выбирают нужное направление сделки.
По наступлении MarketCloseHour вызывается ClosePosition(), что закрывает оставшиеся позиции. До следующего торгового дня
новые сделки не рассматриваются.
Параметры
Название
Тип
Значение по умолчанию
Описание
CandleType
DataType
Минутный таймфрейм
Основной ряд свечей, определяющий входы и выходы.
TradeVolume
decimal
1
Объём каждой рыночной заявки.
EntryCutoffHour
int
16
Час (0–23), после которого стратегия не открывает новые позиции.
MarketCloseHour
int
20
Час (0–23), по наступлении которого открытая позиция закрывается принудительно.
RangePointsThreshold
decimal
15
Минимальная дистанция в пунктах между открытием дня и ближайшим экстремумом.
Отличия от MetaTrader-версии
StockSharp работает с неттинговыми позициями. Благодаря ограничению на одну сделку поведение полностью совпадает с оригинальным
экспертом, где в каждый момент времени открыт только один ордер.
В MetaTrader значения открытия, максимума и минимума получаются через функции iOpen/iHigh/iLow на дневном таймфрейме. В портированной
версии данные формируются из внутридневных свечей, что позволяет обходиться без запрещённых вызовов индикаторов и работать даже при
отсутствии готовых дневных баров у брокера.
Закрытие выполняется методом ClosePosition() вместо перебора тикетов. Результат идентичен: позиция закрывается сразу после наступления
заданного часа.
Если у инструмента отсутствует PriceStep, параметр RangePointsThreshold трактуется как абсолютная ценовая дистанция. Это резервный
механизм, гарантирующий работу стратегии на инструментах без информации о минимальном шаге цены.
Рекомендации по применению
В OnStarted свойство Volume приравнивается к TradeVolume, поэтому изменение параметра сразу повлияет на объём следующих сделок.
При выборе другого CandleType убедитесь, что таймфрейм достаточно мелкий для отслеживания дневных экстремумов. Пятиминутные свечи
подходят хорошо, а часовые могут дать запоздалый сигнал.
Увеличение RangePointsThreshold отсекает маловолатильные дни; уменьшение значения делает стратегию более чувствительной.
Стратегия не переносит позиции через ночь, поэтому не требует дополнительного маржинального обеспечения. Если торговая площадка
делает перерывы, при возобновлении торгов расчёт дневных уровней начнётся заново.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// SecondEasiest: RSI reversal with EMA filter and ATR stops.
/// </summary>
public class SecondEasiestStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevRsi;
private decimal _entryPrice;
public SecondEasiestStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 25)
.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 RsiLength { get => _rsiLength.Value; set => _rsiLength.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();
_prevRsi = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0; _entryPrice = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevRsi == 0 || atrVal <= 0) { _prevRsi = rsiVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 70) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 30) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (rsiVal > 55 && _prevRsi <= 55 && close > emaVal) { _entryPrice = close; BuyMarket(); }
else if (rsiVal < 45 && _prevRsi >= 45 && close < emaVal) { _entryPrice = close; SellMarket(); }
}
_prevRsi = rsiVal;
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage, AverageTrueRange
class second_easiest_strategy(Strategy):
def __init__(self):
super(second_easiest_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._ema_length = self.Param("EmaLength", 25) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_rsi = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(second_easiest_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
ev = float(ema_val)
av = float(atr_val)
if self._prev_rsi == 0 or av <= 0:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or rv > 70:
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 rv < 30:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self.Position == 0:
if rv > 55 and self._prev_rsi <= 55 and close > ev:
self._entry_price = close
self.BuyMarket()
elif rv < 45 and self._prev_rsi >= 45 and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_rsi = rv
def OnReseted(self):
super(second_easiest_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._entry_price = 0.0
def CreateClone(self):
return second_easiest_strategy()