Ver no GitHub

For Max V2

Overview

For Max V2 is a port of the MetaTrader 4 expert advisor for_max_v2.mq4. The strategy waits for specific two-candle engulfing patterns and then places a symmetrical pair of buy-stop and sell-stop orders around the most recent candle. Once a breakout order fills, the opposite pending order is removed and the position is managed with fixed stops, optional take-profit levels, and a trailing routine that first locks in a small profit at break-even and then follows price.

Strategy logic

Engulfing pattern detection

The original expert advisor exposes two entry blocks and both are preserved:

  • Type 1 setup – scans the previous Max Search candles (skipping the current bar) and waits for the lowest low within that range to occur two bars ago or for the highest high to occur two bars ago. When that happens the candle two bars back must engulf the previous candle (higher high and lower low). The setup arms a straddle around the most recent finished candle.
  • Type 2 setup – also scans the previous Max Search candles, but looks for the extreme to appear one bar ago. In addition, the candle one bar ago must engulf the candle two bars back. A straddle is then placed around the most recent candle. Both setups can coexist; each manages its own pending orders and expiration clock.

Pending order placement

  • Entry prices – buy-stop orders are placed at the previous candle high plus Gap Points, sell-stop orders at the previous candle low minus Gap Points.
  • Stop-loss – for Type 1 the long stop is anchored at the low of the candle two bars back (minus the gap) and the short stop at the high of that candle (plus the gap). Type 2 uses the previous candle for both sides.
  • Take-profit – optional. Long targets add Gap Points + Buy Take Profit Points to the previous high, and shorts subtract Gap Points + Sell Take Profit Points from the previous low. Setting the take-profit inputs to 0 disables the respective targets.
  • Expiration – each straddle carries a validity timestamp computed as Order Expiry (bars) multiplied by the configured candle timeframe. If the pending orders are still working when the timestamp is reached, both sides are cancelled.

Position management

  • Once a buy-stop fills, any remaining sell-stop orders from either setup are cancelled; the symmetric rule applies after a short entry.
  • Stops and targets are monitored on completed candles. If a candle’s low reaches the long stop (or the high reaches the short stop) the position is closed with a market order. The same approach is used for the take-profit levels.
  • The break-even routine (Break-even Trigger and Break-even Offset) moves the stop to the entry price plus/minus the configured offset once the position advances by the trigger amount.
  • The trailing block keeps the stop Long/Short Trailing Buffer points away from the best excursion, but only after price has travelled far enough (and optionally only after the trade is already profitable). Trailing Step prevents over-frequent adjustments by requiring a minimum improvement before the stop is tightened again.

Parameters

  • Volume – order volume for each pending stop order.
  • Buy Take Profit (points) – distance in points used to compute the long take-profit (set to 0 to disable).
  • Sell Take Profit (points) – distance in points used to compute the short take-profit (set to 0 to disable).
  • Gap (points) – buffer added to highs/lows before placing stop entries and folded into the take-profit distance.
  • Search Depth – number of finished candles scanned when checking for Type 1 and Type 2 engulfing setups.
  • Order Expiry (bars) – number of candle lengths a pending straddle remains active before both sides are cancelled.
  • Break-even Trigger (points) – profit threshold that arms the break-even stop adjustment.
  • Break-even Offset (points) – additional buffer added to the entry price when the break-even stop is placed.
  • Long Trailing Buffer (points) – trailing distance for long positions once break-even has been reached.
  • Short Trailing Buffer (points) – trailing distance for short positions once break-even has been reached.
  • Trailing Step (points) – minimum improvement in stop location required before updating the trailing stop again.
  • Trail Only After Profit – if enabled, trailing waits until the position has moved beyond the buffer before activating.
  • Candle Type – timeframe of the candles used for pattern detection, order expiry, and exit processing.

Additional notes

  • Price offsets expressed in “points” rely on the security’s PriceStep. Symbols with five (or three) decimal places automatically convert to fractional pip sizes just like in MetaTrader.
  • Stop losses and take profits are executed via market orders inside the strategy to mirror the EA’s behaviour of managing levels on closed candles.
  • The strategy does not implement the unused vhod_3 function from the original source; only the two active entry blocks were ported.
  • This package contains only the C# implementation; no Python version is provided.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// For Max V2: N-bar engulfing breakout with EMA filter and ATR stops.
/// </summary>
public class ForMaxV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<int> _lookback;

	private decimal _entryPrice;
	private decimal _prevHigh;
	private decimal _prevLow;
	private readonly decimal[] _highs = new decimal[10];
	private readonly decimal[] _lows = new decimal[10];
	private int _barCount;

	public ForMaxV2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_emaLength = Param(nameof(EmaLength), 30)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_lookback = Param(nameof(Lookback), 10)
			.SetDisplay("Lookback", "N-bar channel lookback.", "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;
	}

	public int Lookback
	{
		get => _lookback.Value;
		set => _lookback.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh = 0;
		_prevLow = 0;
		Array.Clear(_highs, 0, _highs.Length);
		Array.Clear(_lows, 0, _lows.Length);
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_barCount = 0;
		_prevHigh = 0;
		_prevLow = 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 len = Math.Min(Lookback, _highs.Length);
		var idx = _barCount % len;
		_highs[idx] = candle.HighPrice;
		_lows[idx] = candle.LowPrice;
		_barCount++;

		if (_barCount < len || atrVal <= 0)
			return;

		var high = decimal.MinValue;
		var low = decimal.MaxValue;
		for (var i = 0; i < len; i++)
		{
			if (_highs[i] > high) high = _highs[i];
			if (_lows[i] < low) low = _lows[i];
		}

		var close = candle.ClosePrice;

		if (_prevHigh == 0 || _prevLow == 0)
		{
			_prevHigh = high;
			_prevLow = low;
			return;
		}

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 1.5m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 1.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 = high;
		_prevLow = low;
	}
}