This strategy ports the MetaTrader 5 expert advisor TDSGlobal from MQL/23255/TDSGlobal.mq5 to the StockSharp high-level API. It evaluates momentum on four-hour candles through the MACD line, the MACD histogram (OsMA) and the Force Index. When the indicator combination signals a potential reversal, the strategy submits pending limit orders around the previous candle extremums and manages the resulting position with optional stop-loss, take-profit and trailing-stop logic.
The implementation reproduces the original workflow while adapting it to idiomatic StockSharp constructs such as StrategyParam<T>, candle subscriptions via SubscribeCandles, and asynchronous order handling through the strategy life cycle events.
Trading Logic
Indicator calculations
MACD(12, 26, 9) provides both the MACD line and the histogram (OsMA).
ForceIndex(24) measures the force of the last completed candle.
Each indicator is updated on the close of the selected candle type (default: 4-hour).
Signal detection
The algorithm waits until two historical MACD and OsMA values are available to determine their slope.
A sell setup requires OsMA to increase (osma[1] > osma[2]) while the Force Index of the previous candle is negative.
A buy setup requires OsMA to decrease (osma[1] < osma[2]) while the previous Force Index is positive.
Order placement
Sell limit orders are placed slightly above the previous candle high; buy limit orders slightly below the previous candle low.
If the price is not sufficiently far from the current bid/ask, the order price is pulled to the configured offset buffer (EntryOffsetPips, default 16 pips).
The strategy checks that the distance between the order price and the current bid/ask exceeds the broker safety level approximation (MinDistancePips or the dynamic spread-based value).
Risk controls
Optional stop-loss and take-profit levels are computed from the order price.
When a position is active, a trailing stop can advance by the configured step once price moves beyond the initial trailing distance.
If price hits the protective levels inside a candle, the position is closed with a market order to mimic MetaTrader behaviour.
Order maintenance
Pending orders are cancelled when the OsMA slope turns against the original setup, matching the source EA’s clean-up routine.
Filling one side automatically cancels the opposite pending order to avoid conflicting exposures.
Money Management
Two position sizing approaches are available:
Fixed volume (default OrderVolume = 1) — uses the base Strategy.Volume without adjustments.
Risk-based sizing — when UseRiskSizing is enabled, the strategy estimates the portfolio equity, converts the configured risk percentage into currency risk, and divides it by the stop-loss distance to derive the order volume. Volumes are aligned to the instrument’s volume step to avoid invalid order sizes.
Parameters
Name
Description
Default
OrderVolume
Fixed order size when risk sizing is disabled.
1
UseRiskSizing
Enable money management based on RiskPercent.
true
RiskPercent
Percentage of portfolio equity risked per trade.
3
MacdFastPeriod
Fast EMA length for the MACD line.
12
MacdSlowPeriod
Slow EMA length for the MACD line.
26
MacdSignalPeriod
Signal EMA length for the MACD histogram.
9
ForceLength
EMA smoothing length for the Force Index.
24
StopLossPips
Stop-loss distance in pips (0 disables).
50
TakeProfitPips
Take-profit distance in pips (0 disables).
50
TrailingStopPips
Trailing stop distance in pips (0 disables).
5
TrailingStepPips
Minimum step for trailing updates.
5
EntryOffsetPips
Buffer added around previous highs/lows for pending orders.
16
MinDistancePips
Minimum allowed distance between price and protective levels.
3
PipSize
Pip size used for pip-to-price conversions.
0.0001
CandleType
Candle type processed by the strategy.
4-hour candles
Usage Notes
Add the file CS/TdsGlobalPendingStrategy.cs to your StockSharp project or load it dynamically through the Backtester environment.
Assign the desired security and portfolio before starting the strategy. If UseRiskSizing is enabled, ensure the portfolio provides current equity values.
The strategy requires at least two completed candles to initialise MACD/OsMA slopes. Expect a brief warm-up phase.
Monitor logs for detailed order and position events. The implementation logs key actions (order submission, cancellation, trailing updates) to aid verification against the original EA behaviour.
Differences from the MQL Version
The high-level API manages asynchronous order events, so limit order fills are handled via OnOwnTradeReceived instead of synchronous OrderSend results.
Broker “freeze” and “stops” levels are approximated using the configured minimum distance and a spread-based heuristic because StockSharp does not expose MetaTrader-specific trading limits.
Protective exits execute via market orders when the candle shows a breach. This replicates the EA’s manual stop modification logic without relying on MT5 trade server constraints.
These adjustments keep the trading logic faithful while ensuring the strategy integrates smoothly with the StockSharp framework.
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;
public class TdsGlobalPendingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public TdsGlobalPendingStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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
class tds_global_pending_strategy(Strategy):
def __init__(self):
super(tds_global_pending_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(tds_global_pending_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(tds_global_pending_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return tds_global_pending_strategy()