Renko Level Strategy
Overview
Renko Level Strategy is a faithful conversion of the MetaTrader 5 expert advisor “Renko Level EA”. It reconstructs the indicator-driven logic inside StockSharp and trades whenever the rounded Renko level jumps to a new block. The strategy interprets each level shift as a breakout of the synthetic Renko brick and enters in the breakout direction or in the opposite direction when the reverse mode is enabled.
The system uses regular time-based candles (1 minute by default) only as a data feed. Candle closes are rounded to a configurable block size that emulates Renko bricks without the need for Renko data subscriptions. Every time the rounded block changes, the strategy closes any opposite exposure and opens a new position aligned with the detected move.
Trading Logic
- Initialization
- Detect the pip size from the instrument (
PriceStep).
- Convert the
Block Size parameter from pips to price units (3-digit and 5-digit instruments automatically multiply the pip value by 10).
- Round the first finished candle close to the nearest block to create the initial upper and lower Renko levels.
- Level Maintenance
- On each finished candle the close price is rounded to the nearest block size.
- When the close stays within the current block, the stored levels remain unchanged.
- When the close breaks below the lower bound, the algorithm rounds the price down and shifts the block lower (
lower = round, upper = round + size).
- When the close breaks above the upper bound, the block is shifted higher (
upper = round, lower = round - size).
- Signal Generation
- A rising upper level indicates a bullish breakout of the Renko block. A falling upper level indicates a bearish breakout.
- If
Reverse is disabled the strategy buys on bullish shifts and sells on bearish shifts. When Reverse is enabled the actions are swapped.
- When a signal is triggered, existing exposure in the opposite direction is flattened automatically (buy order closes shorts, sell order closes longs). If
Allow Increase is disabled, the strategy refuses to add size on top of an already open position in the same direction.
- Order Execution
- Orders are sent with the strategy
Volume setting. When reversing an existing position the order size equals the absolute position plus the configured volume so that the flip happens immediately.
StartProtection() is called during start-up so risk protections configured in Designer or via composition are active.
Parameters
| Parameter |
Description |
Default |
Block Size |
Renko block size in pips. The strategy multiplies it by the pip value of the instrument to obtain the actual price increment. Larger values reduce trade frequency. |
30 |
Reverse |
When true, invert all trading signals (buy on bearish shift, sell on bullish shift). |
false |
Allow Increase |
When true, permits pyramiding by adding additional orders in the same direction on every signal. When false, a new order is only sent if the net position is flat after closing the opposite side. |
false |
Candle Type |
Source candle data. Any supported DataType can be used; by default the strategy subscribes to 1-minute candles. |
TimeFrame(1m) |
Volume (inherited) |
Order size used when sending market orders. Set this property on the strategy instance before starting it. |
Depends on portfolio |
Usage Notes
- Choose the block size according to the instrument volatility. For major FX pairs 30–50 pips emulate the behaviour of the original EA. On indices or crypto assets use larger block sizes.
- The strategy works with any candle feed (tick, time frame, range) as long as the candle close reflects the desired price sampling. For a pure Renko feed you can switch the candle type to a Renko data series.
- Enable
Reverse to transform the breakout system into a mean-reversion system that fades every Renko level change.
Allow Increase can be switched on to mimic the original EA’s “Increase” parameter that adds contracts on every new level in the same direction.
- Risk and money management (stop-loss, take-profit, drawdown control) can be configured through StockSharp protections or wrapper strategies. The sample keeps the logic identical to the MT5 expert and does not impose fixed exits beyond level flips.
Data Requirements
- Historical and real-time candle data for the configured
Candle Type.
- The instrument metadata must provide
PriceStep and Decimals so that pip conversion works correctly. When these values are not available, the strategy falls back to a 0.0001 default step.
Suggested Workflow
- Add the strategy to Designer or create it programmatically through StockSharp API.
- Set
Security, Portfolio, Volume, and optionally adjust the parameters listed above.
- Start the strategy. It will wait for the first finished candle to establish the initial Renko block.
- Monitor the built-in trades chart or subscribe to logs to verify that orders are triggered only when the rounded level changes.
This documentation mirrors the behaviour of the original Renko Level EA while explaining how it is implemented inside StockSharp so that you can further customise or extend it.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Renko level breakout strategy. Emulates Renko bricks on time-based candles
/// and trades when the rounded level shifts up or down.
/// </summary>
public class RenkoLevelStrategy : Strategy
{
private readonly StrategyParam<int> _blockSize;
private readonly StrategyParam<DataType> _candleType;
private decimal _upperLevel;
private decimal _lowerLevel;
private bool _hasLevels;
public int BlockSize
{
get => _blockSize.Value;
set => _blockSize.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public RenkoLevelStrategy()
{
_blockSize = Param(nameof(BlockSize), 5000)
.SetGreaterThanZero()
.SetDisplay("Block Size", "Renko block size in price steps", "Renko");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_upperLevel = 0m;
_lowerLevel = 0m;
_hasLevels = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var brickSize = GetBrickSize();
if (brickSize <= 0m)
return;
var close = candle.ClosePrice;
if (!_hasLevels)
{
InitializeLevels(close, brickSize);
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var previousUpper = _upperLevel;
var moved = false;
if (close < _lowerLevel)
{
var (round, _, ceil) = CalculateLevels(close, brickSize);
if (Math.Abs(round - _lowerLevel) > brickSize * 0.01m)
{
_lowerLevel = round;
_upperLevel = ceil;
moved = true;
}
}
else if (close > _upperLevel)
{
var (round, floor, _) = CalculateLevels(close, brickSize);
if (Math.Abs(round - _upperLevel) > brickSize * 0.01m)
{
_lowerLevel = floor;
_upperLevel = round;
moved = true;
}
}
if (!moved)
return;
if (_upperLevel > previousUpper && Position <= 0)
BuyMarket();
else if (_upperLevel < previousUpper && Position >= 0)
SellMarket();
}
private void InitializeLevels(decimal price, decimal brickSize)
{
var (round, floor, _) = CalculateLevels(price, brickSize);
_upperLevel = round;
_lowerLevel = floor;
_hasLevels = true;
}
private (decimal round, decimal floor, decimal ceil) CalculateLevels(decimal price, decimal brickSize)
{
var ratio = price / brickSize;
var rounded = Math.Round(ratio, 0, MidpointRounding.AwayFromZero);
var priceRound = rounded * brickSize;
var priceFloor = priceRound - brickSize;
var priceCeil = priceRound + brickSize;
return (priceRound, priceFloor, priceCeil);
}
private decimal GetBrickSize()
{
var step = Security?.PriceStep ?? 0.01m;
return step * BlockSize;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class renko_level_strategy(Strategy):
def __init__(self):
super(renko_level_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candles", "General")
self._block_size = self.Param("BlockSize", 5000) \
.SetDisplay("Block Size", "Renko block size in price steps", "Renko")
self._upper_level = 0.0
self._lower_level = 0.0
self._has_levels = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def BlockSize(self):
return self._block_size.Value
def OnReseted(self):
super(renko_level_strategy, self).OnReseted()
self._upper_level = 0.0
self._lower_level = 0.0
self._has_levels = False
def OnStarted2(self, time):
super(renko_level_strategy, self).OnStarted2(time)
self._upper_level = 0.0
self._lower_level = 0.0
self._has_levels = False
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
brick_size = self._get_brick_size()
if brick_size <= 0:
return
close = float(candle.ClosePrice)
if not self._has_levels:
self._initialize_levels(close, brick_size)
return
previous_upper = self._upper_level
moved = False
if close < self._lower_level:
rnd, _, ceil = self._calculate_levels(close, brick_size)
if abs(rnd - self._lower_level) > brick_size * 0.01:
self._lower_level = rnd
self._upper_level = ceil
moved = True
elif close > self._upper_level:
rnd, floor, _ = self._calculate_levels(close, brick_size)
if abs(rnd - self._upper_level) > brick_size * 0.01:
self._lower_level = floor
self._upper_level = rnd
moved = True
if not moved:
return
if self._upper_level > previous_upper and self.Position <= 0:
self.BuyMarket()
elif self._upper_level < previous_upper and self.Position >= 0:
self.SellMarket()
def _initialize_levels(self, price, brick_size):
rnd, floor, _ = self._calculate_levels(price, brick_size)
self._upper_level = rnd
self._lower_level = floor
self._has_levels = True
def _calculate_levels(self, price, brick_size):
ratio = price / brick_size
rounded = round(ratio)
price_round = rounded * brick_size
price_floor = price_round - brick_size
price_ceil = price_round + brick_size
return (price_round, price_floor, price_ceil)
def _get_brick_size(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.01
return step * self.BlockSize
def CreateClone(self):
return renko_level_strategy()