Triple MA HTF Strategy - Dynamic Smoothing
Strategy that compares three moving averages calculated on higher timeframes. Each higher timeframe MA is smoothed proportionally to the ratio between its timeframe and the working timeframe. Signals are generated when the first MA crosses the second while the third confirms the direction.
Details
- Entry Criteria: Cross of MA1 and MA2 with MA3 trend confirmation.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal.
- Stops: No.
- Default Values:
CandleType= TimeSpan.FromMinutes(5)HigherTimeFrame1= TimeSpan.FromMinutes(15)HigherTimeFrame2= TimeSpan.FromMinutes(60)HigherTimeFrame3= TimeSpan.FromMinutes(240)Length1= 21Length2= 21Length3= 50
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MA
- Stops: None
- Complexity: Intermediate
- Timeframe: Intraday (5m base)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Triple moving average trend following using EMA crossovers with RSI confirmation.
/// </summary>
public class TripleMaHtfDynamicSmoothingStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _length1;
private readonly StrategyParam<int> _length2;
private readonly StrategyParam<int> _length3;
private decimal _prevMa1;
private decimal _prevMa2;
private decimal _prevRsi;
private int _cooldown;
public TripleMaHtfDynamicSmoothingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Base timeframe", "General");
_length1 = Param(nameof(Length1), 10)
.SetDisplay("MA1 Length", "Length for fast EMA", "Trend");
_length2 = Param(nameof(Length2), 30)
.SetDisplay("MA2 Length", "Length for slow EMA", "Trend");
_length3 = Param(nameof(Length3), 14)
.SetDisplay("RSI Length", "RSI period", "Trend");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Length1
{
get => _length1.Value;
set => _length1.Value = value;
}
public int Length2
{
get => _length2.Value;
set => _length2.Value = value;
}
public int Length3
{
get => _length3.Value;
set => _length3.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMa1 = 0;
_prevMa2 = 0;
_prevRsi = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema1 = new ExponentialMovingAverage { Length = Length1 };
var ema2 = new ExponentialMovingAverage { Length = Length2 };
var rsi = new RelativeStrengthIndex { Length = Length3 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema1, ema2, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema1);
DrawIndicator(area, ema2);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ma1, decimal ma2, decimal rsiVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevMa1 == 0 || _prevMa2 == 0 || _prevRsi == 0)
{
_prevMa1 = ma1;
_prevMa2 = ma2;
_prevRsi = rsiVal;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevMa1 = ma1;
_prevMa2 = ma2;
_prevRsi = rsiVal;
return;
}
var price = candle.ClosePrice;
// EMA crossover
var crossUp = _prevMa1 <= _prevMa2 && ma1 > ma2;
var crossDown = _prevMa1 >= _prevMa2 && ma1 < ma2;
// EMA trend direction
var trendUp = ma1 > ma2;
var trendDown = ma1 < ma2;
// Exit on EMA cross in opposite direction
if (Position > 0 && crossDown)
{
SellMarket();
_cooldown = 30;
}
else if (Position < 0 && crossUp)
{
BuyMarket();
_cooldown = 30;
}
// Entry: EMA crossover + RSI filter
if (Position == 0)
{
if (crossUp && rsiVal > 45m && rsiVal < 75m)
{
BuyMarket();
_cooldown = 30;
}
else if (crossDown && rsiVal > 25m && rsiVal < 55m)
{
SellMarket();
_cooldown = 30;
}
// Re-entry: RSI cross 50 in trend direction when flat
else if (trendUp && _prevRsi <= 50m && rsiVal > 50m)
{
BuyMarket();
_cooldown = 30;
}
else if (trendDown && _prevRsi >= 50m && rsiVal < 50m)
{
SellMarket();
_cooldown = 30;
}
}
_prevMa1 = ma1;
_prevMa2 = ma2;
_prevRsi = rsiVal;
}
}
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, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class triple_ma_htf_dynamic_smoothing_strategy(Strategy):
"""EMA crossover with RSI confirmation and re-entry on RSI crossing 50."""
def __init__(self):
super(triple_ma_htf_dynamic_smoothing_strategy, self).__init__()
self._len1 = self.Param("Length1", 10).SetDisplay("MA1 Length", "Fast EMA", "Trend")
self._len2 = self.Param("Length2", 30).SetDisplay("MA2 Length", "Slow EMA", "Trend")
self._len3 = self.Param("Length3", 14).SetDisplay("RSI Length", "RSI period", "Trend")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(triple_ma_htf_dynamic_smoothing_strategy, self).OnReseted()
self._prev_ma1 = 0
self._prev_ma2 = 0
self._prev_rsi = 0
self._cooldown = 0
def OnStarted2(self, time):
super(triple_ma_htf_dynamic_smoothing_strategy, self).OnStarted2(time)
self._prev_ma1 = 0
self._prev_ma2 = 0
self._prev_rsi = 0
self._cooldown = 0
ema1 = ExponentialMovingAverage()
ema1.Length = self._len1.Value
ema2 = ExponentialMovingAverage()
ema2.Length = self._len2.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._len3.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(ema1, ema2, rsi, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema1)
self.DrawIndicator(area, ema2)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ma1, ma2, rsi_val):
if candle.State != CandleStates.Finished:
return
ma1 = float(ma1)
ma2 = float(ma2)
rsi_val = float(rsi_val)
if self._prev_ma1 == 0 or self._prev_ma2 == 0 or self._prev_rsi == 0:
self._prev_ma1 = ma1
self._prev_ma2 = ma2
self._prev_rsi = rsi_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_ma1 = ma1
self._prev_ma2 = ma2
self._prev_rsi = rsi_val
return
cross_up = self._prev_ma1 <= self._prev_ma2 and ma1 > ma2
cross_down = self._prev_ma1 >= self._prev_ma2 and ma1 < ma2
trend_up = ma1 > ma2
trend_down = ma1 < ma2
# Exit on opposite cross
if self.Position > 0 and cross_down:
self.SellMarket()
self._cooldown = 30
elif self.Position < 0 and cross_up:
self.BuyMarket()
self._cooldown = 30
# Entry
if self.Position == 0:
if cross_up and rsi_val > 45 and rsi_val < 75:
self.BuyMarket()
self._cooldown = 30
elif cross_down and rsi_val > 25 and rsi_val < 55:
self.SellMarket()
self._cooldown = 30
elif trend_up and self._prev_rsi <= 50 and rsi_val > 50:
self.BuyMarket()
self._cooldown = 30
elif trend_down and self._prev_rsi >= 50 and rsi_val < 50:
self.SellMarket()
self._cooldown = 30
self._prev_ma1 = ma1
self._prev_ma2 = ma2
self._prev_rsi = rsi_val
def CreateClone(self):
return triple_ma_htf_dynamic_smoothing_strategy()