Ver no GitHub

Auto Stop-Loss and Take-Profit Strategy

This utility strategy automatically attaches protective stop-loss and take-profit orders to every open position on the configured instrument. It mirrors the behaviour of the original MetaTrader "AutoSet SL TP" expert by monitoring the active position list and enforcing broker distance restrictions before registering protective orders.

The strategy does not open trades on its own. Instead, it watches the volume, direction and execution price of positions that have been created manually or by other strategies. As soon as a long or short position appears, the algorithm calculates the desired stop-loss and take-profit levels expressed in MetaTrader-style pips, adjusts the levels to comply with the freeze and stop constraints published by the trading venue, and then submits the appropriate market-protective orders. When the position is fully closed the protective orders are cancelled automatically.

How it works

  1. Subscribes to Level1 data to receive best bid/ask prices together with optional StopLevel and FreezeLevel fields supplied by the broker.
  2. Converts the configured pip distances into absolute prices using the symbol metadata (price step and decimal precision). Five-digit and three-digit quotes automatically scale by a factor of ten to match MetaTrader pip semantics.
  3. On every quote update or personal trade notification:
    • Ignores the signal if there is no open position or if the direction does not match the configured filter (buy-only, sell-only or both).
    • Calculates the minimal permitted distance between the market price and a protective order. If the broker does not publish freeze/stop levels, the algorithm falls back to three spreads multiplied by 1.1 to stay safely outside of forbidden zones.
    • Determines the stop-loss price and take-profit price relative to the current ask (for longs) or bid (for shorts) and normalises the result to the instrument price step.
    • Places or re-registers stop or limit protective orders with the exact position volume. Orders are replaced only when the target price or volume changes, which keeps exchange modifications to a minimum.
  4. If the position volume becomes zero, all outstanding protective orders are cancelled. The strategy also cancels the existing orders when the trade direction is no longer allowed by the filter.

Because the algorithm relies solely on external fills, it can be combined with discretionary trading, panels or other automated systems that manage entries, while this strategy guarantees a consistent protective envelope.

Parameters

  • StopLossPips – distance from the current market price to the stop-loss in MetaTrader pips. A value of 0 disables the stop order. Default: 50.
  • TakeProfitPips – distance from the current market price to the take-profit in MetaTrader pips. A value of 0 disables the take-profit order. Default: 140.
  • DirectionFilter – specifies which position direction is managed:
    • Buy – protect only long exposure.
    • Sell – protect only short exposure.
    • BuySell – protect both sides (default behaviour in the original script).

Practical notes

  • Protective orders are always created with the absolute position volume. If the broker enforces minimum or maximum lot sizes, the strategy rounds the volume to the nearest permissible value before placing the orders.
  • The algorithm uses ReRegisterOrder to adjust active protective orders. This keeps the same exchange order identifiers whenever possible and avoids unnecessary cancellations.
  • The fallback distance (spread × 3 × 1.1) prevents the stop or take-profit from violating hidden exchange restrictions when explicit freeze/stop levels are not provided.
  • Since the strategy does not manage entries, it can be started before or after positions are opened. Any qualifying position that already exists at the time of startup will be protected immediately after the first quote update.
  • MetaTrader "pips" differ from exchange price steps on symbols with three or five decimal digits. The strategy mirrors the original Expert Advisor by multiplying the point value accordingly, ensuring the configured numbers map exactly to the MT5 settings.

Differences from the MetaTrader expert

  • Instead of modifying in-position stop and take-profit attributes, StockSharp manages explicit protective stop and limit orders. This approach keeps the logic fully transparent inside the StockSharp order book.
  • The StockSharp version uses Level1 market data to rebuild broker restriction levels. If the provider exposes different field names for freeze or stop distances, the strategy automatically discovers them through reflection on the Level1Fields enum.
  • Every code comment and log message is in English to remain consistent with the coding guidelines, while the documentation is localised into Russian and Chinese for end users.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class AutoSetStopLossTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public AutoSetStopLossTakeProfitStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}