Pausar a negociação na estratégia de perdas consecutivas
A Estratégia Pause Trading On Consecutive Loss reproduz a lógica de controle de risco do consultor especialista MetaTrader 4 "Pause Trading On Consecutive Loss". O script original monitorava as negociações fechadas mais recentes, contava quantas delas terminavam com lucro negativo e suspendia novos pedidos quando a seqüência de perdas ultrapassava um limite definido pelo usuário em um curto espaço de tempo. A porta StockSharp mantém esse comportamento enquanto o envolve em um modelo de entrada de impulso mínimo para que o mecanismo de pausa possa ser avaliado dentro da estratégia independente.
Como funciona
A estratégia assina velas de prazo especificadas por CandleType. Sempre que chega uma vela finalizada, o preço de fechamento é comparado com o fechamento anterior. Se aumentou, a estratégia tenta uma entrada longa; se diminuiu, uma entrada curta é considerada. As posições saem sempre que uma posição de alta enfrenta uma vela de baixa (fechamento abaixo da abertura) ou uma posição de baixa enfrenta uma vela de alta (fechamento acima da abertura).
Após cada posição fechada, o lucro realizado da estratégia é inspecionado. Os resultados perdedores enfileiram seu carimbo de data/hora de fechamento em uma lista FIFO interna que armazena apenas perdas consecutivas. As saídas lucrativas ou de equilíbrio apagam a lista, assim como o loop MQL foi abortado ao encontrar um negócio sem perdas.
Quando a lista atinge ConsecutiveLosses itens, a estratégia verifica se a diferença de tempo entre a perda mais antiga e a mais recente está dentro de WithinMinutes. Se for, a negociação será pausada até PauseMinutes decorrer do último horário de fechamento. Durante a pausa, nenhuma nova ordem de mercado é enviada, mas o gerenciamento de posição existente continua operando para que a carteira possa se estabilizar naturalmente.
Assim que a pausa expirar, a lista de perdas será apagada e a negociação será retomada automaticamente. O comportamento imita as funções originais CheckLastNLossDifference e lastOrderCloseTime sem depender de uma verificação persistente do histórico de pedidos.
A implementação usa assinaturas de vela de alto nível de StockSharp (SubscribeCandles) e o gerenciador de PnL integrado para monitorar os lucros realizados. Uma fila simples (Queue<DateTimeOffset>) captura os carimbos de data e hora da seqüência de perdas, respeitando a proibição de travessia manual redundante do histórico.
Parâmetros
Parâmetro
Padrão
Descrição
CandleType
Período de 5 minutos
Agregação de velas usada para entradas de impulso simples.
OrderVolume
0.1
Volume (em lotes/contratos) enviado com cada ordem de entrada e saída.
ConsecutiveLosses
3
Número de posições perdedoras consecutivas necessárias antes que novas negociações sejam pausadas.
WithinMinutes
20
Número máximo de minutos permitidos entre a primeira e a última derrota da seqüência. Um valor zero desativa a verificação da janela.
PauseMinutes
20
Duração da suspensão da negociação após a detecção da sequência de perdas.
Notas
A fila de carimbos de data/hora de perda só é preenchida quando a estratégia está plana e acaba de perceber uma perda. Fechamentos parciais ou negociações lucrativas não prolongam a sequência, evitando falsos positivos.
O temporizador de pausa é avaliado em relação a cada vela finalizada. Se PauseMinutes decorrer enquanto a estratégia estiver ociosa, a próxima vela desbloqueia imediatamente a negociação.
Como a versão StockSharp opera em uma posição de compensação, a diferença de PnL realizada é derivada de PnLManager.RealizedPnL, espelhando fielmente a pesquisa do histórico de MetaTrader sem reprocessar todo o registro do pedido.
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()