Ver no GitHub

Estratégia de canal pendente TrendMeLeaveMe

Esta implementação StockSharp recria o consultor especialista MetaTrader "TrendMeLeaveMe" original. A ideia é seguir manualmente um canal de tendência dinâmico e usar ordens de stop pendentes para capturar rompimentos sempre que o preço abraçar a linha de tendência. Como StockSharp não funciona com objetos gráficos desenhados pelo usuário, a estratégia reconstrói o centro do canal automaticamente com um indicador de regressão linear e, em seguida, reproduz a mesma lógica de deslocamento que a versão MQL aplicou às linhas guias superiores e inferiores.

A abordagem é projetada para entradas longas e curtas. Assim que uma ordem de stop é acionada, a posição é imediatamente protegida com ordens estáticas de stop-loss e take-profit que refletem as distâncias configuradas no EA. Os pedidos pendentes são constantemente atualizados para que os níveis de ativação rastreiem o valor mais recente da linha de regressão.

Como funciona a estratégia

  1. Uma assinatura de vela aciona um indicador LinearRegression que atua como a linha de tendência intermediária.
  2. O usuário define quatro compensações (superior/inferior para cenários de compra e venda) nas etapas de preço do instrumento. A estratégia traduz-os em preços acima ou abaixo da linha de regressão.
  3. Quando a última vela fecha entre a linha de tendência e o deslocamento inferior configurado, um stop de compra é posicionado no deslocamento superior. Simetricamente, quando o preço fecha entre a linha e o deslocamento superior, um stop de venda é colocado no limite inferior.
  4. Se o mercado sair dessas zonas de ativação, a ordem pendente correspondente será cancelada para que a estratégia não sobrecarregue o livro.
  5. Após a execução de uma ordem de stop, a negociação é encerrada com um stop loss estático e um take-profit que usa as mesmas distâncias de pontos do consultor especialista original.

Sinais

  • Configuração de compra: O fechamento da vela está abaixo ou igual à linha de regressão, mas ainda acima do deslocamento inferior de compra. Uma ordem stop de compra é colocada no deslocamento superior e segue a linha enquanto a condição permanece válida.
  • Configuração de venda: O fechamento da vela está acima ou igual à linha de regressão, mas ainda abaixo do deslocamento superior de venda. Uma ordem stop de venda é colocada no deslocamento inferior e segue a linha de tendência.
  • Sem configuração: Quando o preço está fora do corredor de ativação, os pedidos pendentes existentes são removidos.

Gestão de risco

  • As negociações de compra usam BuyStopLossSteps e BuyTakeProfitSteps para calcular níveis fixos de stop-loss e take-profit a partir do preço de entrada.
  • As negociações de venda usam SellStopLossSteps e SellTakeProfitSteps para a mesma finalidade.
  • As ordens de proteção são recalculadas apenas quando a posição líquida muda, imitando como MetaTrader atribui níveis de stop diretamente a cada ordem pendente.

Parâmetros

  • CandleType – agregação de velas usada para calcular a linha de tendência.
  • TrendLength – número de velas na janela de regressão linear.
  • BuyStepUpper / BuyStepLower – compensações (em etapas de preço) que definem o gatilho superior e o limite de ativação inferior para configurações longas.
  • SellStepUpper / SellStepLower – compensações (em etapas de preço) que definem o corredor de ativação para configurações curtas.
  • BuyTakeProfitSteps / BuyStopLossSteps – distâncias para saídas de posições longas, expressas em etapas de preço.
  • SellTakeProfitSteps / SellStopLossSteps – distâncias para saídas de posições curtas.
  • BuyVolume / SellVolume – volume utilizado para ordens pendentes de cada lado.

Notas

  • Como não há linhas de tendência manuais, o indicador de regressão substitui os objetos gráficos da estratégia MQL. Os usuários podem experimentar o comprimento da regressão para aproximar sua análise manual de tendências.
  • A estratégia só negocia quando a conexão da exchange está ativa (IsFormedAndOnlineAndAllowTrading).
  • As ordens pendentes são canceladas automaticamente sempre que já existir uma posição na mesma direção, reproduzindo o comportamento de ordem única do EA original.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Pending stop-order breakout strategy inspired by the TrendMeLeaveMe expert advisor.
