Heikin Ashi ROCパーセンタイル戦略
この戦略はローソク足をHeikin Ashiに変換し、SMAで終値を平滑化してそのRate of Changeを測定します。最近の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()