Ver en GitHub

Estrategia típica de ruptura del lunes

Descripción general

La Estrategia de ruptura típica del lunes es una versión de C# del asesor experto MetaTrader yi1ywioff50qr6 (ID de repositorio 8187). El robot original monitorea las velas cada hora y abre una posición larga todos los lunes cuando la nueva sesión se abre por encima del precio típico de la barra anterior (high + low + close) / 3. Esta implementación reproduce la lógica de entrada dentro del marco estratégico de alto nivel StockSharp y agrega parámetros de configuración detallados para el dimensionamiento de posiciones y el control de riesgos.

Lógica de trading

  1. La estrategia se suscribe a la serie de velas configurada (cada hora por defecto).
  2. Al inicio de cada vela terminada se comprueba si:
    • La vela pertenece al lunes.
    • La hora de apertura de la vela coincide con el parámetro Hora de apertura configurado (por defecto 09:00).
    • No existen posiciones abiertas ni órdenes activas.
    • El precio de apertura de la vela es mayor que el precio típico de la barra anterior.
  3. Si se cumplen todas las condiciones, la estrategia envía una orden de compra de mercado con un volumen calculado por el bloque de gestión de dinero. Las distancias protectoras de stop-loss y take-profit se aplican a través de StartProtection.

La estrategia nunca abre posiciones cortas y solo realizará una operación por cada vela del lunes calificada.

Parámetros

Parámetro Descripción Predeterminado
FixedVolume Tamaño del lote para entradas. Establezca en 0 para habilitar la tabla de escala de acciones. 0.1
OpenHour Hora de la sesión de negociación (0-23) en la que se evalúan las señales del lunes. 9
StopLossPoints Distancia en puntos de precio para la parada de protección. 0 desactiva la parada. 50
TakeProfitPoints Distancia en puntos de precio para el objetivo de ganancias. 0 desactiva el objetivo. 20
InitialEquity Umbral de capital que activa el escalado de lotes basado en capital. 600
EquityStep Incremento de capital necesario para aumentar el tamaño de la operación. 300
InitialStepVolume Tamaño del lote utilizado cuando el capital es al menos InitialEquity. 0.4
VolumeStep Se agregó un tamaño de lote adicional por cada EquityStep alcanzado. 0.2
CandleType Tipo de datos de vela que impulsa la estrategia (cada hora de forma predeterminada). 1 hour time-frame

Gestión monetaria

  • Cuando FixedVolume es mayor que cero, la estrategia siempre utiliza el tamaño de lote fijo.
  • Cuando FixedVolume es igual a cero, la estrategia inspecciona el capital de la cartera:
    • Si el capital es inferior a InitialEquity, se utiliza el lote mínimo del instrumento.
    • De lo contrario, el volumen comienza en InitialStepVolume y aumenta en VolumeStep por cada EquityStep de capital adicional.
    • El volumen final está alineado con las restricciones mínimas y de paso del instrumento.

Gestión del riesgo

StartProtection se activa durante OnStarted. Las distancias de stop-loss y take-profit se traducen automáticamente de puntos a compensaciones de precios utilizando el instrumento PriceStep. Establezca cualquiera de las distancias en cero para desactivar ese componente.

Notas de uso

  • El EA original está diseñado para velas por horas. Los períodos de tiempo más bajos pueden producir múltiples velas de lunes con la misma hora. El puerto conserva el comportamiento de entrada única por vela y seguirá ignorando señales adicionales mientras una posición esté abierta.
  • Asegúrese de que la información de la cartera (Portfolio.CurrentValue) esté disponible si el bloque de tamaño dinámico está habilitado.
  • La estrategia requiere datos de nivel 1 para ejecutar órdenes de mercado y la suscripción de vela correspondiente para el CandleType configurado.

Notas de conversión

  • El filtrado de números mágicos de MQL se reemplaza con verificaciones de posición y orden de StockSharp (Position y ActiveOrders).
  • Las comparaciones de tiempo aprovechan DateTimeOffset del tiempo de apertura de la vela con .ToLocalTime() para mantenerse alineado con el tiempo del gráfico.
  • Las órdenes de protección las maneja el ayudante de alto nivel StartProtection en lugar de realizarlas manualmente.
using System;
using System.Linq;
using System.Collections.Generic;

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

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

using StockSharp.Algo;
using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader strategy yi1ywioff50qr6 (ID 8187).
/// Buys on Monday when the hourly open breaks above the prior bar's typical price.
/// Applies equity-based position sizing when the fixed lot is disabled.
/// </summary>
public class MondayTypicalBreakoutStrategy : Strategy
{
	private readonly StrategyParam<decimal> _fixedVolume;
	private readonly StrategyParam<int> _openHour;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<decimal> _initialEquity;
	private readonly StrategyParam<decimal> _equityStep;
	private readonly StrategyParam<decimal> _initialStepVolume;
	private readonly StrategyParam<decimal> _volumeStep;
	private readonly StrategyParam<DataType> _candleType;

	private ICandleMessage _previousCandle;
	private DateTimeOffset? _lastSignalTime;
	private decimal _priceStep;

	/// <summary>
	/// Initializes parameters to mirror the MQL expert defaults.
	/// </summary>
	public MondayTypicalBreakoutStrategy()
	{
		_fixedVolume = Param(nameof(FixedVolume), 0.1m)
		.SetNotNegative()
		.SetDisplay("Fixed Volume", "Lot size used for entries (set to 0 to enable equity scaling)", "Risk");

		_openHour = Param(nameof(OpenHour), 9)
		.SetRange(0, 23)
		.SetDisplay("Open Hour", "Hour of the session to evaluate Monday breakout entries", "Session");

		_stopLossPoints = Param(nameof(StopLossPoints), 50)
		.SetNotNegative()
		.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20)
		.SetNotNegative()
		.SetDisplay("Take Profit (points)", "Profit target distance expressed in price points", "Risk");

