Ver no GitHub

Estratégia de Bloqueio (Lock)

A Estratégia de Bloqueio recria o assessor especialista clássico de "lock" do MetaTrader: sempre mantém um par hedgeado de posições compradas e vendidas e continua reciclando-os até que uma condição de bloqueio de lucro seja satisfeita. O algoritmo é projetado para instrumentos com tamanhos de tick pequenos onde um take-profit fixo baseado em pips pode ser aplicado.

Fluxo de trabalho de trading

  1. Hedge inicial – assim que os dados de mercado estiverem disponíveis, a estratégia abre uma posição comprada e vendida com o mesmo volume. Se ambas as ordens forem preenchidas, o volume usado para o próximo hedge é multiplicado pelo fator LotExponential.
  2. Gestão do take-profit – cada perna armazena seu preço de entrada. Quando o fechamento da vela se move por TakeProfitPips (convertido para ticks do instrumento) desde a entrada, a perna é fechada com uma ordem de mercado. O lado oposto permanece aberto, preservando o comportamento tipo hedge da versão MQL.
  3. Re-hedge – sempre que o número total de pernas ativas for um ou zero, a estratégia imediatamente abre um novo par. Se não houver pernas abertas, o volume base é redefinido para LotSize antes de criar o novo par.
  4. Controle de volume – o método auxiliar AdjustVolume aplica as restrições da bolsa: arredonda os volumes para o VolumeStep do ativo, limita-os por MinVolume e MaxVolume, e cancela o escalonamento se o valor ajustado se tornar zero.

Condição de bloqueio de lucro

A lógica MQL original monitora o saldo da conta versus o capital: quando o saldo excede o capital por ExcessBalanceOverEquity e o capital está pelo menos MinProfit acima do último saldo bloqueado, cada perna é fechada. A implementação em C# espelha esse comportamento rastreando o capital observado quando a estratégia está plana e tratando-o como o saldo em execução. Assim que a condição é acionada, todas as pernas são liquidadas e o saldo de referência é atualizado antes do ciclo reiniciar com LotSize.

Parâmetros

  • LotSize – volume base para o primeiro ciclo de hedge (padrão: 0.1m).
  • TakeProfitPips – distância em pips para fechar cada perna (padrão: 100). Um valor de 0 desativa a saída automática.
  • LotExponential – multiplicador aplicado ao volume atual após ambas as pernas abrirem com sucesso (padrão: 2m).
  • ExcessBalanceOverEquity – gap tolerado entre saldo e capital antes de garantir lucros (padrão: 3000m).
  • MinProfit – crescimento adicional do capital que deve ser alcançado antes de fechar todas as pernas (padrão: 500m).
  • CandleType – timeframe que impulsiona a lógica da estratégia (padrão: timeframe de 1 minuto).

Notas de implementação

  • O tamanho do pip é recalculado a partir de Security.PriceStep e Security.Decimals, portanto a estratégia se adapta a símbolos FX de 3/5 dígitos bem como a futuros ou ações padrão.
  • As ordens são colocadas usando execução de mercado, refletindo o comportamento do especialista MQL que envia ordens de mercado com take-profits do lado do broker.
  • A estratégia mantém um histórico completo de pernas hedgeadas, o que permite múltiplas posições empilhadas em cada lado, exatamente como o script fonte permitia.
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);
		}
	}
}