Renko Live Charts Pimped Strategy
Эта стратегия строит кирпичи ренко и торгует при изменении их направления. Опционально размер кирпича рассчитывается по ATR, что позволяет адаптировать структуру ренко к волатильности рынка.
Подробности
- Условия входа:
- Лонг: бычий кирпич ренко после медвежьего.
- Шорт: медвежий кирпич ренко после бычьего.
- Лонг/Шорт: Оба.
- Условия выхода:
- Обратный сигнал.
- Стопы: Нет.
- Значения по умолчанию:
BoxSize= 10m.Volume= 1m.CalculateBestBoxSize= false.AtrPeriod= 24.AtrCandleType= 60m.UseAtrMa= true.AtrMaPeriod= 120.
- Фильтры:
- Категория: Trend Following
- Направление: Оба
- Индикаторы: Renko, ATR
- Стопы: Нет
- Сложность: Intermediate
- Таймфрейм: Renko
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: 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()