Long EMA Advanced Exit 策略
Long EMA Advanced Exit 是一个仅做多策略,当短期均线向上穿越中期均线且价格位于长期均线上方时入场。退出条件包括 MACD 死叉、价格收于选定均线之下、短期均线下穿中期均线、可选的跟踪止损以及基于 ATR 的波动率过滤器。
细节
- 数据:价格K线。
- 入场条件:
- 多头:短期均线上穿中期均线且价格在长期均线上方。
- 出场条件:MACD 死叉、价格跌破选定均线、短期均线下穿中期均线、可选跟踪止损。
- 止损:可选跟踪止损。
- 默认参数:
MaType= EMAEntryConditionType= CrossoverLongTermPeriod= 200ShortTermPeriod= 5MidTermPeriod= 10EnableMacdExit= trueMacdCandleType= TimeSpan.FromDays(7).TimeFrame()MacdFastLength= 12MacdSlowLength= 26MacdSignalLength= 9UseTrailingStop= falseTrailingStopPercent= 15UseMaCloseExit= falseMaCloseExitPeriod= 50UseMaCrossExit= trueUseVolatilityFilter= falseAtrPeriod= 14AtrMultiplier= 1.5CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类:趋势跟随
- 方向:仅多头
- 指标:MA, MACD, ATR
- 复杂度:中等
- 风险等级:中等
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>
/// Long EMA advanced exit strategy.
/// Long only: enters when the short MA crosses above (or, with the Above condition, is above) the medium MA while price is above the long MA.
/// Exits when the MACD on the higher MacdCandleType timeframe crosses below its signal line, when price closes below the MaCloseExitPeriod MA,
/// when the short MA crosses below the medium MA, when a candle range exceeds AtrMultiplier ATRs, or on a percent trailing stop; each exit can be switched off.
/// </summary>
public class LongEmaAdvancedExitStrategy : Strategy
{
/// <summary>
/// Moving average types.
/// </summary>
public enum MaTypes
{
/// <summary>
/// Exponential moving average.
/// </summary>
EMA,
/// <summary>
/// Simple moving average.
/// </summary>
SMA,
}
/// <summary>
/// Entry condition types.
/// </summary>
public enum EntryConditions
{
/// <summary>
/// Short MA crosses above the medium MA.
/// </summary>
Crossover,
/// <summary>
/// Short MA is above the medium MA.
/// </summary>
Above,
}
private readonly StrategyParam<MaTypes> _maType;
private readonly StrategyParam<EntryConditions> _entryConditionType;
private readonly StrategyParam<int> _longTermPeriod;
private readonly StrategyParam<int> _shortTermPeriod;
private readonly StrategyParam<int> _midTermPeriod;
private readonly StrategyParam<bool> _enableMacdExit;
private readonly StrategyParam<DataType> _macdCandleType;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdSignalLength;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<decimal> _trailingStopPercent;
private readonly StrategyParam<bool> _useMaCloseExit;
private readonly StrategyParam<int> _maCloseExitPeriod;
private readonly StrategyParam<bool> _useMaCrossExit;
private readonly StrategyParam<bool> _useVolatilityFilter;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevShort;
private decimal? _prevMid;
private decimal? _prevMacd;
private decimal? _prevSignal;
private MovingAverageConvergenceDivergenceSignal _macd;
/// <summary>
/// Moving average type.
/// </summary>
public MaTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Entry condition type.
/// </summary>
public EntryConditions EntryConditionType
{
get => _entryConditionType.Value;
set => _entryConditionType.Value = value;
}
/// <summary>
/// Long-term MA period.
/// </summary>
public int LongTermPeriod
{
get => _longTermPeriod.Value;
set => _longTermPeriod.Value = value;
}
/// <summary>
/// Short-term MA period.
/// </summary>
public int ShortTermPeriod
{
get => _shortTermPeriod.Value;
set => _shortTermPeriod.Value = value;
}
/// <summary>
/// Medium-term MA period.
/// </summary>
public int MidTermPeriod
{
get => _midTermPeriod.Value;
set => _midTermPeriod.Value = value;
}
/// <summary>
/// Exit on a MACD cross down.
/// </summary>
public bool EnableMacdExit
{
get => _enableMacdExit.Value;
set => _enableMacdExit.Value = value;
}
/// <summary>
/// Candle type of the MACD exit.
/// </summary>
public DataType MacdCandleType
{
get => _macdCandleType.Value;
set => _macdCandleType.Value = value;
}
/// <summary>
/// MACD fast length.
/// </summary>
public int MacdFastLength
{
get => _macdFastLength.Value;
set => _macdFastLength.Value = value;
}
/// <summary>
/// MACD slow length.
/// </summary>
public int MacdSlowLength
{
get => _macdSlowLength.Value;
set => _macdSlowLength.Value = value;
}
/// <summary>
/// MACD signal length.
/// </summary>
public int MacdSignalLength
{
get => _macdSignalLength.Value;
set => _macdSignalLength.Value = value;
}
/// <summary>
/// Use the percent trailing stop.
/// </summary>
public bool UseTrailingStop
{
get => _useTrailingStop.Value;
set => _useTrailingStop.Value = value;
}
/// <summary>
/// Trailing stop percent.
/// </summary>
public decimal TrailingStopPercent
{
get => _trailingStopPercent.Value;
set => _trailingStopPercent.Value = value;
}
/// <summary>
/// Exit when price closes below the exit MA.
/// </summary>
public bool UseMaCloseExit
{
get => _useMaCloseExit.Value;
set => _useMaCloseExit.Value = value;
}
/// <summary>
/// Exit MA period.
/// </summary>
public int MaCloseExitPeriod
{
get => _maCloseExitPeriod.Value;
set => _maCloseExitPeriod.Value = value;
}
/// <summary>
/// Exit when the short MA crosses below the medium MA.
/// </summary>
public bool UseMaCrossExit
{
get => _useMaCrossExit.Value;
set => _useMaCrossExit.Value = value;
}
/// <summary>
/// Exit when a candle range exceeds the ATR threshold.
/// </summary>
public bool UseVolatilityFilter
{
get => _useVolatilityFilter.Value;
set => _useVolatilityFilter.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the volatility exit.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public LongEmaAdvancedExitStrategy()
{
_maType = Param(nameof(MaType), MaTypes.EMA)
.SetDisplay("MA Type", "Moving average type", "Indicators");
_entryConditionType = Param(nameof(EntryConditionType), EntryConditions.Crossover)
.SetDisplay("Entry Condition", "Crossover or Above", "Entry");
_longTermPeriod = Param(nameof(LongTermPeriod), 200)
.SetGreaterThanZero()
.SetDisplay("Long MA", "Long-term MA period", "Indicators");
_shortTermPeriod = Param(nameof(ShortTermPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Short MA", "Short-term MA period", "Indicators");
_midTermPeriod = Param(nameof(MidTermPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Medium MA", "Medium-term MA period", "Indicators");
_enableMacdExit = Param(nameof(EnableMacdExit), true)
.SetDisplay("MACD Exit", "Exit on a MACD cross down", "Exit");
_macdCandleType = Param(nameof(MacdCandleType), TimeSpan.FromDays(7).TimeFrame())
.SetDisplay("MACD Candle Type", "Candle type of the MACD exit", "Exit");
_macdFastLength = Param(nameof(MacdFastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast length", "Exit");
_macdSlowLength = Param(nameof(MacdSlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow length", "Exit");
_macdSignalLength = Param(nameof(MacdSignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "MACD signal length", "Exit");
_useTrailingStop = Param(nameof(UseTrailingStop), false)
.SetDisplay("Use Trailing Stop", "Use the percent trailing stop", "Risk");
_trailingStopPercent = Param(nameof(TrailingStopPercent), 15m)
.SetNotNegative()
.SetDisplay("Trailing Stop %", "Trailing stop percent", "Risk");
_useMaCloseExit = Param(nameof(UseMaCloseExit), false)
.SetDisplay("MA Close Exit", "Exit when price closes below the exit MA", "Exit");
_maCloseExitPeriod = Param(nameof(MaCloseExitPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Exit MA Period", "Exit MA period", "Exit");
_useMaCrossExit = Param(nameof(UseMaCrossExit), true)
.SetDisplay("MA Cross Exit", "Exit when the short MA crosses below the medium MA", "Exit");
_useVolatilityFilter = Param(nameof(UseVolatilityFilter), false)
.SetDisplay("Volatility Exit", "Exit when a candle range exceeds the ATR threshold", "Exit");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Exit");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "ATR multiplier of the volatility exit", "Exit");
_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), (Security, MacdCandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevShort = null;
_prevMid = null;
_prevMacd = null;
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevShort = null;
_prevMid = null;
_prevMacd = null;
_prevSignal = null;
var shortMa = CreateMa(ShortTermPeriod);
var midMa = CreateMa(MidTermPeriod);
var longMa = CreateMa(LongTermPeriod);
var exitMa = CreateMa(MaCloseExitPeriod);
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(shortMa, midMa, longMa, exitMa, atr, ProcessCandle)
.Start();
_macd = new MovingAverageConvergenceDivergenceSignal();
_macd.Macd.ShortMa.Length = MacdFastLength;
_macd.Macd.LongMa.Length = MacdSlowLength;
_macd.SignalMa.Length = MacdSignalLength;
// The weekly MACD needs months of history, so it is processed manually to keep it from blocking entries until it forms.
SubscribeCandles(MacdCandleType)
.Bind(ProcessMacd)
.Start();
if (UseTrailingStop && TrailingStopPercent > 0)
StartProtection(new Unit(), new Unit(TrailingStopPercent, UnitTypes.Percent), isStopTrailing: true, useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortMa);
DrawIndicator(area, midMa);
DrawIndicator(area, longMa);
DrawOwnTrades(area);
}
}
private IIndicator CreateMa(int length)
{
return MaType == MaTypes.SMA
? new SimpleMovingAverage { Length = length }
: new ExponentialMovingAverage { Length = length };
}
private void ProcessMacd(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var macdValue = _macd.Process(candle);
if (!macdValue.IsFormed || macdValue is not IMovingAverageConvergenceDivergenceSignalValue { Macd: decimal macd, Signal: decimal signal })
return;
var crossDown = _prevMacd is decimal prevMacd && _prevSignal is decimal prevSignal && prevMacd >= prevSignal && macd < signal;
_prevMacd = macd;
_prevSignal = signal;
if (crossDown && EnableMacdExit && Position > 0 && IsFormedAndOnlineAndAllowTrading())
SellMarket(Position);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue shortValue, IIndicatorValue midValue, IIndicatorValue longValue, IIndicatorValue exitValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!shortValue.IsFormed || !midValue.IsFormed)
return;
var shortMa = shortValue.GetValue<decimal>();
var midMa = midValue.GetValue<decimal>();
var prevShort = _prevShort;
var prevMid = _prevMid;
_prevShort = shortMa;
_prevMid = midMa;
if (prevShort is not decimal ps || prevMid is not decimal pm)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var crossUp = ps <= pm && shortMa > midMa;
var crossDown = ps >= pm && shortMa < midMa;
if (Position > 0)
{
var exit = (UseMaCrossExit && crossDown)
|| (UseMaCloseExit && exitValue.IsFormed && close < exitValue.GetValue<decimal>())
|| (UseVolatilityFilter && atrValue.IsFormed && candle.HighPrice - candle.LowPrice > atrValue.GetValue<decimal>() * AtrMultiplier);
if (exit)
SellMarket(Position);
return;
}
if (Position < 0 || !longValue.IsFormed)
return;
var entry = EntryConditionType == EntryConditions.Crossover ? crossUp : shortMa > midMa;
if (entry && close > longValue.GetValue<decimal>())
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
from StockSharp.Algo.Indicators import (ExponentialMovingAverage, SimpleMovingAverage, AverageTrueRange,
MovingAverageConvergenceDivergenceSignal, CandleIndicatorValue)
from StockSharp.Algo.Strategies import Strategy
class long_ema_advanced_exit_strategy(Strategy):
"""
Long EMA advanced exit strategy.
Long only: enters when the short MA crosses above (or, with the Above condition, is above) the medium MA while price is above the long MA.
Exits when the MACD on the higher MacdCandleType timeframe crosses below its signal line, when price closes below the MaCloseExitPeriod MA,
when the short MA crosses below the medium MA, when a candle range exceeds AtrMultiplier ATRs, or on a percent trailing stop; each exit can be switched off.
MaType: EMA or SMA. EntryConditionType: Crossover or Above.
"""
def __init__(self):
super(long_ema_advanced_exit_strategy, self).__init__()
self._ma_type = self.Param("MaType", "EMA").SetDisplay("MA Type", "Moving average type", "Indicators")
self._entry_condition_type = self.Param("EntryConditionType", "Crossover").SetDisplay("Entry Condition", "Crossover or Above", "Entry")
self._long_term_period = self.Param("LongTermPeriod", 200).SetGreaterThanZero().SetDisplay("Long MA", "Long-term MA period", "Indicators")
self._short_term_period = self.Param("ShortTermPeriod", 5).SetGreaterThanZero().SetDisplay("Short MA", "Short-term MA period", "Indicators")
self._mid_term_period = self.Param("MidTermPeriod", 10).SetGreaterThanZero().SetDisplay("Medium MA", "Medium-term MA period", "Indicators")
self._enable_macd_exit = self.Param("EnableMacdExit", True).SetDisplay("MACD Exit", "Exit on a MACD cross down", "Exit")
self._macd_candle_type = self.Param("MacdCandleType", DataType.TimeFrame(TimeSpan.FromDays(7))).SetDisplay("MACD Candle Type", "Candle type of the MACD exit", "Exit")
self._macd_fast_length = self.Param("MacdFastLength", 12).SetGreaterThanZero().SetDisplay("MACD Fast", "MACD fast length", "Exit")
self._macd_slow_length = self.Param("MacdSlowLength", 26).SetGreaterThanZero().SetDisplay("MACD Slow", "MACD slow length", "Exit")
self._macd_signal_length = self.Param("MacdSignalLength", 9).SetGreaterThanZero().SetDisplay("MACD Signal", "MACD signal length", "Exit")
self._use_trailing_stop = self.Param("UseTrailingStop", False).SetDisplay("Use Trailing Stop", "Use the percent trailing stop", "Risk")
self._trailing_stop_percent = self.Param("TrailingStopPercent", 15.0).SetNotNegative().SetDisplay("Trailing Stop %", "Trailing stop percent", "Risk")
self._use_ma_close_exit = self.Param("UseMaCloseExit", False).SetDisplay("MA Close Exit", "Exit when price closes below the exit MA", "Exit")
self._ma_close_exit_period = self.Param("MaCloseExitPeriod", 50).SetGreaterThanZero().SetDisplay("Exit MA Period", "Exit MA period", "Exit")
self._use_ma_cross_exit = self.Param("UseMaCrossExit", True).SetDisplay("MA Cross Exit", "Exit when the short MA crosses below the medium MA", "Exit")
self._use_volatility_filter = self.Param("UseVolatilityFilter", False).SetDisplay("Volatility Exit", "Exit when a candle range exceeds the ATR threshold", "Exit")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Exit")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5).SetNotNegative().SetDisplay("ATR Multiplier", "ATR multiplier of the volatility exit", "Exit")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._macd = 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._macd_candle_type.Value)]
def _reset_state(self):
self._prev_short = None
self._prev_mid = None
self._prev_macd = None
self._prev_signal = None
def OnReseted(self):
super(long_ema_advanced_exit_strategy, self).OnReseted()
self._reset_state()
def _create_ma(self, length):
ma = SimpleMovingAverage() if str(self._ma_type.Value) == "SMA" else ExponentialMovingAverage()
ma.Length = length
return ma
def OnStarted2(self, time):
super(long_ema_advanced_exit_strategy, self).OnStarted2(time)
self._reset_state()
short_ma = self._create_ma(self._short_term_period.Value)
mid_ma = self._create_ma(self._mid_term_period.Value)
long_ma = self._create_ma(self._long_term_period.Value)
exit_ma = self._create_ma(self._ma_close_exit_period.Value)
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(short_ma, mid_ma, long_ma, exit_ma, atr, self._process_candle).Start()
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = self._macd_fast_length.Value
self._macd.Macd.LongMa.Length = self._macd_slow_length.Value
self._macd.SignalMa.Length = self._macd_signal_length.Value
# The weekly MACD needs months of history, so it is processed manually to keep it from blocking entries until it forms.
self.SubscribeCandles(self._macd_candle_type.Value).Bind(self._process_macd).Start()
if self._use_trailing_stop.Value and self._trailing_stop_percent.Value > 0:
self.StartProtection(Unit(), Unit(Decimal(self._trailing_stop_percent.Value), UnitTypes.Percent), isStopTrailing=True, useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ma)
self.DrawIndicator(area, mid_ma)
self.DrawIndicator(area, long_ma)
self.DrawOwnTrades(area)
def _process_macd(self, candle):
if candle.State != CandleStates.Finished:
return
macd_value = self._macd.Process(CandleIndicatorValue(self._macd, candle))
if not macd_value.IsFormed or macd_value.Macd is None or macd_value.Signal is None:
return
macd = macd_value.Macd
signal = macd_value.Signal
cross_down = self._prev_macd is not None and self._prev_signal is not None and self._prev_macd >= self._prev_signal and macd < signal
self._prev_macd = macd
self._prev_signal = signal
if cross_down and self._enable_macd_exit.Value and self.Position > 0 and self.IsFormedAndOnlineAndAllowTrading():
self.SellMarket(self.Position)
def _process_candle(self, candle, short_value, mid_value, long_value, exit_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not short_value.IsFormed or not mid_value.IsFormed:
return
short_ma = short_value.GetValue[Decimal](None)
mid_ma = mid_value.GetValue[Decimal](None)
prev_short = self._prev_short
prev_mid = self._prev_mid
self._prev_short = short_ma
self._prev_mid = mid_ma
if prev_short is None or prev_mid is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
cross_up = prev_short <= prev_mid and short_ma > mid_ma
cross_down = prev_short >= prev_mid and short_ma < mid_ma
if self.Position > 0:
exit_signal = (self._use_ma_cross_exit.Value and cross_down) \
or (self._use_ma_close_exit.Value and exit_value.IsFormed and close < exit_value.GetValue[Decimal](None)) \
or (self._use_volatility_filter.Value and atr_value.IsFormed
and candle.HighPrice - candle.LowPrice > atr_value.GetValue[Decimal](None) * Decimal(self._atr_multiplier.Value))
if exit_signal:
self.SellMarket(self.Position)
return
if self.Position < 0 or not long_value.IsFormed:
return
entry = cross_up if str(self._entry_condition_type.Value) == "Crossover" else short_ma > mid_ma
if entry and close > long_value.GetValue[Decimal](None):
self.BuyMarket(self.Volume)
def CreateClone(self):
return long_ema_advanced_exit_strategy()