/// Uses a regression trend line as the dynamic channel center and offsets upper/lower boundaries.
/// When price trades near the trend line it places stop orders that include static stop-loss and take-profit distances.
/// </summary>
public class TrendMeLeaveMeChannelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _trendLength;
	private readonly StrategyParam<int> _buyStepUpper;
	private readonly StrategyParam<int> _buyStepLower;
	private readonly StrategyParam<int> _sellStepUpper;
	private readonly StrategyParam<int> _sellStepLower;
	private readonly StrategyParam<int> _buyTakeProfitSteps;
	private readonly StrategyParam<int> _buyStopLossSteps;
	private readonly StrategyParam<int> _sellTakeProfitSteps;
	private readonly StrategyParam<int> _sellStopLossSteps;
	private readonly StrategyParam<decimal> _buyVolume;
	private readonly StrategyParam<decimal> _sellVolume;

	private decimal _entryPrice;
	private decimal? _activeStop;
	private decimal? _activeTake;
	private int _activeDirection; // 1=long, -1=short, 0=flat

	/// <summary>
	/// Initializes parameters.
	/// </summary>
	public TrendMeLeaveMeChannelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Candle aggregation used for trend estimation", "General");

		_trendLength = Param(nameof(TrendLength), 100)
		.SetGreaterThanZero()
		.SetDisplay("Trend Length", "Number of candles used in the regression trend line", "Trend")
		
		.SetOptimize(50, 200, 25);

		_buyStepUpper = Param(nameof(BuyStepUpper), 10)
		.SetGreaterThanZero()
		.SetDisplay("Buy Upper Offset", "Number of price steps added above the trend line for buy stop", "Buy Orders")
		
		.SetOptimize(5, 30, 5);

		_buyStepLower = Param(nameof(BuyStepLower), 50)
		.SetGreaterThanZero()
		.SetDisplay("Buy Lower Offset", "Number of price steps below the trend line that activates buy orders", "Buy Orders")
		
		.SetOptimize(20, 80, 10);

		_sellStepUpper = Param(nameof(SellStepUpper), 50)
		.SetGreaterThanZero()
		.SetDisplay("Sell Upper Offset", "Number of price steps above the trend line that activates sell orders", "Sell Orders")
		
		.SetOptimize(20, 80, 10);

		_sellStepLower = Param(nameof(SellStepLower), 10)
		.SetGreaterThanZero()
		.SetDisplay("Sell Lower Offset", "Number of price steps below the trend line for sell stop", "Sell Orders")
		
		.SetOptimize(5, 30, 5);

		_buyTakeProfitSteps = Param(nameof(BuyTakeProfitSteps), 50)
		.SetGreaterThanZero()
		.SetDisplay("Buy Take Profit", "Take-profit distance in price steps for long trades", "Risk")
		
		.SetOptimize(20, 100, 10);

		_buyStopLossSteps = Param(nameof(BuyStopLossSteps), 30)
		.SetGreaterThanZero()
		.SetDisplay("Buy Stop Loss", "Stop-loss distance in price steps for long trades", "Risk")
		
		.SetOptimize(10, 60, 10);

		_sellTakeProfitSteps = Param(nameof(SellTakeProfitSteps), 50)
		.SetGreaterThanZero()
		.SetDisplay("Sell Take Profit", "Take-profit distance in price steps for short trades", "Risk")
		
		.SetOptimize(20, 100, 10);

		_sellStopLossSteps = Param(nameof(SellStopLossSteps), 30)
		.SetGreaterThanZero()
		.SetDisplay("Sell Stop Loss", "Stop-loss distance in price steps for short trades", "Risk")
		
		.SetOptimize(10, 60, 10);

		_buyVolume = Param(nameof(BuyVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Buy Volume", "Order volume for buy stop entries", "Buy Orders");

		_sellVolume = Param(nameof(SellVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Sell Volume", "Order volume for sell stop entries", "Sell Orders");
	}

	/// <summary>
	/// Candle aggregation used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Length of the regression trend line indicator.
	/// </summary>
	public int TrendLength
	{
		get => _trendLength.Value;
		set => _trendLength.Value = value;
	}

	/// <summary>
	/// Price steps added above the trend line for buy stop orders.
	/// </summary>
	public int BuyStepUpper
	{
		get => _buyStepUpper.Value;
		set => _buyStepUpper.Value = value;
	}

	/// <summary>
	/// Price steps subtracted below the trend line that activates buy orders.
	/// </summary>
	public int BuyStepLower
	{
		get => _buyStepLower.Value;
		set => _buyStepLower.Value = value;
	}

	/// <summary>
	/// Price steps added above the trend line that activates sell orders.
	/// </summary>
	public int SellStepUpper
	{
		get => _sellStepUpper.Value;
		set => _sellStepUpper.Value = value;
	}

	/// <summary>
	/// Price steps subtracted below the trend line for sell stop orders.
	/// </summary>
	public int SellStepLower
	{
		get => _sellStepLower.Value;
		set => _sellStepLower.Value = value;
	}

	/// <summary>
	/// Take-profit distance (price steps) for long trades.
	/// </summary>
	public int BuyTakeProfitSteps
	{
		get => _buyTakeProfitSteps.Value;
		set => _buyTakeProfitSteps.Value = value;
	}

	/// <summary>
	/// Stop-loss distance (price steps) for long trades.
	/// </summary>
	public int BuyStopLossSteps
	{
		get => _buyStopLossSteps.Value;
		set => _buyStopLossSteps.Value = value;
	}

	/// <summary>
	/// Take-profit distance (price steps) for short trades.
	/// </summary>
	public int SellTakeProfitSteps
	{
		get => _sellTakeProfitSteps.Value;
		set => _sellTakeProfitSteps.Value = value;
	}

	/// <summary>
	/// Stop-loss distance (price steps) for short trades.
	/// </summary>
	public int SellStopLossSteps
	{
		get => _sellStopLossSteps.Value;
		set => _sellStopLossSteps.Value = value;
	}

	/// <summary>
	/// Order volume for buy stop entries.
	/// </summary>
	public decimal BuyVolume
	{
		get => _buyVolume.Value;
		set => _buyVolume.Value = value;
	}

	/// <summary>
	/// Order volume for sell stop entries.
	/// </summary>
	public decimal SellVolume
	{
		get => _sellVolume.Value;
		set => _sellVolume.Value = value;
	}

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

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

		_entryPrice = 0m;
		_activeStop = null;
		_activeTake = null;
		_activeDirection = 0;
	}

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

		var regression = new LinearRegression { Length = TrendLength };
		var subscription = SubscribeCandles(CandleType);

		subscription
		.BindEx(regression, ProcessCandle)
		.Start();
	}

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

		if (!indVal.IsFinal || !indVal.IsFormed)
			return;

		if (indVal is not ILinearRegressionValue lrVal || lrVal.LinearReg is not decimal trendValue)
			return;

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

		// Check virtual SL/TP first
		CheckProtection(candle);

		var close = candle.ClosePrice;
		var middle = trendValue;

		var buyLower = middle - BuyStepLower * priceStep;
		var sellUpper = middle + SellStepUpper * priceStep;

		// Buy signal: price is below trend line in the buy zone
		if (close <= middle && close >= buyLower && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				ClearProtection();
			}

			BuyMarket(BuyVolume);
			_entryPrice = close;
			_activeStop = close - BuyStopLossSteps * priceStep;
			_activeTake = close + BuyTakeProfitSteps * priceStep;
			_activeDirection = 1;
		}
		// Sell signal: price is above trend line in the sell zone
		else if (close >= middle && close <= sellUpper && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Position);
				ClearProtection();
			}

			SellMarket(SellVolume);
			_entryPrice = close;
			_activeStop = close + SellStopLossSteps * priceStep;
			_activeTake = close - SellTakeProfitSteps * priceStep;
			_activeDirection = -1;
		}
	}

	private void CheckProtection(ICandleMessage candle)
	{
		if (_activeDirection == 1 && Position > 0 && _activeStop.HasValue && _activeTake.HasValue)
		{
			if (candle.LowPrice <= _activeStop.Value || candle.HighPrice >= _activeTake.Value)
			{
				SellMarket(Position);
				ClearProtection();
			}
		}
		else if (_activeDirection == -1 && Position < 0 && _activeStop.HasValue && _activeTake.HasValue)
		{
			if (candle.HighPrice >= _activeStop.Value || candle.LowPrice <= _activeTake.Value)
			{
				BuyMarket(Math.Abs(Position));
				ClearProtection();
			}
		}
	}

	private void ClearProtection()
	{
		_activeStop = null;
		_activeTake = null;
		_activeDirection = 0;
		_entryPrice = 0m;
	}
}