5 EMA 策略
5 EMA 策略会记录一根完全收盘在 5 周期 EMA 之下或之上的K线。如果在三根K线内价格突破该信号K线的极值且不在禁止交易的时间窗口内,策略按突破方向入场。目标基于用户设定的风险收益比,并且可以在指定时间强制平仓。
详情
- 入场条件:
- K线收盘和最高价低于 EMA → 标记做多;在三根K线内突破信号最高价时买入。
- K线收盘和最低价高于 EMA → 标记做空;在三根K线内跌破信号最低价时卖出。
- 方向: 多/空。
- 出场条件:
- 止损设在信号K线的相反极值。
- 目标为
TargetRR× 风险。 - 可选按时间退出 (
ExitHour,ExitMinute)。
- 止损: 有。
- 默认参数:
EmaLength= 5TargetRR= 3.0ExitHour= 15,ExitMinute= 30BlockStartHour= 15,BlockStartMinute= 0BlockEndHour= 15,BlockEndMinute= 30
- 过滤:
- 分类: 突破
- 方向: 多/空
- 指标: EMA
- 止损: 有
- 复杂度: 低
- 周期: 5 分钟
- 季节性: 无
- 神经网络: 无
- 背离: 无
- 风险等级: 中
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>
/// 5 EMA strategy.
/// A candle whose close and high are below the EMA marks a long setup, one whose close and low are above it marks a short setup.
/// If price breaks the signal candle's high (long) or low (short) within the next three candles and outside the block window, the
/// strategy enters in that direction with the stop at the signal candle's opposite extreme and the target at TargetRR times the risk.
/// Open positions are closed at ExitHour:ExitMinute.
/// </summary>
public class FiveEmaStrategy : Strategy
{
private const int _signalBars = 3;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _targetRR;
private readonly StrategyParam<int> _exitHour;
private readonly StrategyParam<int> _exitMinute;
private readonly StrategyParam<int> _blockStartHour;
private readonly StrategyParam<int> _blockStartMinute;
private readonly StrategyParam<int> _blockEndHour;
private readonly StrategyParam<int> _blockEndMinute;
private readonly StrategyParam<DataType> _candleType;
private decimal? _signalHigh;
private decimal? _signalLow;
private bool _signalIsLong;
private int _barsSinceSignal;
private decimal? _stopPrice;
private decimal? _targetPrice;
/// <summary>
/// EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Reward to risk ratio of the target.
/// </summary>
public decimal TargetRR
{
get => _targetRR.Value;
set => _targetRR.Value = value;
}
/// <summary>
/// Hour of the forced exit.
/// </summary>
public int ExitHour
{
get => _exitHour.Value;
set => _exitHour.Value = value;
}
/// <summary>
/// Minute of the forced exit.
/// </summary>
public int ExitMinute
{
get => _exitMinute.Value;
set => _exitMinute.Value = value;
}
/// <summary>
/// Hour the entry block starts.
/// </summary>
public int BlockStartHour
{
get => _blockStartHour.Value;
set => _blockStartHour.Value = value;
}
/// <summary>
/// Minute the entry block starts.
/// </summary>
public int BlockStartMinute
{
get => _blockStartMinute.Value;
set => _blockStartMinute.Value = value;
}
/// <summary>
/// Hour the entry block ends.
/// </summary>
public int BlockEndHour
{
get => _blockEndHour.Value;
set => _blockEndHour.Value = value;
}
/// <summary>
/// Minute the entry block ends.
/// </summary>
public int BlockEndMinute
{
get => _blockEndMinute.Value;
set => _blockEndMinute.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FiveEmaStrategy()
{
_emaLength = Param(nameof(EmaLength), 5)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period", "EMA");
_targetRR = Param(nameof(TargetRR), 3.0m)
.SetGreaterThanZero()
.SetDisplay("Target R:R", "Reward to risk ratio of the target", "Risk");
_exitHour = Param(nameof(ExitHour), 15)
.SetRange(0, 23)
.SetDisplay("Exit Hour", "Hour of the forced exit", "Time");
_exitMinute = Param(nameof(ExitMinute), 30)
.SetRange(0, 59)
.SetDisplay("Exit Minute", "Minute of the forced exit", "Time");
_blockStartHour = Param(nameof(BlockStartHour), 15)
.SetRange(0, 23)
.SetDisplay("Block Start Hour", "Hour the entry block starts", "Time");
_blockStartMinute = Param(nameof(BlockStartMinute), 0)
.SetRange(0, 59)
.SetDisplay("Block Start Minute", "Minute the entry block starts", "Time");
_blockEndHour = Param(nameof(BlockEndHour), 15)
.SetRange(0, 23)
.SetDisplay("Block End Hour", "Hour the entry block ends", "Time");
_blockEndMinute = Param(nameof(BlockEndMinute), 30)
.SetRange(0, 59)
.SetDisplay("Block End Minute", "Minute the entry block ends", "Time");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ResetState()
{
_signalHigh = null;
_signalLow = null;
_signalIsLong = false;
_barsSinceSignal = 0;
_stopPrice = null;
_targetPrice = null;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!emaValue.IsFormed)
return;
var ema = emaValue.ToDecimal();
var high = candle.HighPrice;
var low = candle.LowPrice;
var close = candle.ClosePrice;
if (_signalHigh != null)
{
_barsSinceSignal++;
if (_barsSinceSignal > _signalBars)
_signalHigh = _signalLow = null;
}
var signalHigh = _signalHigh;
var signalLow = _signalLow;
var signalIsLong = _signalIsLong;
// A new signal candle replaces the pending one and can only be broken by later candles.
var newSignal = true;
if (close < ema && high < ema)
SetSignal(high, low, true);
else if (close > ema && low > ema)
SetSignal(high, low, false);
else
newSignal = false;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var tod = candle.OpenTime.TimeOfDay;
var exitTime = new TimeSpan(ExitHour, ExitMinute, 0);
var frame = CandleType.Arg is TimeSpan tf ? tf : TimeSpan.Zero;
if (Position != 0 && tod <= exitTime && exitTime < tod + frame)
{
ClosePosition();
return;
}
if (Position > 0 && _stopPrice is decimal longStop && _targetPrice is decimal longTarget)
{
if (low <= longStop || high >= longTarget)
{
ClosePosition();
return;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop && _targetPrice is decimal shortTarget)
{
if (high >= shortStop || low <= shortTarget)
{
ClosePosition();
return;
}
}
if (signalHigh is not decimal sHigh || signalLow is not decimal sLow)
return;
var blockStart = new TimeSpan(BlockStartHour, BlockStartMinute, 0);
var blockEnd = new TimeSpan(BlockEndHour, BlockEndMinute, 0);
if (tod >= blockStart && tod < blockEnd)
return;
var risk = sHigh - sLow;
if (risk <= 0)
return;
if (signalIsLong && high > sHigh && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = sLow;
_targetPrice = sHigh + risk * TargetRR;
if (!newSignal)
_signalHigh = _signalLow = null;
}
else if (!signalIsLong && low < sLow && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = sHigh;
_targetPrice = sLow - risk * TargetRR;
if (!newSignal)
_signalHigh = _signalLow = null;
}
}
private void SetSignal(decimal high, decimal low, bool isLong)
{
_signalHigh = high;
_signalLow = low;
_signalIsLong = isLong;
_barsSinceSignal = 0;
}
private void ClosePosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_stopPrice = null;
_targetPrice = null;
}
}
import clr
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
SIGNAL_BARS = 3
class five_ema_strategy(Strategy):
"""
5 EMA strategy.
A candle whose close and high are below the EMA marks a long setup, one whose close and low are above it marks a short setup.
If price breaks the signal candle's high (long) or low (short) within the next three candles and outside the block window, the
strategy enters in that direction with the stop at the signal candle's opposite extreme and the target at TargetRR times the risk.
Open positions are closed at ExitHour:ExitMinute.
"""
def __init__(self):
super(five_ema_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 5).SetGreaterThanZero().SetDisplay("EMA Length", "EMA period", "EMA")
self._target_rr = self.Param("TargetRR", 3.0).SetGreaterThanZero().SetDisplay("Target R:R", "Reward to risk ratio of the target", "Risk")
self._exit_hour = self.Param("ExitHour", 15).SetRange(0, 23).SetDisplay("Exit Hour", "Hour of the forced exit", "Time")
self._exit_minute = self.Param("ExitMinute", 30).SetRange(0, 59).SetDisplay("Exit Minute", "Minute of the forced exit", "Time")
self._block_start_hour = self.Param("BlockStartHour", 15).SetRange(0, 23).SetDisplay("Block Start Hour", "Hour the entry block starts", "Time")
self._block_start_minute = self.Param("BlockStartMinute", 0).SetRange(0, 59).SetDisplay("Block Start Minute", "Minute the entry block starts", "Time")
self._block_end_hour = self.Param("BlockEndHour", 15).SetRange(0, 23).SetDisplay("Block End Hour", "Hour the entry block ends", "Time")
self._block_end_minute = self.Param("BlockEndMinute", 30).SetRange(0, 59).SetDisplay("Block End Minute", "Minute the entry block ends", "Time")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._signal_high = None
self._signal_low = None
self._signal_is_long = False
self._bars_since_signal = 0
self._stop_price = None
self._target_price = None
def OnReseted(self):
super(five_ema_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(five_ema_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
if not ema_value.IsFormed:
return
ema = ema_value.GetValue[Decimal](None)
high = candle.HighPrice
low = candle.LowPrice
close = candle.ClosePrice
if self._signal_high is not None:
self._bars_since_signal += 1
if self._bars_since_signal > SIGNAL_BARS:
self._signal_high = None
self._signal_low = None
signal_high = self._signal_high
signal_low = self._signal_low
signal_is_long = self._signal_is_long
# A new signal candle replaces the pending one and can only be broken by later candles.
new_signal = True
if close < ema and high < ema:
self._set_signal(high, low, True)
elif close > ema and low > ema:
self._set_signal(high, low, False)
else:
new_signal = False
if not self.IsFormedAndOnlineAndAllowTrading():
return
tod = candle.OpenTime.TimeOfDay
exit_time = TimeSpan(self._exit_hour.Value, self._exit_minute.Value, 0)
frame = self.candle_type.Arg if isinstance(self.candle_type.Arg, TimeSpan) else TimeSpan.Zero
if self.Position != 0 and tod <= exit_time and exit_time < tod + frame:
self._close_position()
return
if self.Position > 0 and self._stop_price is not None and self._target_price is not None:
if low <= self._stop_price or high >= self._target_price:
self._close_position()
return
elif self.Position < 0 and self._stop_price is not None and self._target_price is not None:
if high >= self._stop_price or low <= self._target_price:
self._close_position()
return
if signal_high is None or signal_low is None:
return
block_start = TimeSpan(self._block_start_hour.Value, self._block_start_minute.Value, 0)
block_end = TimeSpan(self._block_end_hour.Value, self._block_end_minute.Value, 0)
if tod >= block_start and tod < block_end:
return
risk = signal_high - signal_low
if risk <= 0:
return
target_rr = Decimal(self._target_rr.Value)
if signal_is_long and high > signal_high and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = signal_low
self._target_price = signal_high + risk * target_rr
if not new_signal:
self._signal_high = None
self._signal_low = None
elif not signal_is_long and low < signal_low and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = signal_high
self._target_price = signal_low - risk * target_rr
if not new_signal:
self._signal_high = None
self._signal_low = None
def _set_signal(self, high, low, is_long):
self._signal_high = high
self._signal_low = low
self._signal_is_long = is_long
self._bars_since_signal = 0
def _close_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
def CreateClone(self):
return five_ema_strategy()