This strategy is a C# conversion of the MetaTrader 5 expert advisor Exp_TDI-2_ReOpen. It trades using the Trend Direction Index (TDI-2) indicator and applies the original position re-entry logic. The C# port uses the high-level StockSharp API and keeps the core behavior of the MQL version: it reacts to crossings between the TDI momentum line and the TDI index line, scales into profitable positions after a configurable price advance, and manages trades with optional protective stops.
Indicators
TDI-2 indicator – a custom momentum-based indicator implemented in this repository. It builds two lines:
Directional line: Period × SmoothedMomentum, where the momentum equals the applied price minus the price Period bars ago.
Index line: |Directional| − (2 × Period × Smooth(|Momentum|, 2×Period) − |Momentum|).
The indicator supports the following smoothing methods: Simple, Exponential, Smoothed (RMA), and Linear Weighted moving averages.
Supported applied price options replicate the original MQL implementation, including the TrendFollow and Demark formulas.
Trading Logic
On every finished candle the strategy evaluates the TDI-2 values at the bar specified by Signal Bar (default: previous closed candle) and one bar earlier.
When the directional line was above the index line and then crosses below it:
If Allow Long Entries is enabled and no long position is active, the strategy prepares a new long entry.
If a short position exists and Allow Short Exits is enabled, it closes the short position.
When the directional line was below the index line and then crosses above it:
If Allow Short Entries is enabled and no short position is active, the strategy prepares a new short entry.
If a long position exists and Allow Long Exits is enabled, it closes the long position.
Re-entry logic (scale-in):
While holding a long position the strategy tracks the fill price of the latest long trade. If the market moves in favor by Re-entry Step (points) and the number of executed long trades is still below Max Entries, it opens an additional long order with the base volume.
The same logic applies to short positions using the most recent short fill price.
When opening a position while an opposite position exists, the strategy sends a combined market order sized to both close the opposite exposure and establish the new position with the configured base volume.
Optional stop-loss and take-profit levels are activated through StartProtection using the instrument's PriceStep multiplier.
Parameters
Name
Description
Default
Money Management
Base order volume.
0.1
Max Entries
Maximum number of entries per direction (initial trade + re-entries).
10
Stop Loss (points)
Stop-loss distance in instrument points.
1000
Take Profit (points)
Take-profit distance in instrument points.
2000
Slippage (points)
Retained for compatibility; not used in the StockSharp implementation.
10
Re-entry Step (points)
Minimum favorable move before scaling into an existing position.
300
Allow Long Entries / Allow Short Entries
Enable opening long/short positions.
true
Allow Long Exits / Allow Short Exits
Enable closing long/short positions.
true
Candle Type
Candle series used for calculations.
H4 candles
TDI Smoothing
Smoothing method for the TDI-2 indicator.
Simple MA
TDI Period
Momentum lookback period.
20
TDI Phase
Reserved for compatibility with the MQL input (no effect in supported smoothing modes).
15
Applied Price
Price source used by TDI-2.
Close
Signal Bar
Number of closed candles to look back when evaluating crosses.
1
Additional Notes
Only the smoothing methods supported by StockSharp indicators (SMA, EMA, SMMA, LWMA) are implemented. Other MQL modes such as JJMA or T3 are not available.
The TDI Phase parameter is kept for completeness. It does not influence the supported smoothing methods and can be left at its default value.
The Slippage (points) parameter is provided for parity with the original expert advisor but is not used by the high-level API.
The scale-in counters reset automatically whenever the net position returns to zero.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend Direction Index re-entry strategy.
/// Trades based on crossings between the TDI momentum line and the TDI index line.
/// </summary>
public class Tdi2ReOpenStrategy : Strategy
{
private readonly StrategyParam<int> _tdiPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _lastClose;
private decimal? _directional;
private decimal? _index;
private decimal? _prevDirectional;
private decimal? _prevIndex;
public int TdiPeriod { get => _tdiPeriod.Value; set => _tdiPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Tdi2ReOpenStrategy()
{
_tdiPeriod = Param(nameof(TdiPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("TDI Period", "Momentum lookback period", "Indicator")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Data series", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastClose = null;
_directional = null;
_index = null;
_prevDirectional = null;
_prevIndex = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lastClose = null;
_directional = null;
_index = null;
_prevDirectional = null;
_prevIndex = null;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_lastClose is not decimal lastClose)
{
_lastClose = close;
return;
}
var momentum = close - lastClose;
_lastClose = close;
var alpha = 2m / (TdiPeriod + 1m);
if (_directional is not decimal prevDirectionalLine || _index is not decimal prevIndexLine)
{
_directional = momentum;
_index = momentum;
return;
}
var directional = prevDirectionalLine + alpha * (momentum - prevDirectionalLine);
var index = prevIndexLine + alpha * (directional - prevIndexLine);
if (_prevDirectional is not decimal prevDir || _prevIndex is not decimal prevIdx)
{
_directional = directional;
_index = index;
_prevDirectional = prevDirectionalLine;
_prevIndex = prevIndexLine;
return;
}
var crossUp = prevDir <= prevIdx && directional > index;
var crossDown = prevDir >= prevIdx && directional < index;
if (crossUp && Position <= 0)
BuyMarket();
else if (crossDown && Position >= 0)
SellMarket();
_directional = directional;
_index = index;
_prevDirectional = directional;
_prevIndex = index;
}
}
}
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 tdi_2_re_open_strategy(Strategy):
"""TDI momentum/index crossover: custom EMA-smoothed momentum lines."""
def __init__(self):
super(tdi_2_re_open_strategy, self).__init__()
self._tdi_period = self.Param("TdiPeriod", 10).SetGreaterThanZero().SetDisplay("TDI Period", "Momentum lookback period", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(tdi_2_re_open_strategy, self).OnReseted()
self._last_close = 0
self._directional = None
self._index = None
self._prev_dir = None
self._prev_idx = None
def OnStarted2(self, time):
super(tdi_2_re_open_strategy, self).OnStarted2(time)
self._last_close = 0
self._directional = None
self._index = None
self._prev_dir = None
self._prev_idx = None
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._last_close == 0:
self._last_close = close
return
momentum = close - self._last_close
self._last_close = close
alpha = 2.0 / (self._tdi_period.Value + 1.0)
if self._directional is None or self._index is None:
self._directional = momentum
self._index = momentum
return
directional = self._directional + alpha * (momentum - self._directional)
index = self._index + alpha * (directional - self._index)
if self._prev_dir is None or self._prev_idx is None:
self._directional = directional
self._index = index
self._prev_dir = self._directional
self._prev_idx = self._index
return
cross_up = self._prev_dir <= self._prev_idx and directional > index
cross_down = self._prev_dir >= self._prev_idx and directional < index
if cross_up and self.Position <= 0:
self.BuyMarket()
elif cross_down and self.Position >= 0:
self.SellMarket()
self._directional = directional
self._index = index
self._prev_dir = directional
self._prev_idx = index
def CreateClone(self):
return tdi_2_re_open_strategy()