		_initialEquity = Param(nameof(InitialEquity), 600m)
		.SetGreaterThanZero()
		.SetDisplay("Initial Equity", "Account equity threshold that triggers the first scaling tier", "Money Management");

		_equityStep = Param(nameof(EquityStep), 300m)
		.SetGreaterThanZero()
		.SetDisplay("Equity Step", "Incremental equity required to raise the position size", "Money Management");

		_initialStepVolume = Param(nameof(InitialStepVolume), 0.4m)
		.SetGreaterThanZero()
		.SetDisplay("Initial Step Volume", "Lot size used once the equity threshold is met", "Money Management");

		_volumeStep = Param(nameof(VolumeStep), 0.2m)
		.SetNotNegative()
		.SetDisplay("Volume Step", "Additional lot size added for each equity step", "Money Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for detecting the Monday breakout", "General");
	}

	/// <summary>
	/// Fixed lot size used for entries (set to zero to enable scaling).
	/// </summary>
	public decimal FixedVolume
	{
		get => _fixedVolume.Value;
		set => _fixedVolume.Value = value;
	}

	/// <summary>
	/// Hour (0-23) when Monday entries are evaluated.
	/// </summary>
	public int OpenHour
	{
		get => _openHour.Value;
		set => _openHour.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in price points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in price points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Minimum equity required before the scaling table becomes active.
	/// </summary>
	public decimal InitialEquity
	{
		get => _initialEquity.Value;
		set => _initialEquity.Value = value;
	}

	/// <summary>
	/// Equity increment that increases the trade size by <see cref="VolumeStep"/>.
	/// </summary>
	public decimal EquityStep
	{
		get => _equityStep.Value;
		set => _equityStep.Value = value;
	}

	/// <summary>
	/// Volume applied when the first equity threshold is met.
	/// </summary>
	public decimal InitialStepVolume
	{
		get => _initialStepVolume.Value;
		set => _initialStepVolume.Value = value;
	}

	/// <summary>
	/// Additional volume added for each equity tier.
	/// </summary>
	public decimal VolumeStep
	{
		get => _volumeStep.Value;
		set => _volumeStep.Value = value;
	}

	/// <summary>
	/// Candle type used for detecting breakout conditions.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

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

		_previousCandle = null;
		_lastSignalTime = null;
		_priceStep = 0m;
	}

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

		_priceStep = Security?.PriceStep ?? 0m;
		if (_priceStep <= 0m)
		_priceStep = 0.0001m;

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		if (TakeProfitPoints > 0 || StopLossPoints > 0)
		{
			var takeDistance = TakeProfitPoints > 0
			? new Unit(TakeProfitPoints * _priceStep, UnitTypes.Absolute)
			: new Unit(0m);
			var stopDistance = StopLossPoints > 0
			? new Unit(StopLossPoints * _priceStep, UnitTypes.Absolute)
			: new Unit(0m);

			StartProtection(takeProfit: takeDistance, stopLoss: stopDistance);
		}
	}

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

		var previous = _previousCandle;
		_previousCandle = candle;

		if (previous is null)
		return;

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (Position != 0m)
		return;

		var candleTime = candle.OpenTime.ToLocalTime();
		if (candleTime.DayOfWeek != DayOfWeek.Monday)
		return;

		if (candleTime.Hour != OpenHour)
		return;

		if (_lastSignalTime is DateTimeOffset last && last == candle.OpenTime)
		return;

		var typicalPrice = (previous.HighPrice + previous.LowPrice + previous.ClosePrice) / 3m;
		if (candle.OpenPrice <= typicalPrice)
		return;

		var volume = CalculateOrderVolume();
		volume = AlignVolume(volume);

		if (volume <= 0m)
		return;

		BuyMarket(volume);

		_lastSignalTime = candle.OpenTime;
	}

	private decimal CalculateOrderVolume()
	{
		var fixedVolume = FixedVolume;
		if (fixedVolume > 0m)
		return fixedVolume;

		var security = Security;
		var portfolio = Portfolio;

		var minVolume = security?.MinVolume ?? 0m;
		if (minVolume <= 0m)
		minVolume = 0.01m;

		var equity = portfolio?.CurrentValue ?? portfolio?.BeginValue ?? 0m;
		if (equity <= 0m)
		return minVolume;

		if (equity < InitialEquity)
		return minVolume;

		if (EquityStep <= 0m)
		return InitialStepVolume;

		var stepsDecimal = (equity - InitialEquity) / EquityStep;
		if (stepsDecimal < 0m)
		stepsDecimal = 0m;

		var steps = (int)Math.Floor(stepsDecimal);
		var dynamicVolume = InitialStepVolume + VolumeStep * steps;

		if (dynamicVolume < minVolume)
		dynamicVolume = minVolume;

		return dynamicVolume;
	}

	private decimal AlignVolume(decimal volume)
	{
		var security = Security;
		if (security == null)
		return volume;

		var minVolume = security.MinVolume ?? 0m;
		var maxVolume = security.MaxVolume ?? 0m;
		var step = security.VolumeStep ?? 0m;

		if (minVolume > 0m && volume < minVolume)
		volume = minVolume;

		if (maxVolume > 0m && volume > maxVolume)
		volume = maxVolume;

		if (step > 0m)
		{
			var steps = Math.Round(volume / step, MidpointRounding.AwayFromZero);
			volume = steps * step;
		}

		return volume;
	}
}