Heikin Ashi ROC 百分位策略
该策略将K线转换为Heikin Ashi,并用SMA平滑收盘价后计算其变化率(ROC)。最近ROC高低点的百分位区间形成突破带。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()