Ver en GitHub

T3MA(MTC) Strategy

Converted from the MetaTrader 4 expert advisor T3MA(MTC).mq4 (directory MQL/7904). The original robot trades signals from the "T3MA-ALARM" indicator: it builds a double-smoothed exponential moving average and places an order whenever the slope of that curve flips from falling to rising or vice versa. The StockSharp port mirrors the same logic with idiomatic high-level APIs.

Trading idea

  1. Build a first EMA using the selected candle type and period.
  2. Smooth that series with a second EMA of the same period.
  3. Compare the smoothed value with the previous one (optionally shifted by MaShift).
  4. When the slope changes direction, the strategy records a signal. Orders are executed after the configured CalculationBarOffset delay, reproducing the CalculationBarIndex parameter of the EA.
  5. Each signal uses the bar's low (for a long entry) or high (for a short entry) as a unique marker to avoid duplicate trades, just like the LastOrder variable in MetaTrader.

Porting details

  • Uses two ExponentialMovingAverage instances to emulate the T3MA-ALARM smoothing chain.
  • Maintains a tiny queue of recent smoothed values to support the MaShift lookback.
  • Signals are stored in a FIFO queue and executed after the requested number of finished candles.
  • Protective orders are managed through StartProtection with distances expressed in price steps, matching MetaTrader points.
  • The AllowMultiplePositions flag reproduces the MultiPositions input: when disabled, the strategy waits until the net position is flat before acting on a new signal.

Parameters

  • MaPeriod – EMA length used for both smoothing passes (default: 4).
  • MaShift – number of bars to shift the smoothed series before comparing its slope (default: 0).
  • CalculationBarOffset – delay (in finished candles) between detecting a signal and sending the order (default: 1).
  • TradeVolume – base order volume in lots (default: 1).
  • UseStopLoss / StopLossPoints – enable and distance of the stop loss in price steps (default: enabled, 40 steps).
  • UseTakeProfit / TakeProfitPoints – enable and distance of the take profit in price steps (default: enabled, 11 steps).
  • AllowMultiplePositions – allow stacking positions even when an opposite one is open (default: enabled).
  • CandleType – timeframe or data type used to feed the indicator chain (default: 5-minute candles).

Trading workflow

  1. Subscribe to the chosen candle series and feed closing prices through the double EMA chain.
  2. Track the current slope direction and generate a signal when it flips.
  3. Push each signal (or the absence of one) into the delay queue so that executions happen exactly after CalculationBarOffset completed candles, just like the MQL4 script reads older indicator buffers.
  4. When a matured signal is executed:
    • Skip it if trading is disabled, the platform is not ready, or AllowMultiplePositions is off while a net position is already open.
    • Ensure the signal marker differs from the previous one to prevent duplicates.
    • Send a market order (BuyMarket/SellMarket) with the configured volume. Protective stops are attached automatically when enabled.

Notes

  • Price comparisons use a small decimal tolerance to avoid floating-point artifacts when checking the LastOrder analogue.
  • The strategy does not auto-close opposite positions when AllowMultiplePositions is disabled, mimicking the original EA that relied on protective exits.
  • Visualization of candles and own trades is available when the charting subsystem is present.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class T3MaMtc9Strategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevEma;
	private decimal _prevMom;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public T3MaMtc9Strategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
		_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 150).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevEma = default;
		_prevMom = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevEma = 0;
		_prevMom = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var mom = new Momentum { Length = MomentumPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, mom, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema, decimal mom)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevEma = ema; _prevMom = mom; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevEma = ema;
			_prevMom = mom;
			return;
		}

		if (close > ema && _prevMom <= 0 && mom > 0 && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (close < ema && _prevMom >= 0 && mom < 0 && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevEma = ema;
		_prevMom = mom;
	}
}