Adaptive Renko Strategy
This strategy builds an adaptive Renko grid where the brick size follows market volatility measured by the Average True Range (ATR) indicator. A trade is executed whenever price travels a full brick in either direction.
Logic
- ATR is calculated over a configurable
VolatilityPeriod. - The brick size equals
ATR * Multiplierbut cannot be less thanMinBrickSize. - When price rises above the previous brick by at least one brick size, the strategy buys (closing short positions if needed).
- When price falls below the previous brick by at least one brick size, the strategy sells (closing long positions if needed).
Parameters
Volume– order volume.VolatilityPeriod– period used for ATR.Multiplier– coefficient applied to ATR.MinBrickSize– minimal allowed brick size in price units.CandleType– timeframe for ATR calculation.
Timeframe
- Default: 4 hours.
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 trades on adaptive renko movements based on ATR volatility.
/// </summary>
public class AdaptiveRenkoStrategy : Strategy
{
private readonly StrategyParam<int> _volatilityPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<decimal> _minBrick;
private readonly StrategyParam<DataType> _candleType;
private readonly AverageTrueRange _atr = new();
private decimal _lastBrickPrice;
private bool _hasBrick;
public int VolatilityPeriod
{
get => _volatilityPeriod.Value;
set => _volatilityPeriod.Value = value;
}
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
public decimal MinBrickSize
{
get => _minBrick.Value;
set => _minBrick.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public AdaptiveRenkoStrategy()
{
_volatilityPeriod = Param(nameof(VolatilityPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Volatility Period", "ATR calculation period", "Indicator")
.SetOptimize(5, 20, 1);
_multiplier = Param(nameof(Multiplier), 1m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "ATR multiplier", "Indicator")
.SetOptimize(0.5m, 2m, 0.5m);
_minBrick = Param(nameof(MinBrickSize), 2m)
.SetGreaterThanZero()
.SetDisplay("Min Brick", "Minimum brick size", "Indicator")
.SetOptimize(1m, 5m, 1m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for ATR calculation", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atr.Length = VolatilityPeriod;
_atr.Reset();
_lastBrickPrice = 0m;
_hasBrick = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr.Length = VolatilityPeriod;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var brick = Math.Max(atr * Multiplier, MinBrickSize);
if (!_hasBrick)
{
_lastBrickPrice = candle.ClosePrice;
_hasBrick = true;
return;
}
var diff = candle.ClosePrice - _lastBrickPrice;
if (diff >= brick)
{
if (Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
_lastBrickPrice = candle.ClosePrice;
}
else if (diff <= -brick)
{
if (Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_lastBrickPrice = candle.ClosePrice;
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class adaptive_renko_strategy(Strategy):
def __init__(self):
super(adaptive_renko_strategy, self).__init__()
self._volatility_period = self.Param("VolatilityPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Volatility Period", "ATR calculation period", "Indicator") \
.SetOptimize(5, 20, 1)
self._multiplier = self.Param("Multiplier", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Multiplier", "ATR multiplier", "Indicator") \
.SetOptimize(0.5, 2.0, 0.5)
self._min_brick = self.Param("MinBrickSize", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Min Brick", "Minimum brick size", "Indicator") \
.SetOptimize(1.0, 5.0, 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for ATR calculation", "General")
self._last_brick_price = 0.0
self._has_brick = False
@property
def volatility_period(self):
return self._volatility_period.Value
@property
def multiplier(self):
return self._multiplier.Value
@property
def min_brick_size(self):
return self._min_brick.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adaptive_renko_strategy, self).OnReseted()
self._last_brick_price = 0.0
self._has_brick = False
def OnStarted2(self, time):
super(adaptive_renko_strategy, self).OnStarted2(time)
atr = AverageTrueRange()
atr.Length = self.volatility_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
self.StartProtection(None, None)
def process_candle(self, candle, atr):
if candle.State != CandleStates.Finished:
return
atr_val = float(atr)
brick = max(atr_val * float(self.multiplier), float(self.min_brick_size))
if not self._has_brick:
self._last_brick_price = float(candle.ClosePrice)
self._has_brick = True
return
diff = float(candle.ClosePrice) - self._last_brick_price
if diff >= brick:
if self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._last_brick_price = float(candle.ClosePrice)
elif diff <= -brick:
if self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._last_brick_price = float(candle.ClosePrice)
def CreateClone(self):
return adaptive_renko_strategy()