RD Trend Trigger Strategy
The RD Trend Trigger strategy uses the RD-TrendTrigger oscillator to capture trend reversals or level breakouts depending on the selected mode. In twist mode, trades follow changes in oscillator direction; in disposition mode, trades occur when the oscillator crosses predefined levels.
Details
- Entry Criteria:
- Twist mode: Enter long when the oscillator turns upward; enter short when it turns downward.
- Disposition mode: Enter long when the oscillator rises above
HighLevel; enter short when it falls belowLowLevel.
- Long/Short: Both.
- Exit Criteria:
- Opposite signals or explicit exit conditions in disposition mode when the oscillator rises above
LowLevel.
- Opposite signals or explicit exit conditions in disposition mode when the oscillator rises above
- Stops: None by default; protection can be enabled externally.
- Default Values:
Regress= 15T3Length= 5T3VolumeFactor= 0.7HighLevel= 50LowLevel= -50Mode= TwistCandleType= 4-hour candles
- Filters:
- Category: Trend-following
- Direction: Long & Short
- Indicators: Custom RD-TrendTrigger (based on highs/lows and Tillson T3)
- Stops: Optional
- Complexity: Medium
- Timeframe: Any
- 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>
/// RD Trend Trigger oscillator based strategy.
/// </summary>
public class RdTrendTriggerStrategy : Strategy
{
private readonly StrategyParam<int> _regress;
private readonly StrategyParam<int> _t3Length;
private readonly StrategyParam<decimal> _t3VolumeFactor;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<TriggerModes> _mode;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _highs = new();
private readonly Queue<decimal> _lows = new();
private decimal? _prev1;
private decimal? _prev2;
private decimal? _e1, _e2, _e3, _e4, _e5, _e6;
/// <summary>
/// Length for high/low segments.
/// </summary>
public int Regress
{
get => _regress.Value;
set => _regress.Value = value;
}
/// <summary>
/// T3 smoothing length.
/// </summary>
public int T3Length
{
get => _t3Length.Value;
set => _t3Length.Value = value;
}
/// <summary>
/// T3 volume factor.
/// </summary>
public decimal T3VolumeFactor
{
get => _t3VolumeFactor.Value;
set => _t3VolumeFactor.Value = value;
}
/// <summary>
/// Upper threshold for disposition mode.
/// </summary>
public decimal HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Lower threshold for disposition mode.
/// </summary>
public decimal LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Trading mode.
/// </summary>
public TriggerModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="RdTrendTriggerStrategy"/>.
/// </summary>
public RdTrendTriggerStrategy()
{
_regress = Param(nameof(Regress), 15)
.SetGreaterThanZero()
.SetDisplay("Regress", "Length for high/low segments", "Indicator")
.SetOptimize(10, 30, 5);
_t3Length = Param(nameof(T3Length), 5)
.SetGreaterThanZero()
.SetDisplay("T3 Length", "Tillson T3 smoothing depth", "Indicator")
.SetOptimize(3, 10, 1);
_t3VolumeFactor = Param(nameof(T3VolumeFactor), 0.7m)
.SetDisplay("T3 Volume Factor", "Tillson T3 volume factor", "Indicator");
_highLevel = Param(nameof(HighLevel), 50m)
.SetDisplay("High Level", "Upper threshold", "Signal");
_lowLevel = Param(nameof(LowLevel), -50m)
.SetDisplay("Low Level", "Lower threshold", "Signal");
_mode = Param(nameof(Mode), TriggerModes.Twist)
.SetDisplay("Mode", "Trading mode", "Signal");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_prev1 = null;
_prev2 = null;
_e1 = _e2 = _e3 = _e4 = _e5 = _e6 = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Enqueue(candle.HighPrice);
_lows.Enqueue(candle.LowPrice);
var maxCount = Regress * 2;
if (_highs.Count > maxCount)
{
_highs.Dequeue();
_lows.Dequeue();
}
if (_highs.Count < maxCount)
return;
var highestRecent = GetMax(_highs, 0, Regress);
var highestOlder = GetMax(_highs, Regress, Regress);
var lowestRecent = GetMin(_lows, 0, Regress);
var lowestOlder = GetMin(_lows, Regress, Regress);
var buyPower = highestRecent - lowestOlder;
var sellPower = highestOlder - lowestRecent;
var res = buyPower + sellPower;
var ttf = res == 0m ? 0m : (buyPower - sellPower) / (0.5m * res) * 100m;
var e1 = UpdateEma(ref _e1, ttf, T3Length);
var e2 = UpdateEma(ref _e2, e1, T3Length);
var e3 = UpdateEma(ref _e3, e2, T3Length);
var e4 = UpdateEma(ref _e4, e3, T3Length);
var e5 = UpdateEma(ref _e5, e4, T3Length);
var e6 = UpdateEma(ref _e6, e5, T3Length);
var v = T3VolumeFactor;
var c1 = -v * v * v;
var c2 = 3m * v * v + 3m * v * v * v;
var c3 = -6m * v * v - 3m * v - 3m * v * v * v;
var c4 = 1m + 3m * v + v * v * v + 3m * v * v;
var t3 = c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3;
switch (Mode)
{
case TriggerModes.Twist:
{
if (_prev2.HasValue && _prev1.HasValue)
{
if (t3 > _prev1 && _prev1 <= _prev2 && Position <= 0)
{
if (Position < 0)
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
BuyMarket();
}
else if (t3 < _prev1 && _prev1 >= _prev2 && Position >= 0)
{
if (Position > 0)
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
SellMarket();
}
}
_prev2 = _prev1;
_prev1 = t3;
break;
}
case TriggerModes.Disposition:
{
if (_prev1.HasValue)
{
if (t3 > HighLevel && _prev1 <= HighLevel && Position <= 0)
{
if (Position < 0)
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
BuyMarket();
}
else if (t3 < LowLevel && _prev1 >= LowLevel && Position >= 0)
{
if (Position > 0)
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
SellMarket();
}
else if (t3 > LowLevel && Position < 0)
{
if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
}
}
_prev1 = t3;
break;
}
}
}
private static decimal GetMax(IEnumerable<decimal> values, int start, int count)
{
var i = 0;
var max = decimal.MinValue;
foreach (var v in values)
{
if (i >= start && i < start + count && v > max)
max = v;
i++;
}
return max;
}
private static decimal GetMin(IEnumerable<decimal> values, int start, int count)
{
var i = 0;
var min = decimal.MaxValue;
foreach (var v in values)
{
if (i >= start && i < start + count && v < min)
min = v;
i++;
}
return min;
}
private static decimal UpdateEma(ref decimal? prev, decimal input, int length)
{
var alpha = 2m / (length + 1m);
var value = prev is null ? input : alpha * input + (1m - alpha) * prev.Value;
prev = value;
return value;
}
/// <summary>
/// Modes of RD Trend Trigger.
/// </summary>
public enum TriggerModes
{
/// <summary>
/// Trade on oscillator direction change.
/// </summary>
Twist,
/// <summary>
/// Trade on level breakouts.
/// </summary>
Disposition
}
}
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.Strategies import Strategy
class rd_trend_trigger_strategy(Strategy):
def __init__(self):
super(rd_trend_trigger_strategy, self).__init__()
self._regress = self.Param("Regress", 15) \
.SetDisplay("Regress", "Length for high/low segments", "Indicator")
self._t3_length = self.Param("T3Length", 5) \
.SetDisplay("T3 Length", "Tillson T3 smoothing depth", "Indicator")
self._t3_volume_factor = self.Param("T3VolumeFactor", 0.7) \
.SetDisplay("T3 Volume Factor", "Tillson T3 volume factor", "Indicator")
self._high_level = self.Param("HighLevel", 50.0) \
.SetDisplay("High Level", "Upper threshold", "Signal")
self._low_level = self.Param("LowLevel", -50.0) \
.SetDisplay("Low Level", "Lower threshold", "Signal")
self._mode = self.Param("Mode", 0) \
.SetDisplay("Mode", "Trading mode (0=Twist, 1=Disposition)", "Signal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "Parameters")
self._highs = []
self._lows = []
self._prev1 = None
self._prev2 = None
self._e1 = None
self._e2 = None
self._e3 = None
self._e4 = None
self._e5 = None
self._e6 = None
@property
def regress(self):
return self._regress.Value
@property
def t3_length(self):
return self._t3_length.Value
@property
def t3_volume_factor(self):
return self._t3_volume_factor.Value
@property
def high_level(self):
return self._high_level.Value
@property
def low_level(self):
return self._low_level.Value
@property
def mode(self):
return self._mode.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rd_trend_trigger_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._prev1 = None
self._prev2 = None
self._e1 = None
self._e2 = None
self._e3 = None
self._e4 = None
self._e5 = None
self._e6 = None
def _update_ema(self, prev, inp, length):
alpha = 2.0 / (float(length) + 1.0)
if prev is None:
return inp
return alpha * inp + (1.0 - alpha) * prev
def OnStarted2(self, time):
super(rd_trend_trigger_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
reg = int(self.regress)
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
max_count = reg * 2
if len(self._highs) > max_count:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < max_count:
return
highest_recent = max(self._highs[:reg])
highest_older = max(self._highs[reg:])
lowest_recent = min(self._lows[:reg])
lowest_older = min(self._lows[reg:])
buy_power = highest_recent - lowest_older
sell_power = highest_older - lowest_recent
res = buy_power + sell_power
ttf = 0.0 if res == 0 else (buy_power - sell_power) / (0.5 * res) * 100.0
t3l = int(self.t3_length)
self._e1 = self._update_ema(self._e1, ttf, t3l)
self._e2 = self._update_ema(self._e2, self._e1, t3l)
self._e3 = self._update_ema(self._e3, self._e2, t3l)
self._e4 = self._update_ema(self._e4, self._e3, t3l)
self._e5 = self._update_ema(self._e5, self._e4, t3l)
self._e6 = self._update_ema(self._e6, self._e5, t3l)
v = float(self.t3_volume_factor)
c1 = -v * v * v
c2 = 3 * v * v + 3 * v * v * v
c3 = -6 * v * v - 3 * v - 3 * v * v * v
c4 = 1 + 3 * v + v * v * v + 3 * v * v
t3 = c1 * self._e6 + c2 * self._e5 + c3 * self._e4 + c4 * self._e3
if int(self.mode) == 0: # Twist
if self._prev2 is not None and self._prev1 is not None:
if t3 > self._prev1 and self._prev1 <= self._prev2 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif t3 < self._prev1 and self._prev1 >= self._prev2 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev2 = self._prev1
self._prev1 = t3
else: # Disposition
hl = float(self.high_level)
ll = float(self.low_level)
if self._prev1 is not None:
if t3 > hl and self._prev1 <= hl and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif t3 < ll and self._prev1 >= ll and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif t3 > ll and self.Position < 0:
self.BuyMarket()
self._prev1 = t3
def CreateClone(self):
return rd_trend_trigger_strategy()