Ver en GitHub

Estrategia de Bloqueo (Lock)

La Estrategia de Bloqueo recrea el asesor experto de "bloqueo" clásico de MetaTrader: siempre mantiene un par cubierto de posiciones largas y cortas y sigue reciclándolos hasta que se satisface una condición de bloqueo de beneficios. El algoritmo está diseñado para instrumentos con tamaños de tick pequeños donde se puede aplicar un take-profit fijo basado en pips.

Flujo de trabajo de trading

  1. Cobertura inicial – tan pronto como los datos de mercado estén disponibles, la estrategia abre una posición larga y corta con el mismo volumen. Si ambas órdenes se ejecutan, el volumen usado para la próxima cobertura se multiplica por el factor LotExponential.
  2. Gestión del take-profit – cada pierna almacena su precio de entrada. Cuando el cierre de la vela se mueve por TakeProfitPips (convertido a ticks del instrumento) desde la entrada, la pierna se cierra con una orden de mercado. El lado opuesto permanece abierto, preservando el comportamiento de cobertura de la versión MQL.
  3. Re-cobertura – siempre que el número total de piernas activas sea uno o cero, la estrategia abre inmediatamente un nuevo par. Si no hay piernas abiertas, el volumen base se restablece a LotSize antes de crear el nuevo par.
  4. Control de volumen – el método helper AdjustVolume aplica las restricciones de la bolsa: redondea los volúmenes al VolumeStep de la seguridad, los limita por MinVolume y MaxVolume, y cancela el escalado si el valor ajustado llega a cero.

Condición de bloqueo de beneficios

La lógica MQL original monitorea el balance de la cuenta versus el capital: cuando el balance supera el capital en ExcessBalanceOverEquity y el capital está al menos MinProfit por encima del último balance bloqueado, cada pierna se cierra. La implementación en C# refleja este comportamiento rastreando el capital observado cuando la estrategia está plana y tratándolo como el balance en ejecución. Una vez que se activa la condición, todas las piernas se liquidan y el balance de referencia se actualiza antes de que el ciclo se reinicie con LotSize.

Parámetros

  • LotSize – volumen base para el primer ciclo de cobertura (predeterminado: 0.1m).
  • TakeProfitPips – distancia en pips para cerrar cada pierna (predeterminado: 100). Un valor de 0 desactiva la salida automática.
  • LotExponential – multiplicador aplicado al volumen actual después de que ambas piernas abren con éxito (predeterminado: 2m).
  • ExcessBalanceOverEquity – brecha tolerada entre balance y capital antes de asegurar beneficios (predeterminado: 3000m).
  • MinProfit – crecimiento adicional del capital que debe lograrse antes de cerrar todas las piernas (predeterminado: 500m).
  • CandleType – marco temporal que impulsa la lógica de la estrategia (predeterminado: marco temporal de 1 minuto).

Notas de implementación

  • El tamaño del pip se recalcula a partir de Security.PriceStep y Security.Decimals, por lo que la estrategia se adapta a símbolos FX de 3/5 dígitos así como a futuros o acciones estándar.
  • Las órdenes se colocan usando ejecución de mercado, reflejando el comportamiento del experto MQL que envía órdenes de mercado con take-profits del lado del broker.
  • La estrategia mantiene un historial completo de piernas cubiertas, lo que permite múltiples posiciones apiladas en cada lado, exactamente como el script fuente lo permitía.
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);
		}
	}
}