The Pause Trading On Consecutive Loss Strategy reproduces the risk control logic of the MetaTrader 4 expert advisor "Pause Trading On Consecutive Loss". The original script monitored the most recent closed trades, counted how many of them ended with a negative profit, and suspended new orders when the losing streak exceeded a user-defined limit within a short time window. The StockSharp port keeps that behaviour while wrapping it around a minimal momentum entry model so the pause mechanism can be evaluated inside the standalone strategy.
How it works
The strategy subscribes to time-frame candles specified by CandleType. Whenever a finished candle arrives, the closing price is compared to the previous close. If it increased, the strategy attempts a long entry; if it decreased, a short entry is considered. Positions exit whenever a bullish position faces a bearish candle (close below open) or a bearish position faces a bullish candle (close above open).
After every closed position the realised profit of the strategy is inspected. Losing results enqueue their closing timestamp in an internal FIFO list that only stores consecutive losses. Profitable or breakeven exits wipe the list, just as the MQL loop aborted once it encountered a non-losing deal.
When the list reaches ConsecutiveLosses items, the strategy checks whether the time difference between the oldest and the newest loss is within WithinMinutes. If it is, trading is paused until PauseMinutes elapse from the last closing time. During the pause no new market orders are submitted, but the existing position management continues operating so the book can flatten naturally.
Once the pause expires, the list of losses is cleared and trading resumes automatically. The behaviour mimics the original CheckLastNLossDifference and lastOrderCloseTime functions without relying on a persistent order history scan.
The implementation uses StockSharp's high-level candle subscriptions (SubscribeCandles) and the built-in PnL manager to monitor realised profits. A simple queue (Queue<DateTimeOffset>) captures the timestamps of the loss streak while respecting the prohibition on redundant manual history traversal.
Parameters
Parameter
Default
Description
CandleType
5-minute time frame
Candle aggregation used for the simple momentum entries.
OrderVolume
0.1
Volume (in lots/contracts) sent with each entry and exit order.
ConsecutiveLosses
3
Number of back-to-back losing positions required before new trades are paused.
WithinMinutes
20
Maximum number of minutes allowed between the first and the last loss in the streak. A value of zero disables the window check.
PauseMinutes
20
Duration of the trading suspension after the loss streak is detected.
Notes
The queue of loss timestamps is only populated when the strategy is flat and has just realised a loss. Partial closes or profitable trades do not extend the streak, preventing false positives.
The pause timer is evaluated against each finished candle. If PauseMinutes elapse while the strategy is idle, the next candle immediately unlocks trading.
Because the StockSharp version operates on a netting position, the realised PnL difference is derived from PnLManager.RealizedPnL, faithfully mirroring the MetaTrader history lookup without reprocessing the entire order log.
using System;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Pause Trading On Consecutive Loss" MetaTrader expert.
/// Uses simple momentum entries (close vs previous close) with a pause mechanism
/// that halts trading after consecutive losing trades within a time window.
/// </summary>
public class PauseTradingOnConsecutiveLossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _consecutiveLosses;
private readonly StrategyParam<int> _pauseBars;
private decimal? _previousClose;
private int _lossStreak;
private int _pauseCountdown;
private decimal _entryPrice;
private Sides? _entryDirection;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int ConsecutiveLosses
{
get => _consecutiveLosses.Value;
set => _consecutiveLosses.Value = value;
}
public int PauseBars
{
get => _pauseBars.Value;
set => _pauseBars.Value = value;
}
public PauseTradingOnConsecutiveLossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for momentum entries", "General");
_consecutiveLosses = Param(nameof(ConsecutiveLosses), 3)
.SetGreaterThanZero()
.SetDisplay("Consecutive Losses", "Losses before pausing", "Risk");
_pauseBars = Param(nameof(PauseBars), 8)
.SetGreaterThanZero()
.SetDisplay("Pause Bars", "Number of bars to pause after loss streak", "Risk");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousClose = null;
_lossStreak = 0;
_pauseCountdown = 0;
_entryPrice = 0;
_entryDirection = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_previousClose is null)
{
_previousClose = close;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var momentumThreshold = _previousClose.Value * 0.003m;
// Check if we should pause
if (_pauseCountdown > 0)
{
_pauseCountdown--;
_previousClose = close;
return;
}
// Check for exit and track wins/losses
if (Position != 0)
{
var shouldExit = false;
if (Position > 0 && close < _previousClose.Value - momentumThreshold)
shouldExit = true;
else if (Position < 0 && close > _previousClose.Value + momentumThreshold)
shouldExit = true;
if (shouldExit)
{
// Determine if this was a winning or losing trade
var isLoss = false;
if (_entryDirection == Sides.Buy && close < _entryPrice)
isLoss = true;
else if (_entryDirection == Sides.Sell && close > _entryPrice)
isLoss = true;
if (isLoss)
{
_lossStreak++;
if (_lossStreak >= ConsecutiveLosses)
{
_pauseCountdown = PauseBars;
_lossStreak = 0;
}
}
else
{
_lossStreak = 0;
}
// Close position
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_entryDirection = null;
}
}
// New entry: momentum - close > prev close -> buy, close < prev close -> sell
if (Position == 0 && _entryDirection is null)
{
if (close > _previousClose.Value + momentumThreshold)
{
BuyMarket(volume);
_entryPrice = close;
_entryDirection = Sides.Buy;
}
else if (close < _previousClose.Value - momentumThreshold)
{
SellMarket(volume);
_entryPrice = close;
_entryDirection = Sides.Sell;
}
}
_previousClose = close;
}
/// <inheritdoc />
protected override void OnReseted()
{
_previousClose = null;
_lossStreak = 0;
_pauseCountdown = 0;
_entryPrice = 0;
_entryDirection = null;
base.OnReseted();
}
}
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.Strategies import Strategy
class pause_trading_on_consecutive_loss_strategy(Strategy):
def __init__(self):
super(pause_trading_on_consecutive_loss_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60))).SetDisplay("Candle Type", "Timeframe for momentum entries", "General")
self._consecutive_losses = self.Param("ConsecutiveLosses", 3).SetGreaterThanZero().SetDisplay("Consecutive Losses", "Losses before pausing", "Risk")
self._pause_bars = self.Param("PauseBars", 8).SetGreaterThanZero().SetDisplay("Pause Bars", "Number of bars to pause after loss streak", "Risk")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pause_trading_on_consecutive_loss_strategy, self).OnReseted()
self._previous_close = None
self._loss_streak = 0
self._pause_countdown = 0
self._entry_price = 0
self._entry_direction = None
def OnStarted2(self, time):
super(pause_trading_on_consecutive_loss_strategy, self).OnStarted2(time)
self._previous_close = None
self._loss_streak = 0
self._pause_countdown = 0
self._entry_price = 0
self._entry_direction = None
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
if self._previous_close is None:
self._previous_close = close
return
momentum_threshold = float(self._previous_close) * 0.003
if self._pause_countdown > 0:
self._pause_countdown -= 1
self._previous_close = close
return
if self.Position != 0:
should_exit = False
if self.Position > 0 and close < self._previous_close - momentum_threshold:
should_exit = True
elif self.Position < 0 and close > self._previous_close + momentum_threshold:
should_exit = True
if should_exit:
is_loss = False
if self._entry_direction == "buy" and close < self._entry_price:
is_loss = True
elif self._entry_direction == "sell" and close > self._entry_price:
is_loss = True
if is_loss:
self._loss_streak += 1
if self._loss_streak >= self._consecutive_losses.Value:
self._pause_countdown = self._pause_bars.Value
self._loss_streak = 0
else:
self._loss_streak = 0
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_direction = None
if self.Position == 0 and self._entry_direction is None:
if close > self._previous_close + momentum_threshold:
self.BuyMarket()
self._entry_price = close
self._entry_direction = "buy"
elif close < self._previous_close - momentum_threshold:
self.SellMarket()
self._entry_price = close
self._entry_direction = "sell"
self._previous_close = close
def CreateClone(self):
return pause_trading_on_consecutive_loss_strategy()