DecEMA Strategy
Strategy using the DecEMA indicator to follow trend direction. The indicator applies ten consecutive exponential smoothings and combines them to create a low-lag moving average. The strategy compares the last three DecEMA values. If the line turns upward and the latest value exceeds the previous one, it buys and closes any short position. If the line turns downward and the latest value is below the previous one, it sells and closes any long position.
Details
- Entry Criteria:
- Long: DecEMA slope turns up and current value > previous value
- Short: DecEMA slope turns down and current value < previous value
- Long/Short: Both
- Exit Criteria:
- Long: slope turns down
- Short: slope turns up
- Stops: None
- Default Values:
EmaPeriod= 3Length= 15BuyPosOpen= trueSellPosOpen= trueBuyPosClose= trueSellPosClose= trueCandleType= TimeSpan.FromHours(8).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: DecEMA
- Stops: No
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Strategy based on DecEMA indicator slope.
/// Buys when the indicator turns upward and sells when it turns downward.
/// </summary>
public class DecEmaStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private decimal _prev;
private decimal _prevPrev;
private int _count;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int Length { get => _length.Value; set => _length.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DecEmaStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Base EMA Period", "Length for initial EMA", "Parameters");
_length = Param(nameof(Length), 15)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "Smoothing length for DecEMA", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prev = default;
_prevPrev = default;
_count = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_count = 0;
var decema = new DecemaIndicator
{
EmaPeriod = EmaPeriod,
Length = Length
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(decema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, decema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal decema)
{
if (candle.State != CandleStates.Finished)
return;
_count++;
if (_count <= 2)
{
_prevPrev = _prev;
_prev = decema;
return;
}
// Slope reversal detection
if (_prev < _prevPrev && decema > _prev && Position <= 0)
{
// Was falling, now turning up -> buy
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (_prev > _prevPrev && decema < _prev && Position >= 0)
{
// Was rising, now turning down -> sell
if (Position > 0) SellMarket();
SellMarket();
}
_prevPrev = _prev;
_prev = decema;
}
private class DecemaIndicator : DecimalLengthIndicator
{
public int EmaPeriod { get; set; } = 3;
private readonly ExponentialMovingAverage _baseEma = new();
private decimal _ema1, _ema2, _ema3, _ema4, _ema5;
private decimal _ema6, _ema7, _ema8, _ema9, _ema10;
public override void Reset()
{
base.Reset();
_baseEma.Length = EmaPeriod;
_baseEma.Reset();
_ema1 = _ema2 = _ema3 = _ema4 = _ema5 = 0m;
_ema6 = _ema7 = _ema8 = _ema9 = _ema10 = 0m;
}
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
var ema0 = _baseEma.Process(input).ToDecimal();
var alpha = 2m / (1m + Length);
_ema1 = alpha * ema0 + (1 - alpha) * _ema1;
_ema2 = alpha * _ema1 + (1 - alpha) * _ema2;
_ema3 = alpha * _ema2 + (1 - alpha) * _ema3;
_ema4 = alpha * _ema3 + (1 - alpha) * _ema4;
_ema5 = alpha * _ema4 + (1 - alpha) * _ema5;
_ema6 = alpha * _ema5 + (1 - alpha) * _ema6;
_ema7 = alpha * _ema6 + (1 - alpha) * _ema7;
_ema8 = alpha * _ema7 + (1 - alpha) * _ema8;
_ema9 = alpha * _ema8 + (1 - alpha) * _ema9;
_ema10 = alpha * _ema9 + (1 - alpha) * _ema10;
var value = 10m * _ema1 - 45m * _ema2 + 120m * _ema3 - 210m * _ema4 + 252m * _ema5
- 210m * _ema6 + 120m * _ema7 - 45m * _ema8 + 10m * _ema9 - _ema10;
IsFormed = _baseEma.IsFormed;
return new DecimalIndicatorValue(this, value, input.Time);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class dec_ema_strategy(Strategy):
def __init__(self):
super(dec_ema_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 3) \
.SetDisplay("Base EMA Period", "Length for initial EMA", "Parameters")
self._length = self.Param("Length", 15) \
.SetDisplay("Smoothing Length", "Smoothing length for DecEMA", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev = 0.0
self._prev_prev = 0.0
self._count = 0
self._base_ema = None
self._ema1 = 0.0
self._ema2 = 0.0
self._ema3 = 0.0
self._ema4 = 0.0
self._ema5 = 0.0
self._ema6 = 0.0
self._ema7 = 0.0
self._ema8 = 0.0
self._ema9 = 0.0
self._ema10 = 0.0
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def Length(self):
return self._length.Value
@Length.setter
def Length(self, value):
self._length.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(dec_ema_strategy, self).OnStarted2(time)
self._count = 0
self._base_ema = ExponentialMovingAverage()
self._base_ema.Length = self.EmaPeriod
self._ema1 = 0.0
self._ema2 = 0.0
self._ema3 = 0.0
self._ema4 = 0.0
self._ema5 = 0.0
self._ema6 = 0.0
self._ema7 = 0.0
self._ema8 = 0.0
self._ema9 = 0.0
self._ema10 = 0.0
self.SubscribeCandles(self.CandleType) \
.Bind(self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
t = candle.OpenTime
ema_result = process_float(self._base_ema, close, t, True)
if not self._base_ema.IsFormed:
return
ema0 = float(ema_result)
alpha = 2.0 / (1.0 + float(self.Length))
self._ema1 = alpha * ema0 + (1.0 - alpha) * self._ema1
self._ema2 = alpha * self._ema1 + (1.0 - alpha) * self._ema2
self._ema3 = alpha * self._ema2 + (1.0 - alpha) * self._ema3
self._ema4 = alpha * self._ema3 + (1.0 - alpha) * self._ema4
self._ema5 = alpha * self._ema4 + (1.0 - alpha) * self._ema5
self._ema6 = alpha * self._ema5 + (1.0 - alpha) * self._ema6
self._ema7 = alpha * self._ema6 + (1.0 - alpha) * self._ema7
self._ema8 = alpha * self._ema7 + (1.0 - alpha) * self._ema8
self._ema9 = alpha * self._ema8 + (1.0 - alpha) * self._ema9
self._ema10 = alpha * self._ema9 + (1.0 - alpha) * self._ema10
decema = (10.0 * self._ema1 - 45.0 * self._ema2 + 120.0 * self._ema3
- 210.0 * self._ema4 + 252.0 * self._ema5 - 210.0 * self._ema6
+ 120.0 * self._ema7 - 45.0 * self._ema8 + 10.0 * self._ema9
- self._ema10)
self._count += 1
if self._count <= 2:
self._prev_prev = self._prev
self._prev = decema
return
if self._prev < self._prev_prev and decema > self._prev and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev > self._prev_prev and decema < self._prev and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_prev = self._prev
self._prev = decema
def OnReseted(self):
super(dec_ema_strategy, self).OnReseted()
self._prev = 0.0
self._prev_prev = 0.0
self._count = 0
self._base_ema = None
self._ema1 = 0.0
self._ema2 = 0.0
self._ema3 = 0.0
self._ema4 = 0.0
self._ema5 = 0.0
self._ema6 = 0.0
self._ema7 = 0.0
self._ema8 = 0.0
self._ema9 = 0.0
self._ema10 = 0.0
def CreateClone(self):
return dec_ema_strategy()