DEMA 趋势振荡策略
该策略通过移动平均和标准差对双指数移动平均线(DEMA)进行归一化。当归一化值高于多头阈值且价格位于上轨之上时做多;当归一化值低于空头阈值且价格位于下轨之下时做空。使用基于 ATR 的追踪止损、带状止损以及按风险回报比计算的止盈。
细节
- 入场条件:
- 多头:归一化值 >
LongThreshold且最低价 > 上轨 - 空头:归一化值 <
ShortThreshold且最高价 < 下轨
- 多头:归一化值 >
- 方向:双向
- 出场条件:
- 多头:触及止盈、带状止损或追踪止损
- 空头:触及止盈、带状止损或追踪止损
- 止损:带状止损、ATR 追踪止损、风险回报止盈
- 默认值:
DemaPeriod= 40BaseLength= 20LongThreshold= 55mShortThreshold= 45mRiskReward= 1.5mAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- 筛选:
- 类型:趋势
- 方向:双向
- 指标:DEMA、SMA、StandardDeviation、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>
/// DEMA trend oscillator strategy.
/// The DEMA is normalized against its BaseLength SMA and standard deviation: the z-score is mapped to 0..100 with a logistic curve.
/// The bands are the DEMA plus and minus that standard deviation.
/// A long opens when the normalized value is above LongThreshold and the whole candle is above the upper band;
/// a short opens when it is below ShortThreshold and the whole candle is below the lower band.
/// The stop is the opposite band at entry, the target is RiskReward times that risk,
/// and an ATR trailing stop (AtrMultiplier times ATR) follows the price.
/// </summary>
public class DemaTrendOscillatorStrategy : Strategy
{
private const int _atrLength = 14;
private readonly StrategyParam<int> _demaPeriod;
private readonly StrategyParam<int> _baseLength;
private readonly StrategyParam<decimal> _longThreshold;
private readonly StrategyParam<decimal> _shortThreshold;
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _base;
private StandardDeviation _stdDev;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal? _trailPrice;
/// <summary>
/// DEMA period.
/// </summary>
public int DemaPeriod { get => _demaPeriod.Value; set => _demaPeriod.Value = value; }
/// <summary>
/// Length of the SMA and standard deviation of the DEMA.
/// </summary>
public int BaseLength { get => _baseLength.Value; set => _baseLength.Value = value; }
/// <summary>
/// Normalized value above which longs are allowed.
/// </summary>
public decimal LongThreshold { get => _longThreshold.Value; set => _longThreshold.Value = value; }
/// <summary>
/// Normalized value below which shorts are allowed.
/// </summary>
public decimal ShortThreshold { get => _shortThreshold.Value; set => _shortThreshold.Value = value; }
/// <summary>
/// Take profit as a multiple of the band stop distance.
/// </summary>
public decimal RiskReward { get => _riskReward.Value; set => _riskReward.Value = value; }
/// <summary>
/// ATR multiplier of the trailing stop.
/// </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 DemaTrendOscillatorStrategy()
{
_demaPeriod = Param(nameof(DemaPeriod), 40)
.SetGreaterThanZero()
.SetDisplay("DEMA Period", "DEMA period", "Indicators");
_baseLength = Param(nameof(BaseLength), 20)
.SetGreaterThanZero()
.SetDisplay("Base Length", "Length of the SMA and standard deviation of the DEMA", "Indicators");
_longThreshold = Param(nameof(LongThreshold), 55m)
.SetDisplay("Long Threshold", "Normalized value above which longs are allowed", "Signals");
_shortThreshold = Param(nameof(ShortThreshold), 45m)
.SetDisplay("Short Threshold", "Normalized value below which shorts are allowed", "Signals");
_riskReward = Param(nameof(RiskReward), 1.5m)
.SetNotNegative()
.SetDisplay("Risk Reward", "Take profit as a multiple of the band stop distance", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "ATR multiplier of the trailing stop", "Risk");
_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();
ClearStops();
}
private void ClearStops()
{
_stopPrice = null;
_takePrice = null;
_trailPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearStops();
var dema = new DoubleExponentialMovingAverage { Length = DemaPeriod };
var atr = new AverageTrueRange { Length = _atrLength };
_base = new SimpleMovingAverage { Length = BaseLength };
_stdDev = new StandardDeviation { Length = BaseLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(dema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, dema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue demaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!demaValue.IsFormed)
return;
var dema = demaValue.GetValue<decimal>();
var baseValue = _base.Process(dema, candle.ServerTime, true);
var sdValue = _stdDev.Process(dema, candle.ServerTime, true);
if (!baseValue.IsFormed || !sdValue.IsFormed || !atrValue.IsFormed)
return;
var basis = baseValue.GetValue<decimal>();
var sd = sdValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
if (sd <= 0m)
return;
var z = (double)((dema - basis) / sd);
var normalized = (decimal)(100.0 / (1.0 + Math.Exp(-z)));
var upper = dema + sd;
var lower = dema - sd;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position > 0)
{
if (AtrMultiplier > 0m)
{
var trail = close - atr * AtrMultiplier;
_trailPrice = _trailPrice is decimal t ? Math.Max(t, trail) : trail;
}
if ((_stopPrice is decimal stop && candle.LowPrice <= stop)
|| (_takePrice is decimal take && candle.HighPrice >= take)
|| (_trailPrice is decimal trailStop && candle.LowPrice <= trailStop))
{
SellMarket(Position);
ClearStops();
}
return;
}
if (Position < 0)
{
if (AtrMultiplier > 0m)
{
var trail = close + atr * AtrMultiplier;
_trailPrice = _trailPrice is decimal t ? Math.Min(t, trail) : trail;
}
if ((_stopPrice is decimal stop && candle.HighPrice >= stop)
|| (_takePrice is decimal take && candle.LowPrice <= take)
|| (_trailPrice is decimal trailStop && candle.HighPrice >= trailStop))
{
BuyMarket(-Position);
ClearStops();
}
return;
}
if (normalized > LongThreshold && candle.LowPrice > upper)
{
BuyMarket();
_stopPrice = lower;
_takePrice = RiskReward > 0m ? close + (close - lower) * RiskReward : null;
_trailPrice = null;
}
else if (normalized < ShortThreshold && candle.HighPrice < lower)
{
SellMarket();
_stopPrice = upper;
_takePrice = RiskReward > 0m ? close - (upper - close) * RiskReward : null;
_trailPrice = null;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DoubleExponentialMovingAverage, AverageTrueRange, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
ATR_LENGTH = 14
class dema_trend_oscillator_strategy(Strategy):
"""
DEMA trend oscillator strategy.
The DEMA is normalized against its BaseLength SMA and standard deviation: the z-score is mapped to 0..100 with a logistic curve.
The bands are the DEMA plus and minus that standard deviation.
A long opens when the normalized value is above LongThreshold and the whole candle is above the upper band;
a short opens when it is below ShortThreshold and the whole candle is below the lower band.
The stop is the opposite band at entry, the target is RiskReward times that risk,
and an ATR trailing stop (AtrMultiplier times ATR) follows the price.
"""
def __init__(self):
super(dema_trend_oscillator_strategy, self).__init__()
self._dema_period = self.Param("DemaPeriod", 40).SetGreaterThanZero().SetDisplay("DEMA Period", "DEMA period", "Indicators")
self._base_length = self.Param("BaseLength", 20).SetGreaterThanZero().SetDisplay("Base Length", "Length of the SMA and standard deviation of the DEMA", "Indicators")
self._long_threshold = self.Param("LongThreshold", 55.0).SetDisplay("Long Threshold", "Normalized value above which longs are allowed", "Signals")
self._short_threshold = self.Param("ShortThreshold", 45.0).SetDisplay("Short Threshold", "Normalized value below which shorts are allowed", "Signals")
self._risk_reward = self.Param("RiskReward", 1.5).SetNotNegative().SetDisplay("Risk Reward", "Take profit as a multiple of the band stop distance", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Multiplier", "ATR multiplier of the trailing stop", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._base = None
self._std_dev = None
self._clear_stops()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _clear_stops(self):
self._stop_price = None
self._take_price = None
self._trail_price = None
def OnReseted(self):
super(dema_trend_oscillator_strategy, self).OnReseted()
self._clear_stops()
def OnStarted2(self, time):
super(dema_trend_oscillator_strategy, self).OnStarted2(time)
self._clear_stops()
dema = DoubleExponentialMovingAverage()
dema.Length = self._dema_period.Value
atr = AverageTrueRange()
atr.Length = ATR_LENGTH
self._base = SimpleMovingAverage()
self._base.Length = self._base_length.Value
self._std_dev = StandardDeviation()
self._std_dev.Length = self._base_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(dema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, dema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, dema_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not dema_value.IsFormed:
return
dema_dec = dema_value.GetValue[Decimal](None)
base_value = process_float(self._base, dema_dec, candle.ServerTime, True)
sd_value = process_float(self._std_dev, dema_dec, candle.ServerTime, True)
if not base_value.IsFormed or not sd_value.IsFormed or not atr_value.IsFormed:
return
dema = float(dema_dec)
basis = float(base_value.GetValue[Decimal](None))
sd = float(sd_value.GetValue[Decimal](None))
atr = float(atr_value.GetValue[Decimal](None))
if sd <= 0:
return
z = (dema - basis) / sd
normalized = 100.0 / (1.0 + math.exp(-z))
upper = dema + sd
lower = dema - sd
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
atr_mult = float(self._atr_multiplier.Value)
risk_reward = float(self._risk_reward.Value)
if self.Position > 0:
if atr_mult > 0:
trail = close - atr * atr_mult
self._trail_price = trail if self._trail_price is None else max(self._trail_price, trail)
if ((self._stop_price is not None and low <= self._stop_price)
or (self._take_price is not None and high >= self._take_price)
or (self._trail_price is not None and low <= self._trail_price)):
self.SellMarket(self.Position)
self._clear_stops()
return
if self.Position < 0:
if atr_mult > 0:
trail = close + atr * atr_mult
self._trail_price = trail if self._trail_price is None else min(self._trail_price, trail)
if ((self._stop_price is not None and high >= self._stop_price)
or (self._take_price is not None and low <= self._take_price)
or (self._trail_price is not None and high >= self._trail_price)):
self.BuyMarket(-self.Position)
self._clear_stops()
return
if normalized > float(self._long_threshold.Value) and low > upper:
self.BuyMarket()
self._stop_price = lower
self._take_price = close + (close - lower) * risk_reward if risk_reward > 0 else None
self._trail_price = None
elif normalized < float(self._short_threshold.Value) and high < lower:
self.SellMarket()
self._stop_price = upper
self._take_price = close - (upper - close) * risk_reward if risk_reward > 0 else None
self._trail_price = None
def CreateClone(self):
return dema_trend_oscillator_strategy()