Converted from the MetaTrader 5 expert Exp_VortexIndicator_MMRec_Duplex.mq5 (MQL ID 23180).
Maintains two independent Vortex indicator streams: one dedicated to long trades and one to short trades. Each stream has its own timeframe, length, and bar shift so that bullish and bearish logic can be tuned separately.
Replicates the "MMRec" money-management recovery module from the original EA. The strategy tracks the latest trade results per direction and temporarily switches to a reduced order size after a configurable number of losses.
Signal Logic
Subscribe to the configured candle type for each stream and calculate the Vortex indicator (VI+ and VI-).
Long entries: when the previous bar had VI+ below or equal to VI- and the current bar closes with VI+ above VI- (bullish crossover). Entries are allowed only if AllowLongEntries is enabled.
Long exits: when VI- rises above VI+ on the evaluated bar, provided that AllowLongExits is enabled.
Short entries: when the previous bar had VI+ above or equal to VI- and the current bar closes with VI+ below VI- (bearish crossover), controlled by AllowShortEntries.
Short exits: when VI+ climbs back above VI- on the evaluated bar, controlled by AllowShortExits.
Each direction keeps its own stop-loss and take-profit levels measured in price steps. Hitting either level immediately closes the position and registers the result for the recovery counters.
Money-Management Recovery
The original EA inspects a sliding window of past trades to decide whether the next order should use the normal or reduced volume. This port mirrors the same behaviour.
For long trades the queue stores up to LongTotalTrigger most recent PnL results. If at least LongLossTrigger of them are losing trades, the next long entry uses LongSmallMoneyManagement; otherwise it uses LongMoneyManagement.
Short trades repeat the same logic with ShortTotalTrigger, ShortLossTrigger, ShortSmallMoneyManagement, and ShortMoneyManagement.
When the trigger values are zero the queues are cleared and the base volume is always used.
Margin Modes
MarginModeOption describes how the money-management value is turned into an executable volume:
FreeMargin (0): treat the value as a fraction of capital (approximation of the original "free margin" mode).
Balance (1): identical to FreeMargin in this port; uses the current portfolio value.
LossFreeMargin (2): risk a capital fraction using the configured stop-loss distance. Falls back to price-based sizing if the stop distance is zero.
LossBalance (3): same as LossFreeMargin in this implementation.
Lot (4): interpret the value directly as order volume.
All calculated sizes are normalised using the instrument's volume step as well as minimum and maximum volume constraints.
Parameters
Parameter
Default
Description
LongCandleType
H4
Timeframe used to feed the long-side Vortex indicator.
ShortCandleType
H4
Timeframe used to feed the short-side Vortex indicator.
LongLength
14
Period of the Vortex indicator for long signals.
ShortLength
14
Period of the Vortex indicator for short signals.
LongSignalBar
1
Closed-bar offset evaluated for long crossovers (0 = latest closed bar).
ShortSignalBar
1
Closed-bar offset evaluated for short crossovers.
AllowLongEntries
true
Enable long entries when the bullish crossover appears.
AllowLongExits
true
Enable closing long positions when VI- dominates VI+.
AllowShortEntries
true
Enable short entries when the bearish crossover appears.
AllowShortExits
true
Enable closing short positions when VI+ dominates VI-.
LongTotalTrigger
5
Number of recent long trades inspected by the recovery counter.
LongLossTrigger
3
Losing long trades required before switching to the reduced long volume.
LongMoneyManagement
0.1
Base money-management value for long trades.
LongSmallMoneyManagement
0.01
Reduced money-management value after a long losing streak.
LongMarginMode
Lot
Interpretation of the long money-management value (see modes above).
LongStopLossSteps
1000
Protective distance below the long entry expressed in price steps.
LongTakeProfitSteps
2000
Take-profit distance above the long entry expressed in price steps.
LongSlippageSteps
10
Informational slippage allowance for long orders (not used for sizing).
ShortTotalTrigger
5
Number of recent short trades inspected by the recovery counter.
ShortLossTrigger
3
Losing short trades required before switching to the reduced short volume.
ShortMoneyManagement
0.1
Base money-management value for short trades.
ShortSmallMoneyManagement
0.01
Reduced money-management value after a short losing streak.
ShortMarginMode
Lot
Interpretation of the short money-management value.
ShortStopLossSteps
1000
Protective distance above the short entry expressed in price steps.
ShortTakeProfitSteps
2000
Take-profit distance below the short entry expressed in price steps.
ShortSlippageSteps
10
Informational slippage allowance for short orders.
Implementation Notes
Built entirely on the StockSharp high-level API. Candle subscriptions drive the Vortex indicators through Bind, which delivers finished bars before any decision is made.
The trade-recovery logic stores per-direction profit series in queues and mirrors the MetaTrader BuyTradeMMRecounterS / SellTradeMMRecounterS functions.
Stop-loss and take-profit levels are recalculated in price units (instrument price step × configured steps) and enforced on every incoming candle.
Order volumes are normalised via the security's VolumeStep, MinVolume, and MaxVolume constraints to avoid invalid submissions.
Slippage parameters are preserved for documentation purposes but are not used directly by the StockSharp order handlers.
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;
/// <summary>
/// Vortex Indicator MMRec Duplex strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class VortexIndicatorMmrecDuplexStrategy : 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 VortexIndicatorMmrecDuplexStrategy()
{
_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 vortex_indicator_mmrec_duplex_strategy(Strategy):
def __init__(self):
super(vortex_indicator_mmrec_duplex_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(vortex_indicator_mmrec_duplex_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(vortex_indicator_mmrec_duplex_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 vortex_indicator_mmrec_duplex_strategy()