Ver no GitHub

Estratégia típica de breakout de segunda-feira

Visão geral

A Estratégia de Breakout Típica de Segunda-feira é uma versão C# do MetaTrader consultor especialista yi1ywioff50qr6 (ID do repositório 8187). O robô original monitora velas horárias e abre uma posição longa toda segunda-feira, quando a nova sessão abre acima do preço típico da barra anterior (high + low + close) / 3. Esta implementação reproduz a lógica de entrada dentro da estrutura estratégica de alto nível StockSharp e adiciona parâmetros de configuração detalhados para dimensionamento de posição e controle de risco.

Lógica de negociação

  1. A estratégia assina a série de velas configurada (de hora em hora por padrão).
  2. No início de cada vela finalizada verifica se:
    • A vela pertence à segunda-feira.
    • O horário de abertura da vela corresponde ao parâmetro Open Hour configurado (padrão 09:00).
    • Não existem posições abertas ou ordens ativas.
    • O preço de abertura da vela é maior que o preço típico da barra anterior.
  3. Se todas as condições forem satisfeitas, a estratégia envia uma ordem de compra a mercado com um volume calculado pelo bloco de gestão de dinheiro. As distâncias protetoras de stop-loss e take-profit são aplicadas por meio de StartProtection.

A estratégia nunca abre posições curtas e realizará apenas uma negociação por vela qualificada de segunda-feira.

Parâmetros

Parâmetro Descrição Padrão
FixedVolume Tamanho do lote para entradas. Defina como 0 para ativar a tabela de escalabilidade de patrimônio. 0.1
OpenHour Hora da sessão de negociação (0-23) quando os sinais de segunda-feira são avaliados. 9
StopLossPoints Distância em faixas de preço para o stop protetor. 0 desativa a parada. 50
TakeProfitPoints Distância em faixas de preço para a meta de lucro. 0 desativa o alvo. 20
InitialEquity Limite de patrimônio que ativa a escala de lote com base no patrimônio. 600
EquityStep Incremento de capital necessário para aumentar o tamanho da negociação. 300
InitialStepVolume Tamanho do lote usado quando o patrimônio líquido é de pelo menos InitialEquity. 0.4
VolumeStep Tamanho de lote adicional adicionado para cada EquityStep alcançado. 0.2
CandleType Tipo de dados Candle que orienta a estratégia (de hora em hora por padrão). 1 hour time-frame

Gestão de capital

  • Quando FixedVolume é maior que zero a estratégia sempre utiliza o tamanho de lote fixo.
  • Quando FixedVolume é igual a zero, a estratégia inspeciona o patrimônio do portfólio:
    • Se o patrimônio for inferior a InitialEquity, será utilizado o lote mínimo do instrumento.
    • Caso contrário, o volume começa em InitialStepVolume e aumenta em VolumeStep para cada EquityStep de capital adicional.
    • O volume final está alinhado às restrições mínimas e de etapas do instrumento.

Gestão de risco

StartProtection é ativado durante OnStarted. As distâncias de stop-loss e take-profit são automaticamente convertidas de pontos em compensações de preço usando o instrumento PriceStep. Defina qualquer distância como zero para desativar esse componente.

Notas de uso

  • O EA original foi projetado para velas horárias. Prazos mais baixos podem produzir várias velas de segunda-feira na mesma hora. A porta mantém o comportamento de entrada única por vela e ainda ignorará sinais adicionais enquanto uma posição estiver aberta.
  • Certifique-se de que as informações do portfólio (Portfolio.CurrentValue) estejam disponíveis se o bloco de dimensionamento dinâmico estiver ativado.
  • A estratégia requer dados de nível 1 para executar ordens de mercado e a assinatura de vela correspondente para o CandleType configurado.

Notas de conversão

  • A filtragem de números mágicos MQL foi substituída pelas verificações de posição e ordem de StockSharp (Position e ActiveOrders).
  • As comparações de tempo aproveitam DateTimeOffset do tempo de abertura da vela com .ToLocalTime() para permanecerem alinhadas com o tempo do gráfico.
  • As ordens de proteção são tratadas pelo ajudante StartProtection de alto nível, em vez da colocação manual de ordens.
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;
	}
}