Открыть на GitHub

Стратегия Renko Level EA

Обзор

  • Конвертация советника MetaTrader Renko Level EA.mq5 на платформу StockSharp.
  • Поддерживает виртуальную решётку Ренко из верхнего и нижнего уровней, рассчитываемых по параметру BrickSize.
  • Работает с завершёнными свечами типа CandleType (по умолчанию таймфрейм 1 минута) и реагирует на смещение решётки.
  • Не использует жёстких стопов и тейк-профитов — выход осуществляется только по встречному сигналу.

Логика торговли

  1. Первая завершённая свеча округляет цену закрытия до сетки Ренко и задаёт исходные границы.
  2. Для каждой следующей свечи:
    • Если закрытие остаётся внутри диапазона, уровни не меняются.
    • Закрытие выше верхнего уровня переносит блок Ренко на следующий уровень вверх.
    • Закрытие ниже нижнего уровня переносит блок вниз.
  3. Изменение верхнего уровня трактуется как смена направления.
    • Рост верхней границы → сигнал на покупку (если ReverseSignals выключен).
    • Снижение верхней границы → сигнал на продажу.
  4. Флаги ReverseSignals и AllowIncrease повторяют параметры оригинального советника (реверс сигналов и разрешение на наращивание позиции).

Управление позициями

  • Перед открытием лонга стратегия закрывает текущий шорт; перед шортом — закрывает лонг.
  • При AllowIncrease = false новая позиция открывается только если нет активной позиции в ту же сторону.
  • При AllowIncrease = true разрешено добавлять объём OrderVolume, даже если позиция уже открыта.
  • Специальные стопы и цели отсутствуют, закрытие происходит по реверсу или вручную.
  • Метод StartProtection() запускается один раз для использования встроенных механизмов защиты.

Параметры

Имя Описание Значение по умолчанию Оптимизация
BrickSize Размер блока Ренко в шагах цены Security.PriceStep. Определяет необходимое движение для смещения уровней. 30 Да (10 → 100 шаг 10)
OrderVolume Объём рыночной заявки. 1 Нет
ReverseSignals Инвертирует действия «покупка/продажа». Полный аналог параметра Reverse. false Нет
AllowIncrease Разрешает добавление к уже открытой позиции. Повторяет флаг Increase. false Нет
CandleType Источник свечей для расчёта. По умолчанию свечи таймфрейма 1 минута, допускается любая поддерживаемая серия. TimeFrameCandleMessage(1m) Нет

Практические замечания

  • Значение BrickSize автоматически масштабируется согласно шагу цены инструмента, поэтому подходит для форекса, фьючерсов и криптовалют.
  • Решение принимается по цене закрытия свечи — внутрисессионные колебания учитываются лишь при формировании итогового закрытия.
  • Комбинация ReverseSignals и AllowIncrease позволяет быстро протестировать контртрендовые и пирамидальные сценарии.
  • Стратегия хорошо подходит для ситуаций, когда требуется следовать за пробоями Ренко без дополнительных фильтров.

Классификация

  • Режим: трендовая стратегия (пробой сетки Ренко).
  • Направление: лонг и шорт.
  • Сложность: средняя (собственная логика уровней при минимуме параметров).
  • Стопы: нет, выходы по обратному сигналу.
  • Таймфрейм: задаётся параметром CandleType.
  • Индикаторы: пользовательская проекция уровней Ренко.
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);
	}
}