Стратегия Heikin Ashi ROC Percentile
Стратегия преобразует свечи в Heikin Ashi, сглаживает закрытие SMA и измеряет его Rate of Change. Перцентильные полосы недавних максимумов и минимумов ROC образуют уровни пробоя. Пересечение ROC выше нижней полосы открывает или переворачивает позицию в лонг, пересечение ниже верхней полосы переворачивает в шорт.
Детали
- Условия входа:
- Лонг: ROC пересекает снизу вверх нижнюю перцентильную линию.
- Шорт: ROC пересекает сверху вниз верхнюю перцентильную линию.
- Лонг/Шорт: Оба направления.
- Условия выхода: Противоположный сигнал или стоп.
- Стопы: Процентный стоп.
- Значения по умолчанию:
RocLength= 100StopLossPercent= 2mCandleType= TimeSpan.FromMinutes(1).TimeFrame()StartDate= new DateTimeOffset(2015, 3, 3, 0, 0, 0, TimeSpan.Zero)
- Фильтры:
- Категория: Пробой
- Направление: Оба
- Индикаторы: Heikin Ashi, RateOfChange, Highest, Lowest
- Стопы: Да
- Сложность: Базовая
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Heikin Ashi ROC Percentile strategy.
/// The Heikin Ashi close is smoothed with an SMA of RocLength and its Rate of Change over RocLength candles is measured. The upper
/// band is the highest and the lower band the lowest ROC of the previous RocLength candles. ROC crossing back above the lower band
/// buys and crossing back below the upper band sells, reversing an opposite position. Trading starts at StartDate and a percent
/// stop limits the loss.
/// </summary>
public class HeikinAshiRocPercentileStrategy : Strategy
{
private readonly StrategyParam<int> _rocLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DateTimeOffset> _startDate;
private readonly Queue<decimal> _haCloses = new();
private readonly Queue<decimal> _smoothed = new();
private decimal _haCloseSum;
private readonly Queue<decimal> _rocHistory = new();
private decimal? _haOpen;
private decimal? _haClose;
private decimal? _prevRoc;
private decimal? _prevUpper;
private decimal? _prevLower;
/// <summary>
/// Length of the smoothing SMA, the ROC and the band window.
/// </summary>
public int RocLength
{
get => _rocLength.Value;
set => _rocLength.Value = value;
}
/// <summary>
/// Stop loss percent from the entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Date trading starts from.
/// </summary>
public DateTimeOffset StartDate
{
get => _startDate.Value;
set => _startDate.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HeikinAshiRocPercentileStrategy()
{
_rocLength = Param(nameof(RocLength), 100)
.SetGreaterThanZero()
.SetDisplay("ROC Length", "Length of the smoothing SMA, the ROC and the band window", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percent from the entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_startDate = Param(nameof(StartDate), new DateTimeOffset(2015, 3, 3, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("Start Date", "Date trading starts from", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_haCloses.Clear();
_haCloseSum = 0m;
_smoothed.Clear();
_rocHistory.Clear();
_haOpen = null;
_haClose = null;
_prevRoc = null;
_prevUpper = null;
_prevLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m;
var haOpen = _haOpen is decimal prevHaOpen && _haClose is decimal prevHaClose
? (prevHaOpen + prevHaClose) / 2m
: (candle.OpenPrice + candle.ClosePrice) / 2m;
_haOpen = haOpen;
_haClose = haClose;
var length = RocLength;
// SMA of the Heikin Ashi close.
_haCloses.Enqueue(haClose);
_haCloseSum += haClose;
if (_haCloses.Count > length)
_haCloseSum -= _haCloses.Dequeue();
if (_haCloses.Count < length)
return;
var smoothed = _haCloseSum / length;
_smoothed.Enqueue(smoothed);
if (_smoothed.Count <= length)
return;
var past = _smoothed.Dequeue();
if (past == 0)
return;
var roc = (smoothed - past) / past * 100m;
decimal? upper = null;
decimal? lower = null;
if (_rocHistory.Count >= length)
{
upper = _rocHistory.Max();
lower = _rocHistory.Min();
}
var prevRoc = _prevRoc;
var prevUpper = _prevUpper;
var prevLower = _prevLower;
_rocHistory.Enqueue(roc);
while (_rocHistory.Count > length)
_rocHistory.Dequeue();
_prevRoc = roc;
_prevUpper = upper;
_prevLower = lower;
if (prevRoc is not decimal lastRoc || upper is not decimal up || lower is not decimal low || prevUpper is not decimal lastUp || prevLower is not decimal lastLow)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (candle.OpenTime < StartDate.UtcDateTime)
return;
if (lastRoc <= lastLow && roc > low && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (lastRoc >= lastUp && roc < up && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from collections import deque
from System import TimeSpan, Decimal, DateTimeOffset
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class heikin_ashi_roc_percentile_strategy(Strategy):
"""
Heikin Ashi ROC Percentile strategy.
The Heikin Ashi close is smoothed with an SMA of RocLength and its Rate of Change over RocLength candles is measured. The upper
band is the highest and the lower band the lowest ROC of the previous RocLength candles. ROC crossing back above the lower band
buys and crossing back below the upper band sells, reversing an opposite position. Trading starts at StartDate and a percent
stop limits the loss.
"""
def __init__(self):
super(heikin_ashi_roc_percentile_strategy, self).__init__()
self._roc_length = self.Param("RocLength", 100).SetGreaterThanZero().SetDisplay("ROC Length", "Length of the smoothing SMA, the ROC and the band window", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percent from the entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._start_date = self.Param("StartDate", DateTimeOffset(2015, 3, 3, 0, 0, 0, TimeSpan.Zero)).SetDisplay("Start Date", "Date trading starts from", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._ha_closes = deque()
self._ha_close_sum = 0.0
self._smoothed = deque()
self._roc_history = deque()
self._ha_open = None
self._ha_close = None
self._prev_roc = None
self._prev_upper = None
self._prev_lower = None
def OnReseted(self):
super(heikin_ashi_roc_percentile_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(heikin_ashi_roc_percentile_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
sl = float(self._stop_loss_percent.Value)
self.StartProtection(Unit(), Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(), useMarketOrders=True)
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
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
ha_close = (open_price + float(candle.HighPrice) + float(candle.LowPrice) + close) / 4.0
if self._ha_open is not None and self._ha_close is not None:
ha_open = (self._ha_open + self._ha_close) / 2.0
else:
ha_open = (open_price + close) / 2.0
self._ha_open = ha_open
self._ha_close = ha_close
length = self._roc_length.Value
# SMA of the Heikin Ashi close.
self._ha_closes.append(ha_close)
self._ha_close_sum += ha_close
if len(self._ha_closes) > length:
self._ha_close_sum -= self._ha_closes.popleft()
if len(self._ha_closes) < length:
return
smoothed = self._ha_close_sum / length
self._smoothed.append(smoothed)
if len(self._smoothed) <= length:
return
past = self._smoothed.popleft()
if past == 0:
return
roc = (smoothed - past) / past * 100.0
upper = None
lower = None
if len(self._roc_history) >= length:
upper = max(self._roc_history)
lower = min(self._roc_history)
prev_roc = self._prev_roc
prev_upper = self._prev_upper
prev_lower = self._prev_lower
self._roc_history.append(roc)
while len(self._roc_history) > length:
self._roc_history.popleft()
self._prev_roc = roc
self._prev_upper = upper
self._prev_lower = lower
if prev_roc is None or upper is None or lower is None or prev_upper is None or prev_lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if candle.OpenTime < self._start_date.Value.UtcDateTime:
return
if prev_roc <= prev_lower and roc > lower and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif prev_roc >= prev_upper and roc < upper and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return heikin_ashi_roc_percentile_strategy()