Renko Live Charts Pimped Strategy
This strategy builds renko bricks and trades on direction changes. It can optionally calculate the brick size from ATR values, allowing the renko structure to adapt to market volatility.
Details
- Entry Criteria:
- Long: bullish renko brick after a bearish brick.
- Short: bearish renko brick after a bullish brick.
- Long/Short: Both.
- Exit Criteria:
- Reverse signal.
- Stops: No.
- Default Values:
BoxSize= 10m.Volume= 1m.CalculateBestBoxSize= false.AtrPeriod= 24.AtrCandleType= 60m.UseAtrMa= true.AtrMaPeriod= 120.
- Filters:
- Category: Trend Following
- Direction: Both
- Indicators: Renko, ATR
- Stops: No
- Complexity: Intermediate
- Timeframe: Renko
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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 strategy with optional ATR-based brick size.
/// Simulates renko bricks from regular candles and trades on direction changes.
/// When ATR mode is enabled, the brick size is derived from ATR of a larger timeframe.
/// </summary>
public class RenkoLiveChartsPimpedStrategy : Strategy
{
private readonly StrategyParam<decimal> _boxSize;
private readonly StrategyParam<bool> _calculateBestBoxSize;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<bool> _useAtrMa;
private readonly StrategyParam<int> _atrMaPeriod;
private readonly StrategyParam<DataType> _candleType;
private AverageTrueRange _atr;
private SimpleMovingAverage _atrMa;
private decimal _renkoPrice;
private int _prevDirection;
private decimal _dynamicBoxSize;
/// <summary>
/// Renko brick size in price units.
/// </summary>
public decimal BoxSize { get => _boxSize.Value; set => _boxSize.Value = value; }
/// <summary>
/// Calculate brick size from ATR.
/// </summary>
public bool CalculateBestBoxSize { get => _calculateBestBoxSize.Value; set => _calculateBestBoxSize.Value = value; }
/// <summary>
/// ATR calculation period.
/// </summary>
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
/// <summary>
/// Apply moving average to ATR.
/// </summary>
public bool UseAtrMa { get => _useAtrMa.Value; set => _useAtrMa.Value = value; }
/// <summary>
/// Moving average length for ATR.
/// </summary>
public int AtrMaPeriod { get => _atrMaPeriod.Value; set => _atrMaPeriod.Value = value; }
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initializes <see cref="RenkoLiveChartsPimpedStrategy"/>.
/// </summary>
public RenkoLiveChartsPimpedStrategy()
{
_boxSize = Param(nameof(BoxSize), 1000m)
.SetGreaterThanZero()
.SetDisplay("Box Size", "Renko brick size", "Renko");
_calculateBestBoxSize = Param(nameof(CalculateBestBoxSize), false)
.SetDisplay("Use ATR Box", "Calculate brick size from ATR", "Renko");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR calculation period", "Renko");
_useAtrMa = Param(nameof(UseAtrMa), false)
.SetDisplay("Smooth ATR", "Apply moving average on ATR", "Renko");
_atrMaPeriod = Param(nameof(AtrMaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ATR MA Period", "Moving average length for ATR", "Renko");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_renkoPrice = 0m;
_prevDirection = 0;
_dynamicBoxSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_dynamicBoxSize = BoxSize;
if (CalculateBestBoxSize)
{
_atr = new AverageTrueRange { Length = AtrPeriod };
_atrMa = new SimpleMovingAverage { Length = AtrMaPeriod };
}
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
StartProtection(null, null);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// update dynamic box size from ATR if enabled
if (CalculateBestBoxSize && _atr != null)
{
var atrResult = _atr.Process(candle);
if (atrResult.IsFormed)
{
var atrVal = atrResult.ToDecimal();
if (UseAtrMa && _atrMa != null)
{
var maResult = _atrMa.Process(atrVal, candle.OpenTime, true);
if (maResult.IsFormed)
_dynamicBoxSize = maResult.ToDecimal();
}
else
{
_dynamicBoxSize = atrVal;
}
}
}
var close = candle.ClosePrice;
var size = _dynamicBoxSize;
if (size <= 0)
return;
if (_renkoPrice == 0m)
{
_renkoPrice = close;
return;
}
var diff = close - _renkoPrice;
if (Math.Abs(diff) < size)
return;
var direction = Math.Sign(diff);
_renkoPrice += direction * size;
// trade on direction change
if (_prevDirection != 0 && direction != _prevDirection)
{
if (direction > 0 && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (direction < 0 && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
else if (_prevDirection == 0)
{
// first brick - enter in its direction
if (direction > 0)
BuyMarket();
else if (direction < 0)
SellMarket();
}
_prevDirection = direction;
}
}
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.Indicators import AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class renko_live_charts_pimped_strategy(Strategy):
def __init__(self):
super(renko_live_charts_pimped_strategy, self).__init__()
self._box_size = self.Param("BoxSize", 1000.0) \
.SetGreaterThanZero() \
.SetDisplay("Box Size", "Renko brick size", "Renko")
self._calculate_best_box_size = self.Param("CalculateBestBoxSize", False) \
.SetDisplay("Use ATR Box", "Calculate brick size from ATR", "Renko")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "ATR calculation period", "Renko")
self._use_atr_ma = self.Param("UseAtrMa", False) \
.SetDisplay("Smooth ATR", "Apply moving average on ATR", "Renko")
self._atr_ma_period = self.Param("AtrMaPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("ATR MA Period", "Moving average length for ATR", "Renko")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._atr = None
self._atr_ma = None
self._renko_price = 0.0
self._prev_direction = 0
self._dynamic_box_size = 0.0
@property
def box_size(self):
return self._box_size.Value
@property
def calculate_best_box_size(self):
return self._calculate_best_box_size.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def use_atr_ma(self):
return self._use_atr_ma.Value
@property
def atr_ma_period(self):
return self._atr_ma_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(renko_live_charts_pimped_strategy, self).OnReseted()
self._renko_price = 0.0
self._prev_direction = 0
self._dynamic_box_size = 0.0
def OnStarted2(self, time):
super(renko_live_charts_pimped_strategy, self).OnStarted2(time)
self._dynamic_box_size = float(self.box_size)
if self.calculate_best_box_size:
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
self._atr_ma = SimpleMovingAverage()
self._atr_ma.Length = self.atr_ma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
self.StartProtection(None, None)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
# update dynamic box size from ATR if enabled
if self.calculate_best_box_size and self._atr is not None:
atr_result = self._atr.Process(candle)
if atr_result.IsFormed:
atr_val = float(atr_result)
if self.use_atr_ma and self._atr_ma is not None:
ma_result = self._atr_ma.Process(atr_val, candle.OpenTime, True)
if ma_result.IsFormed:
self._dynamic_box_size = float(ma_result)
else:
self._dynamic_box_size = atr_val
close = float(candle.ClosePrice)
size = self._dynamic_box_size
if size <= 0:
return
if self._renko_price == 0.0:
self._renko_price = close
return
diff = close - self._renko_price
if abs(diff) < size:
return
direction = 1 if diff > 0 else -1
self._renko_price += direction * size
# trade on direction change
if self._prev_direction != 0 and direction != self._prev_direction:
if direction > 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif direction < 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self._prev_direction == 0:
if direction > 0:
self.BuyMarket()
elif direction < 0:
self.SellMarket()
self._prev_direction = direction
def CreateClone(self):
return renko_live_charts_pimped_strategy()