View on GitHub

Stoch Levels Strategy

Overview

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

  1. Subscribe to the configured candle series (daily by default) and wait for fully finished candles.
  2. 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.
  3. On order fill, create matching stop-loss and take-profit orders using the requested price-step offsets.
  4. 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

  1. Attach the strategy to a security and set OrderVolume, stop distances, and candle type to match the traded instrument.
  2. Ensure the security exposes a proper PriceStep; if not, the strategy falls back to 1 and logs a warning.
  3. Because orders are recalculated only once per completed candle, keep the default daily timeframe to align with the original behaviour.
  4. 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;
	}
}