EMA Trend Heikin Ashi Entry 策略
该策略在 Heikin Ashi 蜡烛图上应用布林带,并结合高时间框架的 EMA 趋势过滤器。当高时间框架的快速 EMA 高于慢速 EMA 时,若连续多个 Heikin Ashi 熊蜡烛触及下轨后出现一根上涨蜡烛收于下轨之上则做多;反之做空。
入场后,设置与风险相等的第一目标位,并根据上一根蜡烛的极值移动止损。
细节
- 入场条件:
- 多头:至少两根熊 HA 蜡烛触及下轨,之后一根牛蜡烛收于其上且高时间框架快速 EMA > 慢速 EMA
- 空头:至少两根牛 HA 蜡烛触及上轨,之后一根熊蜡烛收于其下且高时间框架快速 EMA < 慢速 EMA
- 多空:双向
- 出场条件:
- 多头:第一目标 1R,之后止损跟随前一根蜡烛低点
- 空头:第一目标 1R,之后止损跟随前一根蜡烛高点
- 止损:上一根蜡烛的低/高点
- 默认值:
BollingerPeriod= 20BollingerDeviation= 2mFastEmaPeriod= 9SlowEmaPeriod= 21CandleType= TimeSpan.FromMinutes(5).TimeFrame()HigherTimeframe= TimeSpan.FromMinutes(180).TimeFrame()
- 筛选:
- 类别:回调
- 方向:双向
- 指标:Bollinger Bands, Heikin Ashi, EMA
- 止损:是
- 复杂度:中等
- 时间框架:中期
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
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>
/// EMA trend Heikin Ashi entry strategy.
/// Bollinger Bands are calculated on Heikin Ashi closes. After at least two bearish Heikin Ashi candles touching the lower band,
/// a bullish candle closing above the lower band goes long when the fast EMA is above the slow EMA on the higher timeframe; the
/// short side mirrors it at the upper band. The stop is the signal candle's low (high). Half of the position is taken at 1R,
/// after which the stop trails the previous candle's low (high).
/// </summary>
public class EmaTrendHeikinAshiEntryStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherTimeframe;
private BollingerBands _bollinger;
private decimal? _haOpen;
private decimal? _haClose;
private int _bearTouchCount;
private int _bullTouchCount;
private decimal? _higherFast;
private decimal? _higherSlow;
private decimal _stopPrice;
private decimal _targetPrice;
private bool _targetDone;
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands deviation.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Fast EMA period on the higher timeframe.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Slow EMA period on the higher timeframe.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe for the EMA trend filter.
/// </summary>
public DataType HigherTimeframe
{
get => _higherTimeframe.Value;
set => _higherTimeframe.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EmaTrendHeikinAshiEntryStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");
_bollingerDeviation = Param(nameof(BollingerDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Bollinger Bands deviation", "Indicators");
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period on the higher timeframe", "Trend");
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period on the higher timeframe", "Trend");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_higherTimeframe = Param(nameof(HigherTimeframe), TimeSpan.FromMinutes(180).TimeFrame())
.SetDisplay("Higher Timeframe", "Timeframe of the EMA trend filter", "Trend");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, HigherTimeframe)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_haOpen = null;
_haClose = null;
_bearTouchCount = 0;
_bullTouchCount = 0;
_higherFast = null;
_higherSlow = null;
_stopPrice = 0m;
_targetPrice = 0m;
_targetDone = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerDeviation };
var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
SubscribeCandles(HigherTimeframe)
.Bind(fastEma, slowEma, ProcessHigherCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessHigherCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
_higherFast = fast;
_higherSlow = slow;
}
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 po && _haClose is decimal pc ? (po + pc) / 2m : (candle.OpenPrice + candle.ClosePrice) / 2m;
var haHigh = Math.Max(candle.HighPrice, Math.Max(haOpen, haClose));
var haLow = Math.Min(candle.LowPrice, Math.Min(haOpen, haClose));
_haOpen = haOpen;
_haClose = haClose;
var bandsValue = _bollinger.Process(new DecimalIndicatorValue(_bollinger, haClose, candle.OpenTime) { IsFinal = true });
if (!_bollinger.IsFormed || bandsValue is not IBollingerBandsValue { UpBand: decimal upper, LowBand: decimal lower })
return;
var bearTouches = _bearTouchCount;
var bullTouches = _bullTouchCount;
var bullish = haClose > haOpen;
var bearish = haClose < haOpen;
_bearTouchCount = bearish && haLow <= lower ? _bearTouchCount + 1 : 0;
_bullTouchCount = bullish && haHigh >= upper ? _bullTouchCount + 1 : 0;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0)
{
ManagePosition(candle);
return;
}
if (_higherFast is not decimal fast || _higherSlow is not decimal slow)
return;
var close = candle.ClosePrice;
if (bearTouches >= 2 && bullish && haClose > lower && fast > slow)
{
var risk = close - candle.LowPrice;
if (risk <= 0m)
return;
_stopPrice = candle.LowPrice;
_targetPrice = close + risk;
_targetDone = false;
BuyMarket(Volume);
}
else if (bullTouches >= 2 && bearish && haClose < upper && fast < slow)
{
var risk = candle.HighPrice - close;
if (risk <= 0m)
return;
_stopPrice = candle.HighPrice;
_targetPrice = close - risk;
_targetDone = false;
SellMarket(Volume);
}
}
private decimal HalfVolume()
{
var step = Security?.VolumeStep ?? 0m;
var half = Math.Abs(Position) / 2m;
if (step > 0m)
half = Math.Floor(half / step) * step;
return half;
}
private void ManagePosition(ICandleMessage candle)
{
if (Position > 0)
{
if (candle.LowPrice <= _stopPrice)
{
SellMarket(Position);
return;
}
if (!_targetDone && candle.HighPrice >= _targetPrice)
{
_targetDone = true;
var half = HalfVolume();
SellMarket(half > 0m ? half : Position);
}
if (_targetDone)
_stopPrice = Math.Max(_stopPrice, candle.LowPrice);
}
else
{
if (candle.HighPrice >= _stopPrice)
{
BuyMarket(-Position);
return;
}
if (!_targetDone && candle.LowPrice <= _targetPrice)
{
_targetDone = true;
var half = HalfVolume();
BuyMarket(half > 0m ? half : -Position);
}
if (_targetDone)
_stopPrice = Math.Min(_stopPrice, candle.HighPrice);
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands, ExponentialMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class ema_trend_heikin_ashi_entry_strategy(Strategy):
"""
EMA trend Heikin Ashi entry strategy.
Bollinger Bands are calculated on Heikin Ashi closes. After at least two bearish Heikin Ashi candles touching the lower band,
a bullish candle closing above the lower band goes long when the fast EMA is above the slow EMA on the higher timeframe; the
short side mirrors it at the upper band. The stop is the signal candle's low (high). Half of the position is taken at 1R,
after which the stop trails the previous candle's low (high).
"""
def __init__(self):
super(ema_trend_heikin_ashi_entry_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0).SetGreaterThanZero().SetDisplay("Bollinger Deviation", "Bollinger Bands deviation", "Indicators")
self._fast_ema_period = self.Param("FastEmaPeriod", 9).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period on the higher timeframe", "Trend")
self._slow_ema_period = self.Param("SlowEmaPeriod", 21).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period on the higher timeframe", "Trend")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._higher_timeframe = self.Param("HigherTimeframe", DataType.TimeFrame(TimeSpan.FromMinutes(180))).SetDisplay("Higher Timeframe", "Timeframe of the EMA trend filter", "Trend")
self._bollinger = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, self._higher_timeframe.Value)]
def _reset_state(self):
self._ha_open = None
self._ha_close = None
self._bear_touch_count = 0
self._bull_touch_count = 0
self._higher_fast = None
self._higher_slow = None
self._stop_price = 0.0
self._target_price = 0.0
self._target_done = False
def OnReseted(self):
super(ema_trend_heikin_ashi_entry_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ema_trend_heikin_ashi_entry_strategy, self).OnStarted2(time)
self._reset_state()
self._bollinger = BollingerBands()
self._bollinger.Length = self._bollinger_period.Value
self._bollinger.Width = Decimal(float(self._bollinger_deviation.Value))
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_period.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ema_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
self.SubscribeCandles(self._higher_timeframe.Value).Bind(fast_ema, slow_ema, self._process_higher_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_higher_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
self._higher_fast = float(fast_value)
self._higher_slow = float(slow_value)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
open_price = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
ha_close = (open_price + high + low + 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
ha_high = max(high, ha_open, ha_close)
ha_low = min(low, ha_open, ha_close)
self._ha_open = ha_open
self._ha_close = ha_close
bands_input = DecimalIndicatorValue(self._bollinger, Decimal(ha_close), candle.OpenTime)
bands_input.IsFinal = True
bands_value = self._bollinger.Process(bands_input)
if not self._bollinger.IsFormed or bands_value.UpBand is None or bands_value.LowBand is None:
return
upper = float(bands_value.UpBand)
lower = float(bands_value.LowBand)
bear_touches = self._bear_touch_count
bull_touches = self._bull_touch_count
bullish = ha_close > ha_open
bearish = ha_close < ha_open
self._bear_touch_count = self._bear_touch_count + 1 if bearish and ha_low <= lower else 0
self._bull_touch_count = self._bull_touch_count + 1 if bullish and ha_high >= upper else 0
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position != 0:
self._manage_position(high, low)
return
if self._higher_fast is None or self._higher_slow is None:
return
fast = self._higher_fast
slow = self._higher_slow
if bear_touches >= 2 and bullish and ha_close > lower and fast > slow:
risk = close - low
if risk <= 0:
return
self._stop_price = low
self._target_price = close + risk
self._target_done = False
self.BuyMarket(self.Volume)
elif bull_touches >= 2 and bearish and ha_close < upper and fast < slow:
risk = high - close
if risk <= 0:
return
self._stop_price = high
self._target_price = close - risk
self._target_done = False
self.SellMarket(self.Volume)
def _half_volume(self):
step = float(self.Security.VolumeStep) if self.Security is not None and self.Security.VolumeStep is not None else 0.0
half = abs(float(self.Position)) / 2.0
if step > 0:
half = math.floor(half / step + 1e-9) * step
return half
def _manage_position(self, high, low):
if self.Position > 0:
if low <= self._stop_price:
self.SellMarket(self.Position)
return
if not self._target_done and high >= self._target_price:
self._target_done = True
half = self._half_volume()
self.SellMarket(Decimal(half) if half > 0 else self.Position)
if self._target_done:
self._stop_price = max(self._stop_price, low)
else:
if high >= self._stop_price:
self.BuyMarket(-self.Position)
return
if not self._target_done and low <= self._target_price:
self._target_done = True
half = self._half_volume()
self.BuyMarket(Decimal(half) if half > 0 else -self.Position)
if self._target_done:
self._stop_price = min(self._stop_price, high)
def CreateClone(self):
return ema_trend_heikin_ashi_entry_strategy()