Открыть на GitHub

Стратегия Second Easiest

Обзор

Second Easiest — это перенос на StockSharp эксперта MetaTrader Second_Easiest.mq4. Изначальный робот отслеживает дневную свечу текущей торговой сессии и открывает единственную внутридневную сделку, когда цена демонстрирует движение от открытия дня. По достижении времени закрытия рынка позиция закрывается и советник готовится к следующей сессии. Реализация на StockSharp сохраняет эту логику дневного пробоя, используя высокоуровневые возможности платформы для подписки на свечи, управления заявками и контроля позиций.

Стратегии не нужны дополнительные индикаторы — достаточно текущих значений открытия, максимума и минимума дня. Благодаря этому код остаётся компактным и оперативно реагирует на появление направленного движения. Одновременно может существовать только одна позиция: после закрытия сделки открытие новой возможно лишь при появлении следующего сигнала.

Торговая логика

  1. Выполняется подписка на внутридневной ряд свечей, задаваемый параметром CandleType. По умолчанию это минутный таймфрейм, который позволяет быстро зафиксировать экстремумы дня и совместим с логикой оригинального эксперта.
  2. После завершения каждой свечи обновляются значения открытия, максимума и минимума текущей сессии. Первая обработанная свеча нового дня задаёт все три ориентира; последующие свечи меняют только максимум или минимум, если появляются новые экстремумы.
  3. Как только наступает час, указанный в EntryCutoffHour, новые сигналы игнорируются. В MQL-версии сделки перестают открываться после 16:00 серверного времени — порт повторяет то же правило.
  4. Сделка на покупку возможна лишь в том случае, если текущая цена закрытия выше открытия дня и расстояние между открытием и минимумом превышает RangePointsThreshold. Это точное соответствие условиям «Bid > open» и «open - low > 15 points» из MQL.
  5. Сделка на продажу допускается, если цена закрытия ниже открытия и максимум дня находится не менее чем на тот же порог выше открытия.
  6. Когда сигнал сформирован и открытых позиций нет, отправляется рыночная заявка объёмом TradeVolume. Базовые методы класса Strategy автоматически выбирают нужное направление сделки.
  7. По наступлении 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;
	}
}