Ver no GitHub

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

  1. 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).
  2. 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.
  3. 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.
  4. 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();
	}
}