The Stoch Levels Strategy is a direct conversion of the MetaTrader 4 expert advisor Stoch.mq4. The original script relies on daily session boundaries, calculates custom price levels from the previous candle and places two pending orders for the upcoming session. This C# version keeps the same trading idea and implements it with StockSharp's high-level strategy API.
The strategy computes a synthetic trading range by expanding the previous candle's high/low spread by a configurable multiplier (default 1.1). It then positions:
A sell limit order above the prior close at half of the expanded range.
A buy limit order below the prior close at half of the expanded range.
Whenever a pending order is filled, the strategy immediately attaches bracket exits (stop-loss and take-profit) using the distances defined in price steps. All outstanding exposure and pending orders are cleared at the beginning of every new trading day, mirroring the midnight reset block from the MQL script.
Trading Logic
Subscribe to the configured candle series (daily by default) and wait for fully finished candles.
When a new session arrives:
Close any open position and cancel all protective or entry orders.
Compute the expanded range range * RangeMultiplier using the previous candle.
Place fresh sell and buy limit orders at Close + range / 2 and Close - range / 2 respectively.
On order fill, create matching stop-loss and take-profit orders using the requested price-step offsets.
If either protective order triggers, cancel the sibling protective order and wait for the next session reset.
Parameters
Name
Description
Default
Notes
TakeProfitPoints
Take-profit distance measured in price steps.
20
Equivalent to TakeProfit input in the MQL script. Set to 0 to disable the take-profit order.
StopLossPoints
Stop-loss distance measured in price steps.
40
Equivalent to StopLoss input in the MQL script. Set to 0 to disable the stop-loss order.
RangeMultiplier
Multiplier applied to the previous candle range (High - Low).
1.1
Matches the hard-coded 1.1 expansion factor in MQL.
OrderVolume
Volume for each pending order.
1
Mirrors the Lots parameter.
CandleType
Candle series that defines the trading session.
Daily
Customize if the strategy should operate on other timeframes.
All parameters are configured via Param() to support optimization and UI binding.
Risk Management
Long entries receive a protective sell stop and sell limit bracket; shorts get the mirrored buy stop and buy limit exits.
Orders are sized using OrderVolume. When one side of the bracket executes, the remaining protective order is cancelled to avoid duplicate exits.
A full flat reset occurs on every new candle, ensuring the strategy does not carry exposure beyond the current session.
Conversion Notes
The MQL implementation used MetaTrader global variables to prevent duplicate orders; the C# version tracks the last processed session internally (_lastProcessedDay).
The overnight closing loop has been translated into the ResetOrders() helper which cancels all pending orders and submits a market flatten command if a position remains.
Stop-loss and take-profit levels are recreated explicitly through StockSharp order methods instead of being embedded into OrderSend parameters.
Trailing stop, money management, and risk inputs present in the MQL script were unused there and remain unsupported in this port.
Usage Tips
Attach the strategy to a security and set OrderVolume, stop distances, and candle type to match the traded instrument.
Ensure the security exposes a proper PriceStep; if not, the strategy falls back to 1 and logs a warning.
Because orders are recalculated only once per completed candle, keep the default daily timeframe to align with the original behaviour.
Review logs to confirm the daily reset, order placement, and protective order attachment workflow.
Files
CS/StochLevelsStrategy.cs – main strategy implementation.
README.md, README_zh.md, README_ru.md – multilingual documentation for the converted strategy.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stoch Levels: Previous candle range breakout with EMA filter and ATR stops.
/// </summary>
public class StochLevelsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _entryPrice;
public StochLevelsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.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 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();
_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0; _prevLow = 0; _entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (_prevHigh == 0 || atrVal <= 0) { _prevHigh = candle.HighPrice; _prevLow = candle.LowPrice; return; }
if (Position > 0)
{
if (close <= _entryPrice - atrVal * 1.5m || close >= _entryPrice + atrVal * 2.5m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close >= _entryPrice + atrVal * 1.5m || close <= _entryPrice - atrVal * 2.5m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (close > _prevHigh && close > emaVal) { _entryPrice = close; BuyMarket(); }
else if (close < _prevLow && close < emaVal) { _entryPrice = close; SellMarket(); }
}
_prevHigh = candle.HighPrice; _prevLow = candle.LowPrice;
}
}
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 ExponentialMovingAverage, AverageTrueRange
class stoch_levels_strategy(Strategy):
def __init__(self):
super(stoch_levels_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(stoch_levels_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_high == 0 or av <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
if self.Position > 0:
if close <= self._entry_price - av * 1.5 or close >= self._entry_price + av * 2.5:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close >= self._entry_price + av * 1.5 or close <= self._entry_price - av * 2.5:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close > self._prev_high and close > ev:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_low and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def OnReseted(self):
super(stoch_levels_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
def CreateClone(self):
return stoch_levels_strategy()