View on GitHub

Twenty 200 Pips Strategy

Overview

The strategy replicates the original 20/200 pips MQL5 expert. It examines hourly candles and compares two historical open prices (Open[t1] and Open[t2]). When the difference between these opens exceeds a configurable delta during a specific hour, the strategy enters a single trade for the session and relies on fixed take-profit and stop-loss levels.

Trading logic

  1. Subscribe to hourly candles (configurable) and feed the open price into two Shift indicators to retrieve the opens at the required indexes.
  2. During every finished candle, reset the "can trade" flag once the current hour is greater than the configured trading hour. This mirrors the daily reset in the original expert adviser.
  3. When the hour matches the configured trading hour and no position is open, compare the stored open prices:
    • If Open[t1] > Open[t2] + delta, submit a market sell order.
    • If Open[t1] + delta < Open[t2], submit a market buy order.
  4. After sending an order the strategy forbids new entries until the next daily reset. Protective take-profit and stop-loss orders are managed via StartProtection.

Parameters

  • TakeProfit – distance in price points for the take-profit order (default 200 points).
  • StopLoss – distance in price points for the stop-loss order (default 2000 points).
  • TradeHour – hour of the day when the entry check is performed (default 18).
  • FirstOffset – index of the older open price (maps to Open[t1] in the MQL script, default 7).
  • SecondOffset – index of the more recent open price (Open[t2], default 2).
  • DeltaPoints – minimum difference in points between the two opens to trigger a trade (default 70).
  • Volume – order size used for market entries (default 0.1).
  • CandleType – timeframe used for calculations (default 1-hour candles).

Implementation notes

  • Shift indicators are processed manually to access historical open prices without maintaining custom collections.
  • The strategy calls StartProtection once during OnStarted to emulate the stop-loss/take-profit levels defined in the MQL expert.
  • English comments are included directly in the code to ease maintenance and review.
  • Only one trade per day is allowed because _canTrade is cleared right after an order is placed and restored only after the configured trading hour has passed.

Usage

  1. Attach the strategy to a security and configure the parameters according to the target instrument.
  2. Ensure the security has a valid PriceStep; it is used to convert point-based parameters into absolute price distances.
  3. Start the strategy. It will wait until the configured hour and act on the very next completed candle if the open-price conditions are met.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily breakout strategy derived from the "20/200 pips" MQL5 expert.
/// Compares open prices from different bar offsets and trades the breakout.
/// </summary>
public class Twenty200PipsStrategy : Strategy
{
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _firstOffset;
	private readonly StrategyParam<int> _secondOffset;
	private readonly StrategyParam<int> _deltaPoints;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _opens = new();
	private decimal _pointValue;

	public int TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	public int StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	public int FirstOffset
	{
		get => _firstOffset.Value;
		set => _firstOffset.Value = value;
	}

	public int SecondOffset
	{
		get => _secondOffset.Value;
		set => _secondOffset.Value = value;
	}

	public int DeltaPoints
	{
		get => _deltaPoints.Value;
		set => _deltaPoints.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public Twenty200PipsStrategy()
	{
		_takeProfit = Param(nameof(TakeProfit), 200)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (points)", "Take profit distance in points", "Risk")
			.SetOptimize(50, 500, 50);

		_stopLoss = Param(nameof(StopLoss), 2000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (points)", "Stop loss distance in points", "Risk")
			.SetOptimize(200, 4000, 100);

		_firstOffset = Param(nameof(FirstOffset), 7)
			.SetGreaterThanZero()
			.SetDisplay("First Offset", "Older bar index", "Signal")
			.SetOptimize(1, 12, 1);

		_secondOffset = Param(nameof(SecondOffset), 2)
			.SetGreaterThanZero()
			.SetDisplay("Second Offset", "Newer bar index", "Signal")
			.SetOptimize(1, 6, 1);

		_deltaPoints = Param(nameof(DeltaPoints), 1)
			.SetGreaterThanZero()
			.SetDisplay("Delta (points)", "Minimum difference between opens", "Signal")
			.SetOptimize(10, 200, 10);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_opens.Clear();
		_pointValue = 0m;
	}

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

		_pointValue = Security?.PriceStep ?? 1m;
		if (_pointValue <= 0m)
			_pointValue = 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}

		StartProtection(
			takeProfit: new Unit(TakeProfit * _pointValue, UnitTypes.Absolute),
			stopLoss: new Unit(StopLoss * _pointValue, UnitTypes.Absolute),
			useMarketOrders: true);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_opens.Add(candle.OpenPrice);

		var maxOffset = Math.Max(FirstOffset, SecondOffset);
		if (_opens.Count <= maxOffset)
			return;

		// Keep buffer limited
		if (_opens.Count > maxOffset + 100)
			_opens.RemoveRange(0, _opens.Count - maxOffset - 50);

		if (Position != 0)
			return;

		var openFirst = _opens[_opens.Count - 1 - FirstOffset];
		var openSecond = _opens[_opens.Count - 1 - SecondOffset];
		var threshold = DeltaPoints * _pointValue;

		if (openFirst > openSecond + threshold)
		{
			SellMarket();
		}
		else if (openFirst + threshold < openSecond)
		{
			BuyMarket();
		}
	}
}