The Second Easiest strategy is the StockSharp port of the MetaTrader expert Second_Easiest.mq4. The original robot scans the
daily candle of the current trading session and opens a single intraday position once price proves it is trending away from the
day's open. When the market closes the expert liquidates any exposure, preparing itself for the next session. The StockSharp
version preserves this intraday breakout behaviour while taking advantage of the framework's high-level API for candle
subscriptions, order management and position tracking.
Unlike momentum strategies that require multiple indicators, Second Easiest only needs the running open, high and low of the
current day. This makes it very lightweight while still reacting to the earliest signs of directional conviction. The code keeps
one position at a time and never reverses immediately; the new trade can be opened only after the previous one has been closed.
Trading logic
Subscribe to the intraday candle series defined by CandleType. The default is a one-minute time frame, which gives an early
view of daily extremes while remaining compatible with the daily logic of the original EA.
For every finished candle, update the in-memory record of the session's open, high and low prices. The first candle processed
on a new trading day defines all three values; subsequent candles expand only the high or low whenever a new extreme is reached.
Ignore new setups once the clock reaches EntryCutoffHour. The MetaTrader code stops opening trades at 16:00 server time and
the port follows the same rule.
A long position is allowed only when the current close trades above the daily open and the distance between the open and the
daily low exceeds RangePointsThreshold. This reproduces the "Bid > open" and "open - low > 15 points" conditions from MQL.
A short position is allowed only when the current close sits below the daily open and the distance between the daily high and
the open exceeds the same threshold.
Whenever an entry signal appears and no position is open, send a market order using TradeVolume lots. The helper methods from
the base Strategy class take care of selecting the correct side.
After the market reaches MarketCloseHour, flatten any existing exposure by calling ClosePosition(). No new trades are placed
after this cut-off until the next session begins.
Parameters
Name
Type
Default
Description
CandleType
DataType
1-minute time frame
Primary intraday candles driving the entry and exit logic.
TradeVolume
decimal
1
Lot size used for every market order.
EntryCutoffHour
int
16
Hour (0-23) after which the strategy refuses to open new positions.
MarketCloseHour
int
20
Hour (0-23) when any open position is forcefully closed.
RangePointsThreshold
decimal
15
Minimum distance, expressed in broker points, between the daily open and the closest extreme.
Differences vs. the MetaTrader version
The StockSharp version tracks positions in a netted manner. The behaviour is identical to the original single-order logic
because only one trade can be open at any time and the position is flattened before new entries are evaluated.
MetaTrader retrieves the running open, high and low through iOpen/iHigh/iLow calls on the daily timeframe. The port rebuilds
the same information from intraday candles, avoiding forbidden indicator calls and ensuring the data remains available even when
the brokerage feed does not provide daily bars.
Order closing is performed through ClosePosition() instead of looping through ticket identifiers. The end result is the same:
open exposure is removed as soon as the configured closing hour is reached.
If the security's PriceStep is not provided, the conversion treats the RangePointsThreshold as an absolute price distance.
This safety fallback keeps the system operational on instruments that report prices without step metadata.
Usage notes
Volume is set to TradeVolume in OnStarted, so changing the parameter immediately affects subsequent orders without
modifying the rest of the code.
When choosing a different CandleType, make sure it still provides enough granularity to track the intraday open/high/low
accurately. For example, five-minute candles work well, but hourly bars might delay the detection of daily extremes.
Increase RangePointsThreshold to filter out low-volatility sessions. Decreasing it allows the strategy to trigger even when
the early range is small.
Because the algorithm closes all positions at the end of the day, it does not require overnight margin. Brokers that enforce
session breaks will also reset the internal range counters automatically when trading resumes.
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()