Стратегия DEMA Trend Oscillator
Стратегия нормализует двойную экспоненциальную среднюю (DEMA) с помощью скользящей средней и стандартного отклонения. Открывает лонг, когда нормализованное значение превышает порог и цена находится выше верхней полосы; открывает шорт, когда значение ниже порога и цена ниже нижней полосы. Использует ATR-трейлинг, стоп по полосе и тейк-профит по соотношению риск/прибыль.
Детали
- Условия входа:
- Long: нормализованное значение >
LongThresholdи минимум > верхней полосы - Short: нормализованное значение <
ShortThresholdи максимум < нижней полосы
- Long: нормализованное значение >
- Направление: Оба
- Условия выхода:
- Long: достижение тейк-профита, стопа по полосе или трейлинг-стопа
- Short: достижение тейк-профита, стопа по полосе или трейлинг-стопа
- Стопы: стоп по полосе, 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()