Ver no GitHub

Estratégia Renko Level EA

Visão geral

  • Convertida do consultor especializado MetaTrader Renko Level EA.mq5.
  • Emula o indicador original mantendo um nível Renko superior e inferior derivado do parâmetro BrickSize.
  • Avalia velas finalizadas fornecidas por CandleType (padrão: período de 1 minuto) e reage quando a grade Renko se desloca.
  • Não usa stops ou alvos fixos; cada saída ocorre por meio de um sinal oposto.

Lógica de trading

  1. Na primeira vela finalizada o preço de fechamento é arredondado para a grade Renko para inicializar os níveis superior e inferior.
  2. Para cada vela subsequente:
    • Se o fechamento permanecer entre os limites atuais, a grade permanece inalterada.
    • Um fechamento acima do nível superior eleva o bloco Renko para cima para o próximo valor da grade.
    • Um fechamento abaixo do nível inferior empurra o bloco para baixo.
  3. Uma mudança no nível Renko superior é interpretada como um rompimento direcional.
    • Nível superior crescente → sinal altista (a menos que ReverseSignals esteja habilitado).
    • Nível superior decrescente → sinal baixista.
  4. Os sinais podem opcionalmente ser invertidos (ReverseSignals) ou piramidados (AllowIncrease) para corresponder ao comportamento do EA original.

Gestão de ordens

  • Antes de entrar comprado, qualquer posição vendida é fechada; o oposto acontece antes de entrar vendido.
  • Quando AllowIncrease = false, a estratégia abre um novo trade apenas se não existir nenhuma posição nessa direção.
  • Quando AllowIncrease = true, ordens adicionais de tamanho OrderVolume são permitidas mesmo se uma posição já estiver aberta.
  • Não há stop-loss ou take-profit dedicados; os reversais de posição servem como mecanismo de saída.
  • StartProtection() é invocado uma vez para manter as salvaguardas de risco alinhadas com o framework base.

Parâmetros

Nome Descrição Padrão Otimizável
BrickSize Tamanho do bloco Renko medido como múltiplos de Security.PriceStep. Define o quanto o preço deve se mover para deslocar a grade. 30 Sim (10 → 100 passo 10)
OrderVolume Volume enviado com cada ordem a mercado. 1 Não
ReverseSignals Inverte as ações altistas e baixistas. Espelha a entrada Reverse do EA. false Não
AllowIncrease Permite adicionar a uma posição existente em vez de esperar por uma posição plana. Espelha o indicador Increase do EA. false Não
CandleType Fonte de velas usada para os cálculos. Padrão para velas de período de 1 minuto, mas qualquer série suportada pode ser fornecida. TimeFrameCandleMessage(1m) Não

Notas práticas

  • BrickSize se adapta automaticamente ao instrumento negociado porque multiplica o PriceStep definido pela bolsa.
  • A decisão é baseada puramente em preços de fechamento; movimentos intrabarra importam apenas quando formam o fechamento final.
  • Combinar ReverseSignals e AllowIncrease permite testar variantes tanto contratendência quanto de piramidação do EA.
  • Funciona em qualquer mercado onde a lógica de rompimento estilo Renko é relevante, incluindo forex, futuros e instrumentos cripto.

Classificação

  • Regime: Seguidor de tendência (rompimento Renko).
  • Direção: Comprado/Vendido.
  • Complexidade: Moderado (rastreamento de nível personalizado, ajuste mínimo).
  • Stops: Nenhum; saídas em sinais inversos.
  • Período: Configurável via CandleType.
  • Indicadores: Projeção de nível Renko personalizada.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that mirrors the Renko Level Expert Advisor from MetaTrader.
/// Tracks level changes generated by a Renko style grid and flips positions accordingly.
/// </summary>
public class RenkoLevelEaStrategy : Strategy
{
	private readonly StrategyParam<int> _brickSize;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<bool> _reverseSignals;
	private readonly StrategyParam<bool> _allowIncrease;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _upperLevel;
	private decimal _lowerLevel;
	private decimal? _previousUpperLevel;
	private bool _levelsInitialized;

	/// <summary>
	/// Renko brick size expressed in price steps.
	/// </summary>
	public int BrickSize
	{
		get => _brickSize.Value;
		set => _brickSize.Value = value;
	}

	/// <summary>
	/// Volume for each executed market order.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// When enabled, long and short signals are swapped.
	/// </summary>
	public bool ReverseSignals
	{
		get => _reverseSignals.Value;
		set => _reverseSignals.Value = value;
	}

	/// <summary>
	/// Allows adding to an existing position instead of waiting for a flat position.
	/// </summary>
	public bool AllowIncrease
	{
		get => _allowIncrease.Value;
		set => _allowIncrease.Value = value;
	}

