Three Signal Directional Trend Strategy
The Three Signal Directional Trend strategy combines MACD, Stochastic oscillator and moving average rate of change to determine trend direction. Each indicator votes for long or short conditions and positions are opened when at least two indicators agree. The method aims to capture broad directional moves while filtering noise using multiple confirmation signals.
Details
- Entry Criteria:
- At least two out of three signals agree.
- Long: MACD signal rising, Stochastic below oversold, MA ROC positive.
- Short: MACD signal falling, Stochastic above overbought, MA ROC negative.
- Long/Short: Both sides.
- Exit Criteria:
- Opposite signal.
- Stops: None.
- Default Values:
AvgLength= 50RocLength= 1AvgRocLength= 10StochLength= 14SmoothK= 3Overbought= 80Oversold= 20MacdFastLength= 12MacdSlowLength= 26MacdAvgLength= 9
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: MACD, Stochastic, SMA, ROC
- Stops: None
- Complexity: Low
- Timeframe: Any
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Three Signal Directional Trend Strategy.
/// Combines MACD, Stochastic, and RSI signals.
/// Enters when at least 2 of 3 indicators agree on direction.
/// </summary>
public class ThreeSignalDirectionalTrendStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdAvgLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _cooldownBars;
private MovingAverageConvergenceDivergenceSignal _macd;
private StochasticOscillator _stochastic;
private RelativeStrengthIndex _rsi;
private decimal _prevMacdSignal;
private bool _macdInit;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MacdFastLength
{
get => _macdFastLength.Value;
set => _macdFastLength.Value = value;
}
public int MacdSlowLength
{
get => _macdSlowLength.Value;
set => _macdSlowLength.Value = value;
}
public int MacdAvgLength
{
get => _macdAvgLength.Value;
set => _macdAvgLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public ThreeSignalDirectionalTrendStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_macdFastLength = Param(nameof(MacdFastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA length", "MACD");
_macdSlowLength = Param(nameof(MacdSlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA length", "MACD");
_macdAvgLength = Param(nameof(MacdAvgLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal EMA length", "MACD");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd = null;
_stochastic = null;
_rsi = null;
_prevMacdSignal = 0;
_macdInit = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = MacdFastLength }, LongMa = { Length = MacdSlowLength } },
SignalMa = { Length = MacdAvgLength }
};
_stochastic = new StochasticOscillator
{
K = { Length = 14 },
D = { Length = 3 }
};
_rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, _stochastic, _rsi, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue macdVal, IIndicatorValue stochVal, IIndicatorValue rsiVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!_macd.IsFormed || !_stochastic.IsFormed || !_rsi.IsFormed)
return;
if (macdVal.IsEmpty || stochVal.IsEmpty || rsiVal.IsEmpty)
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdVal;
if (macdTyped.Signal is not decimal macdSignal)
return;
var stochTyped = (StochasticOscillatorValue)stochVal;
if (stochTyped.K is not decimal stochK)
return;
var rsi = rsiVal.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevMacdSignal = macdSignal;
_macdInit = true;
return;
}
if (!_macdInit)
{
_prevMacdSignal = macdSignal;
_macdInit = true;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevMacdSignal = macdSignal;
return;
}
var longCount = 0;
var shortCount = 0;
// MACD signal rising/falling
if (macdSignal > _prevMacdSignal)
longCount++;
else if (macdSignal < _prevMacdSignal)
shortCount++;
// Stochastic oversold/overbought
if (stochK <= 20)
longCount++;
else if (stochK >= 80)
shortCount++;
// RSI direction
if (rsi < 40)
longCount++;
else if (rsi > 60)
shortCount++;
// Trade when at least 2 signals agree
if (longCount >= 2 && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (shortCount >= 2 && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_prevMacdSignal = macdSignal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (MovingAverageConvergenceDivergenceSignal, StochasticOscillator,
RelativeStrengthIndex, IndicatorHelper)
from StockSharp.Algo.Strategies import Strategy
class three_signal_directional_trend_strategy(Strategy):
"""Three Signal Directional Trend Strategy."""
def __init__(self):
super(three_signal_directional_trend_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._macd_fast_length = self.Param("MacdFastLength", 12) \
.SetDisplay("MACD Fast", "Fast EMA length", "MACD")
self._macd_slow_length = self.Param("MacdSlowLength", 26) \
.SetDisplay("MACD Slow", "Slow EMA length", "MACD")
self._macd_avg_length = self.Param("MacdAvgLength", 9) \
.SetDisplay("MACD Signal", "Signal EMA length", "MACD")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._macd = None
self._stochastic = None
self._rsi = None
self._prev_macd_signal = 0.0
self._macd_init = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(three_signal_directional_trend_strategy, self).OnReseted()
self._macd = None
self._stochastic = None
self._rsi = None
self._prev_macd_signal = 0.0
self._macd_init = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(three_signal_directional_trend_strategy, self).OnStarted2(time)
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = int(self._macd_fast_length.Value)
self._macd.Macd.LongMa.Length = int(self._macd_slow_length.Value)
self._macd.SignalMa.Length = int(self._macd_avg_length.Value)
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = 14
self._stochastic.D.Length = 3
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._macd, self._stochastic, self._rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, macd_val, stoch_val, rsi_val):
if candle.State != CandleStates.Finished:
return
if not self._macd.IsFormed or not self._stochastic.IsFormed or not self._rsi.IsFormed:
return
if macd_val.IsEmpty or stoch_val.IsEmpty or rsi_val.IsEmpty:
return
if macd_val.Signal is None:
return
macd_signal = float(macd_val.Signal)
if stoch_val.K is None:
return
stoch_k = float(stoch_val.K)
rsi = float(IndicatorHelper.ToDecimal(rsi_val))
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_macd_signal = macd_signal
self._macd_init = True
return
if not self._macd_init:
self._prev_macd_signal = macd_signal
self._macd_init = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_macd_signal = macd_signal
return
cooldown = int(self._cooldown_bars.Value)
long_count = 0
short_count = 0
if macd_signal > self._prev_macd_signal:
long_count += 1
elif macd_signal < self._prev_macd_signal:
short_count += 1
if stoch_k <= 20:
long_count += 1
elif stoch_k >= 80:
short_count += 1
if rsi < 40:
long_count += 1
elif rsi > 60:
short_count += 1
if long_count >= 2 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif short_count >= 2 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_macd_signal = macd_signal
def CreateClone(self):
return three_signal_directional_trend_strategy()