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
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.
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.
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().
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.
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
Startup
Computes the initial Volume using the balance-aware sizing helper.
Subscribes to the configured candle stream and starts protection mechanisms.
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.
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
Reference Et4MtcV1Strategy in a StockSharp project and assign a Security and Portfolio before starting.
Adjust Lots or other parameters through the provided properties or UI bindings.
Override the placeholder methods or inherit from the class to inject concrete trading logic.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, Momentum
from StockSharp.Algo.Strategies import Strategy
class et4_mtc_v1_strategy(Strategy):
"""
EMA + Momentum crossover strategy.
Buys when price > EMA and momentum crosses above 0.
Sells when price < EMA and momentum crosses below 0.
"""
def __init__(self):
super(et4_mtc_v1_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._momentum_period = self.Param("MomentumPeriod", 14) \
.SetDisplay("Momentum", "Momentum period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_mom = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(et4_mtc_v1_strategy, self).OnReseted()
self._prev_mom = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(et4_mtc_v1_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
mom = Momentum()
mom.Length = self._momentum_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, mom, self._process_candle).Start()
def _process_candle(self, candle, ema_val, mom_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
ema_val = float(ema_val)
mom_val = float(mom_val)
if not self._has_prev:
self._prev_mom = mom_val
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_mom = mom_val
return
if close > ema_val and self._prev_mom <= 0 and mom_val > 0 and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 2
elif close < ema_val and self._prev_mom >= 0 and mom_val < 0 and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = 2
self._prev_mom = mom_val
def CreateClone(self):
return et4_mtc_v1_strategy()