	/// <summary>
	/// Candle type used to evaluate price movement.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="RenkoLevelEaStrategy"/>.
	/// </summary>
	public RenkoLevelEaStrategy()
	{
		_brickSize = Param(nameof(BrickSize), 3000)
			.SetGreaterThanZero()
			.SetDisplay("Brick Size", "Renko block size in price steps", "Renko Levels")
			
			.SetOptimize(10, 100, 10);

		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Volume for market orders", "Trading");

		_reverseSignals = Param(nameof(ReverseSignals), false)
			.SetDisplay("Reverse Signals", "Invert long and short actions", "Trading");

		_allowIncrease = Param(nameof(AllowIncrease), false)
			.SetDisplay("Allow Increase", "Allow adding to existing positions", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles for calculations", "Data");
	}

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

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

		// Reset previously calculated Renko levels.
		_upperLevel = 0m;
		_lowerLevel = 0m;
		_previousUpperLevel = null;
		_levelsInitialized = false;
	}

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

		// Subscribe to candle data that feeds the Renko level logic.
		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

		// Draw prices and trades if a chart is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}

		// Enable built-in protection features.
		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Trade only on completed candles.
		if (candle.State != CandleStates.Finished)
			return;

		// Ensure the strategy is ready to place trades.
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var priceStep = Security?.PriceStep ?? 1m;
		if (priceStep <= 0)
			priceStep = 1m;

		// Update Renko bounds with the latest closing price.
		if (!UpdateLevels(candle.ClosePrice, priceStep))
			return;

		// Skip the very first signal to mirror indicator warm-up.
		if (_previousUpperLevel == null)
		{
			_previousUpperLevel = _upperLevel;
			return;
		}

		// Proceed only if the Renko level actually changed.
		if (AreEqual(_previousUpperLevel.Value, _upperLevel, priceStep))
			return;

		var isUpMove = _upperLevel > _previousUpperLevel.Value;

		if (ReverseSignals)
			isUpMove = !isUpMove;

		if (isUpMove)
			HandleLongSignal();
		else
			HandleShortSignal();

		_previousUpperLevel = _upperLevel;
	}

	private bool UpdateLevels(decimal closePrice, decimal priceStep)
	{
		var stepCount = BrickSize;
		if (stepCount <= 0)
			return false;

		if (!_levelsInitialized)
		{
			CalculateBounds(closePrice, priceStep, stepCount, out var ceil, out var round, out var floor);
			_upperLevel = round;
			_lowerLevel = floor;
			_levelsInitialized = true;
			return true;
		}

		if (closePrice >= _lowerLevel && closePrice <= _upperLevel)
			return false;

		CalculateBounds(closePrice, priceStep, stepCount, out var newCeil, out var newRound, out var newFloor);

		if (closePrice < _lowerLevel)
		{
			if (AreEqual(newRound, _lowerLevel, priceStep))
				return false;

			_upperLevel = newCeil;
			_lowerLevel = newRound;
			return true;
		}

		if (closePrice > _upperLevel)
		{
			if (AreEqual(newRound, _upperLevel, priceStep))
				return false;

			_lowerLevel = newFloor;
			_upperLevel = newRound;
			return true;
		}

		return false;
	}

	private void CalculateBounds(decimal price, decimal priceStep, int stepCount, out decimal priceCeil, out decimal priceRound, out decimal priceFloor)
	{
		var normalizedStep = (decimal)stepCount;

		var ratio = price / priceStep / normalizedStep;
		var rounded = Math.Round(ratio, MidpointRounding.AwayFromZero);

		priceRound = (decimal)rounded * normalizedStep * priceStep;

		var ceilRatio = (priceRound + normalizedStep / 2m * priceStep) / priceStep / normalizedStep;
		var ceilCount = Math.Ceiling((double)ceilRatio);
		priceCeil = (decimal)ceilCount * normalizedStep * priceStep;

		var floorRatio = (priceRound - normalizedStep / 2m * priceStep) / priceStep / normalizedStep;
		var floorCount = Math.Floor((double)floorRatio);
		priceFloor = (decimal)floorCount * normalizedStep * priceStep;
	}

	private bool AreEqual(decimal left, decimal right, decimal priceStep)
	{
		var tolerance = priceStep / 2m;
		return Math.Abs(left - right) <= tolerance;
	}

	private void HandleLongSignal()
	{
		// Close the short side before flipping to long.
		if (Position < 0)
			ClosePosition();

		// Respect the increase toggle to avoid stacking positions unintentionally.
		if (!AllowIncrease && Position > 0)
			return;

		BuyMarket(OrderVolume);
	}

	private void HandleShortSignal()
	{
		// Close the long side before flipping to short.
		if (Position > 0)
			ClosePosition();

		// Respect the increase toggle for short accumulation.
		if (!AllowIncrease && Position < 0)
			return;

		SellMarket(OrderVolume);
	}
}