Estrategia de pairs trading

Descripción general

PairsTradingStrategy es una estrategia de pairs trading basada en arbitraje estadístico entre dos instrumentos relacionados. Sigue el spread entre los precios de dos activos y abre posiciones cuando el spread se desvía significativamente de la media, esperando una reversión a la media.

Componentes principales

La estrategia hereda de Strategy y usa parámetros para configuración:

public class PairsTradingStrategy : Strategy
{
	private readonly StrategyParam<int> _spreadLength;
	private readonly StrategyParam<decimal> _entryThreshold;
	private readonly StrategyParam<decimal> _exitThreshold;
	private readonly StrategyParam<DataType> _candleType;

	// Últimos precios de cada instrumento
	private decimal? _lastPrice1;
	private decimal? _lastPrice2;
}

Parámetros de estrategia

La estrategia permite personalizar los siguientes parámetros:

  • SpreadLength - periodo para calcular la media y desviación estándar del spread (predeterminado 20)
  • EntryThreshold - umbral Z-Score para entrar en posición (predeterminado 2.0)
  • ExitThreshold - umbral Z-Score para salir de posición (predeterminado 0.5)
  • CandleType - tipo de vela con el que trabajar (predeterminado 5 minutos)

Todos los parámetros están disponibles para optimización con rangos de valores especificados.

Inicialización de la estrategia

En el método OnStarted2, se crean indicadores y se configuran suscripciones a velas para dos instrumentos:

protected override void OnStarted2(DateTime time)
{
	base.OnStarted2(time);

	// Obtener dos instrumentos para pairs trading
	var securities = GetWorkingSecurities().ToArray();
	if (securities.Length < 2)
		throw new InvalidOperationException("Deben especificarse dos instrumentos.");

	var sec1 = securities[0].sec;
	var sec2 = securities[1].sec;

	// Indicadores para calcular la media y desviación estándar del spread
	var sma = new SimpleMovingAverage { Length = SpreadLength };
	var stdDev = new StandardDeviation { Length = SpreadLength };

	_lastPrice1 = null;
	_lastPrice2 = null;

	// Suscribirse a velas del primer instrumento
	SubscribeCandles(CandleType, security: sec1)
		.Bind(c =>
		{
			if (c.State != CandleStates.Finished)
				return;

			_lastPrice1 = c.ClosePrice;
		})
		.Start();

	// Suscribirse a velas del segundo instrumento con procesamiento de spread
	SubscribeCandles(CandleType, security: sec2)
		.Bind(c =>
		{
			if (c.State != CandleStates.Finished)
				return;

			_lastPrice2 = c.ClosePrice;

			if (_lastPrice1 == null)
				return;

			ProcessSpread(_lastPrice1.Value, _lastPrice2.Value, sma, stdDev);
		})
		.Start();
}

Procesamiento de spread

El método ProcessSpread calcula el Z-Score del spread y genera señales de trading:

private void ProcessSpread(decimal price1, decimal price2,
	SimpleMovingAverage sma, StandardDeviation stdDev)
{
	// Calcular spread como diferencia de precios
	var spread = price1 - price2;

	// Procesar indicadores
	var smaValue = sma.Process(new DecimalIndicatorValue(sma, spread));
	var devValue = stdDev.Process(new DecimalIndicatorValue(stdDev, spread));

	if (!sma.IsFormed || !stdDev.IsFormed)
		return;

	if (!IsFormedAndOnlineAndAllowTrading())
		return;

	var mean = smaValue.ToDecimal();
	var dev = devValue.ToDecimal();

	if (dev == 0)
		return;

	// Calcular Z-Score: desviación del spread respecto a la media en unidades de desviación estándar
	var zScore = (spread - mean) / dev;

	// Spread demasiado alto: vender el primer instrumento, comprar el segundo
	if (zScore > EntryThreshold && Position >= 0)
	{
		SellMarket(Volume + Math.Abs(Position));
	}
	// Spread demasiado bajo: comprar el primer instrumento, vender el segundo
	else if (zScore < -EntryThreshold && Position <= 0)
	{
		BuyMarket(Volume + Math.Abs(Position));
	}
	// Reversión a la media: cerrar posición
	else if (Math.Abs(zScore) < ExitThreshold && Position != 0)
	{
		ClosePosition();
	}
}

Lógica de trading

  • Señal de venta: el Z-Score del spread supera el umbral de entrada (predeterminado 2.0) cuando no hay posición corta
  • Señal de compra: el Z-Score del spread cae por debajo del umbral de entrada negativo (predeterminado -2.0) cuando no hay posición larga
  • Cerrar posición: el valor absoluto del Z-Score cae por debajo del umbral de salida (predeterminado 0.5), indicando que el spread revierte a la media

Características

  • La estrategia trabaja con dos instrumentos obtenidos mediante el método GetWorkingSecurities()
  • El spread se calcula como la diferencia de precios de cierre de velas de dos instrumentos
  • Z-Score se usa para normalizar la desviación del spread respecto a la media
  • La estrategia implementa el concepto clásico de reversión a la media
  • La estrategia solo trabaja con velas completadas
  • Se admite optimización de parámetros para encontrar ajustes óptimos de estrategia