三重EMA交叉策略
三重EMA交叉策略将传统的快/慢指数移动平均线交叉与更长周期的趋势过滤器结合。在快速EMA自下而上穿越慢速EMA后,策略等待价格回踩快速均线,只要收盘价仍位于长期趋势EMA之上即可进场。这种设置旨在捕捉主趋势中的短暂回调后的延续走势。
当动能减弱且快速EMA再次跌破慢速EMA时平仓。如果价格逆向移动,基于百分比的止损会保护持仓。该方法在趋势明显的市场表现较好,并能避免在震荡区间频繁交易。
细节
- 入场条件:
- 最近 N 根K线内出现快速EMA上穿慢速EMA。
- 当前收盘价 ≥ 快速EMA 且当日最低价 ≤ 快速EMA。
- 趋势EMA ≤ 当前收盘价。
- 方向:仅做多。
- 出场条件:
- 快速EMA跌破慢速EMA。
- 止损:按入场价的
stop_loss_percent百分比设置。 - 默认参数:
FastEmaLength= 10SlowEmaLength= 20TrendEmaLength= 100StopLossPercent= 2.0CrossBackBars= 10
- 过滤器:
- 类型:趋势跟随
- 方向:多头
- 指标:EMA
- 止损:是
- 复杂度:中等
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中等
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Three EMA Cross Strategy.
/// After the fast EMA crosses above the slow EMA, a long opens within CrossBackBars bars on a pullback whose low touches the
/// fast EMA while the close stays at or above both the fast EMA and the trend EMA. The long closes when the fast EMA drops
/// below the slow EMA, and a percent stop limits the loss.
/// </summary>
public class ThreeEmaCrossStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _trendEmaLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _crossBackBars;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevFast;
private decimal? _prevSlow;
private int? _barsSinceCross;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
/// <summary>
/// Trend EMA period.
/// </summary>
public int TrendEmaLength
{
get => _trendEmaLength.Value;
set => _trendEmaLength.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars after the fast/slow cross during which a pullback entry is allowed.
/// </summary>
public int CrossBackBars
{
get => _crossBackBars.Value;
set => _crossBackBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ThreeEmaCrossStrategy()
{
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_trendEmaLength = Param(nameof(TrendEmaLength), 100)
.SetGreaterThanZero()
.SetDisplay("Trend EMA", "Trend EMA period", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_crossBackBars = Param(nameof(CrossBackBars), 10)
.SetGreaterThanZero()
.SetDisplay("Cross Back Bars", "Bars after the cross during which a pullback entry is allowed", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevFast = null;
_prevSlow = null;
_barsSinceCross = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var trendEma = new ExponentialMovingAverage { Length = TrendEmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(fastEma, slowEma, trendEma, ProcessCandle)
.Start();
if (StopLossPercent > 0m)
{
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawIndicator(area, trendEma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue trendValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!fastValue.IsFormed || !slowValue.IsFormed)
return;
var fast = fastValue.GetValue<decimal>();
var slow = slowValue.GetValue<decimal>();
var prevFast = _prevFast;
var prevSlow = _prevSlow;
_prevFast = fast;
_prevSlow = slow;
if (prevFast is decimal pf && prevSlow is decimal ps && pf <= ps && fast > slow)
_barsSinceCross = 0;
else if (_barsSinceCross is int bars)
_barsSinceCross = bars + 1;
if (!trendValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var trend = trendValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (Position > 0)
{
if (fast < slow)
SellMarket(Position);
return;
}
var recentCross = _barsSinceCross is int since && since < CrossBackBars;
if (Position == 0 && recentCross && close >= fast && candle.LowPrice <= fast && trend <= close)
BuyMarket(Volume);
}
}
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, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class three_ema_cross_strategy(Strategy):
"""
Three EMA Cross Strategy.
After the fast EMA crosses above the slow EMA, a long opens within CrossBackBars bars on a pullback whose low touches the
fast EMA while the close stays at or above both the fast EMA and the trend EMA. The long closes when the fast EMA drops
below the slow EMA, and a percent stop limits the loss.
"""
def __init__(self):
super(three_ema_cross_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 20).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._trend_ema_length = self.Param("TrendEmaLength", 100).SetGreaterThanZero().SetDisplay("Trend EMA", "Trend EMA period", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._cross_back_bars = self.Param("CrossBackBars", 10).SetGreaterThanZero().SetDisplay("Cross Back Bars", "Bars after the cross during which a pullback entry is allowed", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).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._prev_fast = None
self._prev_slow = None
self._bars_since_cross = None
def OnReseted(self):
super(three_ema_cross_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(three_ema_cross_strategy, self).OnStarted2(time)
self._reset_state()
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ema_length.Value
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self._trend_ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(fast_ema, slow_ema, trend_ema, self._process_candle).Start()
stop = float(self._stop_loss_percent.Value)
if stop > 0:
self.StartProtection(Unit(), Unit(Decimal(stop), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawIndicator(area, trend_ema)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, fast_value, slow_value, trend_value):
if candle.State != CandleStates.Finished:
return
if not fast_value.IsFormed or not slow_value.IsFormed:
return
fast = float(fast_value.GetValue[Decimal](None))
slow = float(slow_value.GetValue[Decimal](None))
prev_fast = self._prev_fast
prev_slow = self._prev_slow
self._prev_fast = fast
self._prev_slow = slow
if prev_fast is not None and prev_slow is not None and prev_fast <= prev_slow and fast > slow:
self._bars_since_cross = 0
elif self._bars_since_cross is not None:
self._bars_since_cross += 1
if not trend_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
trend = float(trend_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
low = float(candle.LowPrice)
if self.Position > 0:
if fast < slow:
self.SellMarket(self.Position)
return
recent_cross = self._bars_since_cross is not None and self._bars_since_cross < self._cross_back_bars.Value
if self.Position == 0 and recent_cross and close >= fast and low <= fast and trend <= close:
self.BuyMarket(self.Volume)
def CreateClone(self):
return three_ema_cross_strategy()