Pausar la negociación en la estrategia de pérdidas consecutivas
La Estrategia de pausar operaciones con pérdidas consecutivas reproduce la lógica de control de riesgos del asesor experto MetaTrader 4 "Pausar operaciones con pérdidas consecutivas". El script original monitoreaba las operaciones cerradas más recientes, contaba cuántas de ellas terminaron con una ganancia negativa y suspendía nuevas órdenes cuando la racha de pérdidas excedía un límite definido por el usuario en un corto período de tiempo. El puerto StockSharp mantiene ese comportamiento mientras lo envuelve alrededor de un modelo de entrada de impulso mínimo para que el mecanismo de pausa pueda evaluarse dentro de la estrategia independiente.
como funciona
La estrategia se suscribe a velas de marco temporal especificadas por CandleType. Cada vez que llega una vela terminada, el precio de cierre se compara con el cierre anterior. Si aumentó, la estrategia intenta una entrada larga; si disminuyó, se considera una entrada corta. Las posiciones salen siempre que una posición alcista se enfrenta a una vela bajista (cierre por debajo de la apertura) o una posición bajista se enfrenta a una vela alcista (cierre por encima de la apertura).
Después de cada posición cerrada se inspecciona el beneficio obtenido de la estrategia. Los resultados perdedores ponen en cola su marca de tiempo de cierre en una lista FIFO interna que solo almacena pérdidas consecutivas. Las salidas rentables o de equilibrio borran la lista, del mismo modo que el ciclo MQL abortó una vez que encontró un acuerdo sin pérdidas.
Cuando la lista llega a ConsecutiveLosses elementos, la estrategia verifica si la diferencia horaria entre la pérdida más antigua y la más reciente está dentro de WithinMinutes. Si es así, la negociación se pausa hasta que transcurra PauseMinutes desde la última hora de cierre. Durante la pausa no se envían nuevas órdenes de mercado, pero la gestión de posiciones existente continúa funcionando para que el libro pueda aplanarse de forma natural.
Una vez que expira la pausa, la lista de pérdidas se borra y las operaciones se reanudan automáticamente. El comportamiento imita las funciones originales CheckLastNLossDifference y lastOrderCloseTime sin depender de un escaneo persistente del historial de pedidos.
La implementación utiliza las suscripciones de velas de alto nivel de StockSharp (SubscribeCandles) y el administrador PnL integrado para monitorear las ganancias obtenidas. Una cola simple (Queue<DateTimeOffset>) captura las marcas de tiempo de la racha de pérdidas respetando al mismo tiempo la prohibición del recorrido manual redundante del historial.
Parámetros
Parámetro
Predeterminado
Descripción
CandleType
marco de tiempo de 5 minutos
Agregación de velas utilizada para las entradas de impulso simples.
OrderVolume
0.1
Volumen (en lotes/contratos) enviado con cada orden de entrada y salida.
ConsecutiveLosses
3
Número de posiciones perdedoras consecutivas necesarias antes de que se suspendan nuevas operaciones.
WithinMinutes
20
Número máximo de minutos permitidos entre la primera y la última derrota de la racha. Un valor de cero desactiva la verificación de la ventana.
PauseMinutes
20
Duración de la suspensión de la negociación tras la detección de la racha de pérdidas.
Notas
La cola de marcas de tiempo de pérdidas solo se completa cuando la estrategia es plana y acaba de realizar una pérdida. Los cierres parciales o las operaciones rentables no prolongan la racha, evitando falsos positivos.
El temporizador de pausa se evalúa con respecto a cada vela terminada. Si transcurre PauseMinutes mientras la estrategia está inactiva, la siguiente vela desbloquea inmediatamente el comercio.
Debido a que la versión StockSharp opera en una posición de compensación, la diferencia PnL obtenida se deriva de PnLManager.RealizedPnL, reflejando fielmente la búsqueda del historial de MetaTrader sin reprocesar todo el registro de pedidos.
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()