Ver no GitHub

Estratégia de teste retangular

Visão geral

A estratégia Rectangle Test reproduz o especialista MetaTrader "RectangleTest" usando o StockSharp de alto nível de API. Ele detecta intervalos laterais em um período intradiário, verifica se duas médias móveis e o preço atual permanecem dentro do intervalo detectado e, em seguida, negocia rompimentos fora do retângulo na direção do EMA mais rápido. Toda a lógica é executada em velas concluídas recebidas de uma fonte de vela configurável.

Lógica de negociação

  1. Assine o fluxo de vela principal (padrão: período de 1 hora) e alimente-o nos seguintes indicadores:
    • ExponentialMovingAverage (EMA) com comprimento configurável EmaPeriod.
    • SimpleMovingAverage (SMA) com comprimento configurável SmaPeriod.
    • Indicadores Mais alto e Mais baixo com comprimento RangeCandles, configurados para ler máximos e mínimos de velas. Eles fornecem os limites do retângulo que emulam os cálculos baseados em matriz MetaTrader.
  2. Depois que todos os indicadores estiverem formados, calcule a altura do retângulo em porcentagem em relação ao limite superior. Somente velas cuja altura é menor que RectangleSizePercent são consideradas consolidações válidas.
  3. Exija que EMA, SMA e a vela próxima permaneçam dentro do retângulo. Isso reproduz o filtro lateral da versão MQL.
  4. Configuração curta:
    • EMA está acima de SMA.
    • O preço de fechamento está acima de EMA (correspondendo à condição "Ask > EMA" de MetaTrader em velas concluídas).
    • A liquidação opcional de uma posição longa existente ocorre primeiro, após a qual uma ordem de mercado curta é enviada.
  5. Configuração longa:
    • EMA está abaixo de SMA.
    • O preço de fechamento está abaixo de EMA (espelhando a regra "Lance < EMA").
    • As posições vendidas existentes são liquidadas antes da abertura da posição comprada.
  6. Cada entrada registra o preço e o volume de entrada esperados. Quando a posição chega a zero, a estratégia compara o preço de saída com o preço de entrada armazenado. As negociações perdedoras aumentam o contador de perdas diárias, aplicando o filtro MaxLosingTradesPerDay exatamente como o auxiliar MQL Loss().

Gestão de dinheiro e risco

  • A estratégia pode funcionar de dois modos:
    • Modo baseado em risco (UseRiskMoneyManagement = true): o volume da posição é dimensionado a partir do valor da conta, o RiskPercent, e do StopLossPoints configurado. O cálculo usa Security.PriceStep, Security.StepPrice e Security.VolumeStep para espelhar a rotina de dimensionamento de lote MetaTrader.
    • Modo de volume fixo (UseRiskMoneyManagement = false): as negociações usam o parâmetro FixedVolume.
  • Após a posição líquida mudar de estável para diferente de zero, SetStopLoss e SetTakeProfit registram ordens de proteção usando StopLossPoints e TakeProfitPoints (expressas em etapas de preço), correspondendo às distâncias SL/TP passadas para m_trade.Sell/Buy no especialista original.
  • MaxLosingTradesPerDay interrompe novos sinais pelo resto do dia assim que o número especificado de negociações perdidas for detectado.

Gerenciamento de tempo

  • A negociação é permitida apenas entre TradeStartTime e TradeEndTime. O auxiliar lida com intervalos que abrangem sessões da meia-noite e também diurnas.
  • Quando EnableTimeClose for verdadeiro, todas as posições abertas serão liquidadas após TimeClose, replicando as entradas MetaTrader "TimeCloseTrue" e TimeClose.

Diferenças vs. versão MetaTrader

  • O indicador original criou retângulos gráficos no gráfico. StockSharp não cria objetos de desenho; em vez disso, o mesmo intervalo é calculado internamente através dos indicadores Mais Alto/Mais Baixo.
  • As negociações perdedoras são contadas usando os preços de fechamento da vela sinalizadora. Isso corresponde à intenção de Loss() (contar pedidos perdidos por dia) enquanto permanece dentro de abstrações de StockSharp de alto nível.
  • As características de preenchimento de pedidos, como ORDER_FILLING_FOK/IOC, são tratadas pelo ambiente de StockSharp, portanto, a configuração explícita do modo de preenchimento não é necessária.

