View on GitHub

Et4 MTC v1 Strategy (StockSharp Conversion)

Overview

  • Origin: MetaTrader 4 expert advisor et4_MTC_v1.mq4 from the GlobeInvestFund collection.
  • Purpose: Provide a StockSharp-native template that mirrors the original advisor's money-management helpers and timing safeguards while leaving trade entry/exit logic open for further development.
  • Trading Style: Skeleton strategy – no automatic entries are generated by default. The class focuses on enforcing timing restrictions and replicating the parameter interface of the MQL4 script so it can serve as a foundation for custom rules.

Core Features

  1. Parameter Parity
    • Exposes TakeProfit, StopLoss, Slippage, Lots, and EnableLogging properties that map one-to-one with the expert's extern variables.
    • Adds TradeCooldown to describe the hard-coded 30-second delay between operations in the source code.
    • Publishes the chart data context through CandleType to emulate the "current timeframe" behavior of MetaTrader charts.
  2. Balance-Based Position Sizing
    • Supports negative lot inputs (original script default) to derive order volume from account balance: floor((balance / 1000 * |Lots|) / 10) / 10, with a 0.1 lot minimum.
  3. Trade Cooldown Enforcement
    • Blocks any further trading attempts until TradeCooldown elapses after the most recent order activity (registration, modification, cancellation, or filled trade). This mirrors the CurTime() - LastTradeTime < 30 guard in start().
  4. New-Candle Detection
    • Keeps the CheckLevels semantics by marking IsNewCandle through a time comparison between subsequent finished candles. While the flag is internal, the hooks in OpenPosition, ManagePosition, and ClosePosition can use it when custom logic is added.
  5. High-Level StockSharp API Usage
    • Utilizes SubscribeCandles().Bind(...) for data delivery.
    • Applies StartProtection() once at start-up, following framework best practices.
    • Does not allocate custom collections or request indicator history explicitly, aligning with project-wide guidelines.

Parameter Reference

Property Default Optimizable Description
TakeProfit 150 ✔️ Target distance in points (placeholder for custom exit rules).
Lots -10 ✔️ Fixed lots when ≥ 0; balance-proportional sizing when negative.
StopLoss 50 ✔️ Stop distance in points, ready for extension logic.
Slippage 3 ✖️ Execution tolerance in points; preserved for compatibility.
EnableLogging false ✖️ Prints informational messages when cooldown blocks trades.
TradeCooldown 30 seconds ✖️ Minimal delay between consecutive trades.
CandleType 1-minute time-frame candles ✖️ Market data subscription used for candle timing.

Execution Flow

  1. Startup
    • Computes the initial Volume using the balance-aware sizing helper.
    • Subscribes to the configured candle stream and starts protection mechanisms.
  2. On Candle Close
    • Confirms the candle is finished before proceeding (equivalent to Time[0] closing in MT4).
    • Updates the new-candle tracker (_isNewCandle).
    • Checks IsFormedAndOnlineAndAllowTrading() to respect engine state.
    • Aborts if the trade cooldown is active, logging the next available time when enabled.
    • Executes placeholder hooks (OpenPosition, ManagePosition, ClosePosition), returning early when any step performs an action.
  3. Order & Trade Callbacks
    • OnOrderRegistered, OnOrderChanged, OnOrderCanceled, and OnNewMyTrade refresh _lastTradeTime, ensuring every type of operation resets the cooldown just as the wrapper functions (MOrderSend, MOrderModify, etc.) did in the original code.

Extending the Template

  • Implement entry logic inside OpenPosition (return true after submitting orders to stop further processing on the same candle).
  • Insert stop-management behavior inside ManagePosition using the preserved parameters.
  • Populate ClosePosition with exit rules. The method currently returns false to match the dormant behavior of the source script.
  • Use _isNewCandle if the rules must trigger once per bar.

Porting Notes

  • The MQL4 expert shipped without trading rules; only infrastructure routines were present. Consequently, the StockSharp conversion prioritizes parity of supporting features rather than adding speculative indicators.
  • All comments are written in English, complying with repository standards.
  • Tabs are used for indentation to match the style guidelines defined in AGENTS.md.
  • Python translation is intentionally omitted, per the conversion request.

Usage Steps

  1. Reference Et4MtcV1Strategy in a StockSharp project and assign a Security and Portfolio before starting.
  2. Adjust Lots or other parameters through the provided properties or UI bindings.
  3. Override the placeholder methods or inherit from the class to inject concrete trading logic.
  4. Run the strategy; the cooldown guard ensures no back-to-back operations within the specified interval.

Testing

  • No automated tests accompany this template because the upstream source also lacked executable rules. Manual strategy extensions should introduce relevant tests when concrete trading behavior is implemented.
using System;

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

namespace StockSharp.Samples.Strategies;

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

	private decimal _prevMom;
	private bool _hasPrev;
	private int _cooldown;

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

	public Et4MtcV1Strategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA trend filter", "Indicators");
		_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		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;
		if (!IsFormedAndOnlineAndAllowTrading()) return;

		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevMom = mom; _hasPrev = true; return; }
		if (_cooldown > 0)
		{
			_cooldown--;
			_prevMom = mom;
			return;
		}

		if (close > ema && _prevMom <= 0 && mom > 0 && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
			_cooldown = 2;
		}
		else if (close < ema && _prevMom >= 0 && mom < 0 && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
			_cooldown = 2;
		}

		_prevMom = mom;
	}
}