View on GitHub

Lock Strategy

Lock Strategy recreates the classic “lock” expert advisor from MetaTrader: it always maintains a hedged pair of long and short positions and keeps recycling them until a profit-lock condition is satisfied. The algorithm is designed for instruments with small tick sizes where a fixed pip-based take profit can be applied.

Trading Workflow

  1. Initial Hedge – as soon as market data becomes available the strategy opens a long and a short position with the same volume. If both orders are filled the volume used for the next hedge is multiplied by the LotExponential factor.
  2. Take Profit Management – every leg stores its entry price. When the candle close moves by TakeProfitPips (converted to instrument ticks) from the entry, the leg is closed with a market order. The opposite side remains open, preserving the hedge-like behaviour from the MQL version.
  3. Re-Hedging – whenever the total number of active legs is one or zero, the strategy immediately opens a fresh pair. If there are no open legs the base volume resets to LotSize before the new pair is created.
  4. Volume Control – the helper method AdjustVolume enforces exchange restrictions: it rounds volumes to the security’s VolumeStep, clamps them by MinVolume and MaxVolume, and cancels the scale-up if the adjusted value becomes zero.

Profit Lock Condition

The original MQL logic monitors account balance versus equity: when balance exceeds equity by ExcessBalanceOverEquity and the equity is at least MinProfit above the last locked balance, every leg is closed. The C# implementation mirrors this behaviour by tracking the equity observed when the strategy is flat and treating it as the running balance. Once the condition is triggered all legs are liquidated and the baseline balance is updated before the cycle restarts with LotSize.

Parameters

  • LotSize – base volume for the first hedge cycle (default: 0.1m).
  • TakeProfitPips – pip distance for closing each leg (default: 100). A value of 0 disables the automatic exit.
  • LotExponential – multiplier applied to the current volume after both legs open successfully (default: 2m).
  • ExcessBalanceOverEquity – tolerated gap between balance and equity before profits are secured (default: 3000m).
  • MinProfit – additional equity growth that must be achieved before closing all legs (default: 500m).
  • CandleType – timeframe driving the strategy logic (default: 1 minute time frame).

Implementation Notes

  • Pip size is recalculated from Security.PriceStep and Security.Decimals so the strategy adapts to 3/5-digit FX symbols as well as standard futures or stocks.
  • Orders are placed using market execution, mirroring the behaviour of the MQL expert that sends market orders with broker-side take profits.
  • The strategy keeps a full history of hedged legs, which enables multiple stacked positions on each side exactly as the source script allowed.
namespace StockSharp.Samples.Strategies;

using System;

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

/// <summary>
/// Lock strategy (simplified).
/// Uses fast and slow EMA crossover with momentum confirmation.
/// </summary>
public class LockStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public LockStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Source candles", "General");

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };

		decimal prevFast = 0, prevSlow = 0;
		bool hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastValue, decimal slowValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevFast = fastValue;
					prevSlow = slowValue;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevFast = fastValue;
					prevSlow = slowValue;
					return;
				}

				// Buy on golden cross
				if (prevFast <= prevSlow && fastValue > slowValue && Position <= 0)
				{
					BuyMarket();
				}
				// Sell on death cross
				else if (prevFast >= prevSlow && fastValue < slowValue && Position >= 0)
				{
					SellMarket();
				}

				prevFast = fastValue;
				prevSlow = slowValue;
			})
			.Start();

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