Parâmetros

Nome Padrão Descrição
EmaPeriod 45 Período do EMA rápida.
SmaPeriod 200 Período da lentidão SMA.
RangeCandles 10 Número de velas formando o retângulo.
RectangleSizePercent 0,5 Altura máxima do retângulo permitida para negociação.
StopLossPoints 250 Distância de stop-loss em etapas de preço.
TakeProfitPoints 750 Distância de lucro em etapas de preço.
UseRiskMoneyManagement verdade Alterne entre volume fixo e baseado em risco.
RiskPercent 1 Porcentagem do patrimônio da conta arriscado por negociação.
FixedVolume 1 Volume fixo quando o dimensionamento baseado em risco está desabilitado.
MaxLosingTradesPerDay 1 Limite diário de perdas em negociações.
TradeStartTime 03:00 Hora do dia em que as entradas são permitidas.
TradeEndTime 22:50 Hora do dia após a qual nenhuma nova entrada será gerada.
EnableTimeClose falso Permite a liquidação no final do dia.
TimeClose 23:00 Hora do dia para fechar todas as posições.
CandleType Velas de 1 hora Fonte de dados de vela primária.

Gráficos

Se uma área do gráfico estiver disponível, a estratégia desenha as velas de preço, rápidas EMA, lentas SMA e negociações próprias para visualizar os intervalos de intervalo e o tempo de negociação.

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Rectangle breakout strategy: detects tight consolidation ranges and trades breakouts
/// using EMA/SMA trend direction as a filter.
/// </summary>
public class RectangleTestStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _rangeCandles;
	private readonly StrategyParam<decimal> _rectangleSizePercent;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public int RangeCandles
	{
		get => _rangeCandles.Value;
		set => _rangeCandles.Value = value;
	}

	public decimal RectangleSizePercent
	{
		get => _rectangleSizePercent.Value;
		set => _rectangleSizePercent.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public RectangleTestStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("Fast EMA Period", "Length of the fast EMA", "Indicators");

		_smaPeriod = Param(nameof(SmaPeriod), 50)
			.SetDisplay("Slow SMA Period", "Length of the slow SMA", "Indicators");

		_rangeCandles = Param(nameof(RangeCandles), 10)
			.SetDisplay("Rectangle Candles", "Number of candles for range detection", "Logic");

		_rectangleSizePercent = Param(nameof(RectangleSizePercent), 10m)
			.SetDisplay("Rectangle Size (%)", "Maximum range height in percent", "Logic");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle source", "General");
	}

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

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

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var sma = new SimpleMovingAverage { Length = SmaPeriod };

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

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent));

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

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

		if (_highs.Count >= RangeCandles)
		{
			// Use PREVIOUS window (excluding current candle) for rectangle detection
			var highestValue = decimal.MinValue;
			var lowestValue = decimal.MaxValue;
			var startIdx = _highs.Count - RangeCandles;
			for (var i = startIdx; i < _highs.Count; i++)
			{
				if (_highs[i] > highestValue) highestValue = _highs[i];
				if (_lows[i] < lowestValue) lowestValue = _lows[i];
			}

			if (highestValue > 0m && lowestValue > 0m)
			{
				var rangePercent = (highestValue - lowestValue) / highestValue * 100m;
				if (rangePercent > 0 && rangePercent < RectangleSizePercent)
				{
					var close = candle.ClosePrice;

					// Breakout above rectangle with bullish trend (EMA > SMA)
					if (close > highestValue && emaValue > smaValue && Position == 0)
					{
						BuyMarket();
					}
					// Breakout below rectangle with bearish trend (EMA < SMA)
					else if (close < lowestValue && emaValue < smaValue && Position == 0)
					{
						SellMarket();
					}
				}
			}
		}

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count > RangeCandles + 1)
		{
			_highs.RemoveAt(0);
			_lows.RemoveAt(0);
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_highs.Clear();
		_lows.Clear();

		base.OnReseted();
	}
}