View on GitHub

Otkat Sys Strategy

The strategy reproduces the MetaTrader expert advisor 1_Otkat_Sys. It monitors the previous trading day's open, close, high, and low to decide whether to enter a position during the first three minutes after midnight (broker time) from Tuesday to Thursday.

Trading Logic

  1. Daily statistics – the last completed daily candle is cached in order to compute:
    • Open - Close and Close - Open to detect whether the previous session was bearish or bullish.
    • Close - Low and High - Close to measure how deeply the price pulled back from the extremes.
  2. Entry window – new trades are evaluated when the entry candle opens between 00:00 and 00:03. Monday and Friday are skipped, matching the original robot's DayOfWeek filters.
  3. Directional filters – four mutually exclusive conditions mirror the MQL rules:
    • Bearish previous day (Open - Close above the corridor threshold) combined with a shallow retracement (Close - Low below Pullback - Tolerance) opens a long.
    • Bullish previous day with an extended upside retracement (High - Close above Pullback + Tolerance) also opens a long.
    • Bullish previous day with a weak upside retracement (High - Close below Pullback - Tolerance) opens a short.
    • Bearish previous day with an extended downside retracement (Close - Low above Pullback + Tolerance) opens a short.
  4. Orders – entries are market orders placed with the configured lot size. Buy trades use a take-profit distance equal to TakeProfit + 3 points (as in the original EA); shorts use exactly TakeProfit points. Both sides apply the same stop-loss distance.
  5. Time-based exit – any open position is flattened after 22:45, replicating the nightly cleanup implemented in the MetaTrader script.

All threshold parameters are expressed in points and translated into price distances with the instrument's PriceStep.

Parameters

Name Description
EntryCandleType Timeframe used for the trading window (default: 1 minute).
DailyCandleType Timeframe providing the daily statistics (default: 1 day).
TakeProfit Profit target in points. Long trades add a 3-point buffer.
StopLoss Protective stop distance in points.
PullbackThreshold Base pullback ("Otkat") threshold in points.
CorridorThreshold Directional corridor threshold (KoridorOC).
ToleranceThreshold Pullback tolerance (KoridorOt).
TradeVolume Lot size for each entry.

The strategy automatically resets its cached values on Reset, subscribes to both entry and daily candle streams, and draws candles plus trade markers when a chart area is available.

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class OtkatSysStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public OtkatSysStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 48).SetDisplay("Channel Period", "Channel lookback", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA filter", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 150).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevMid = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevMid = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(highest, lowest, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;
		if (!_hasPrev) { _prevClose = close; _prevMid = mid; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevMid = mid;
			return;
		}

		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevMid = mid;
	}
}