Ver en GitHub

Estrategia Pipsover

Descripción general

Pipsover es una estrategia de reversión de momentum que reacciona a extremos fuertes del oscilador Chaikin. El Expert Advisor original de MetaTrader 5 abre una nueva operación cuando el oscilador imprime un pico pronunciado mientras la vela anterior retrocede a la media móvil simple de 20 períodos. El puerto en C# mantiene la misma idea reconstruyendo el oscilador Chaikin con la línea de acumulación/distribución y dos medias móviles exponenciales. Cada operación está protegida con las mismas distancias de stop-loss y take-profit definidas en el script para que el control de riesgo coincida con la implementación de referencia.

Indicadores y herramientas

  • Media Móvil Simple (SMA 20) – proporciona el ancla de reversión a la media. La estrategia requiere que la vela anterior toque o cruce el promedio antes de ser elegible para una operación.
  • Oscilador Chaikin (EMA 3 – EMA 10 de ADL) – mide la presión entre precio y volumen. Las lecturas extremadamente negativas desencadenan oportunidades de compra y los valores positivos extremos desencadenan oportunidades de venta.
  • Línea de Acumulación/Distribución (ADL) – alimenta el oscilador Chaikin. Las EMAs rápida y lenta corren sobre este flujo de valores para imitar el indicador iChaikin de MQL5.

Lógica de trading

Entrada larga

  1. Esperar una vela completada para que todos los valores de indicadores sean definitivos.
  2. Verificar que la vela anterior cerró alcista (Close > Open).
  3. Confirmar que el mínimo anterior bajó por debajo de la SMA20, señalando un retroceso.
  4. Leer el valor del oscilador Chaikin de la barra anterior. Debe ser menor que -OpenLevel para reflejar un pico de sobreventa.
  5. Cuando se cumplen todas las condiciones y no hay posición actualmente abierta, enviar una orden de compra a mercado.

Entrada corta

  1. Esperar una vela completada.
  2. Verificar que la vela anterior cerró bajista (Close < Open).
  3. Confirmar que el máximo anterior superó la SMA20.
  4. Asegurarse de que el oscilador Chaikin en la barra anterior sea mayor que OpenLevel.
  5. Si no hay posición activa, colocar una orden de venta a mercado.

Lógica de salida

  • Las posiciones largas se cierran cuando la siguiente vela después de la entrada muestra una estructura bajista (cierre por debajo de apertura), su máximo permanece por encima de la SMA20 y el oscilador Chaikin sube por encima de CloseLevel.
  • Las posiciones cortas se cierran cuando la siguiente vela muestra una estructura alcista, su mínimo se mueve por debajo de la SMA20 y el oscilador Chaikin cae por debajo de -CloseLevel.
  • Las salidas de protección monitorean cada vela terminada. Una posición larga se cierra si el precio cotiza en o por debajo del stop-loss calculado o en o por encima del take-profit calculado. Para cortos la comparación es invertida.

Gestión de posición

  • Solo se permite una posición neta en cualquier momento. Las órdenes pendientes se cancelan antes de abrir una nueva operación para replicar el comportamiento de posición única de MQL5.
  • Los valores de stop-loss y take-profit se calculan a partir del paso de precio del instrumento. Para largos, el stop se establece StopLossPoints * PriceStep por debajo del precio de ejecución y el take-profit TakeProfitPoints * PriceStep por encima. Los cortos usan distancias simétricas pero invertidas.

Parámetros

Nombre Predeterminado Descripción
TradeVolume 0.1 Tamaño de la orden usado para cada orden de mercado.
MaLength 20 Período de la SMA de retroceso.
StopLossPoints 65 Offset de stop-loss en pasos de precio desde la entrada.
TakeProfitPoints 100 Offset de take-profit en pasos de precio desde la entrada.
OpenLevel 100 Umbral absoluto de Chaikin que habilita nuevas entradas.
CloseLevel 125 Umbral absoluto de Chaikin que fuerza la salida de la posición.
ChaikinFastLength 3 Longitud de EMA rápida del oscilador Chaikin.
ChaikinSlowLength 10 Longitud de EMA lenta del oscilador Chaikin.
CandleType 1 hora Marco temporal usado para la suscripción de velas; ajustar para que coincida con la sesión de trading de interés.

Notas de implementación

  • La estrategia vincula la línea de acumulación/distribución y la SMA al feed de velas a través de SubscribeCandles().Bind(...), garantizando que los valores de los indicadores lleguen ya sincronizados con cada vela terminada.
  • Los valores de Chaikin se reconstruyen manualmente dentro de ProcessCandle para evitar el acceso de bajo nivel a buffers prohibido por las pautas de conversión.
  • El algoritmo almacena la última vela completada, el valor de la SMA y la lectura de Chaikin para reproducir la lógica shift=1 (iClose(...,1), iLow(...,1), iChaikin(...,1)) usada en el script MQL5.
  • Los niveles de objetivo de protección se rastrean dentro de la clase de estrategia en lugar de depender de stops gestionados por el broker, por lo que el comportamiento es consistente entre simulaciones y trading en vivo.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Pipsover strategy rebuilt on the high level StockSharp API.
/// The strategy opens positions when a strong Chaikin oscillator spike aligns with a pullback to the 20-period SMA on the previous candle.
/// Protective stop-loss and take-profit levels are recreated using price step distances defined in the original Expert Advisor.
/// </summary>
public class PipsoverStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _maLength;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _openLevel;
	private readonly StrategyParam<decimal> _closeLevel;
	private readonly StrategyParam<int> _chaikinFastLength;
	private readonly StrategyParam<int> _chaikinSlowLength;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _sma;
	private AccumulationDistributionLine _accumulationDistribution;
	private ExponentialMovingAverage _chaikinFast;
	private ExponentialMovingAverage _chaikinSlow;

	private decimal _prevOpen;
	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _prevClose;
	private decimal _prevSma;
	private decimal _prevChaikin;
	private bool _hasPrevCandle;

	private decimal _stopPrice;
	private decimal _takeProfitPrice;
	private bool _hasTargets;

	/// <summary>
	/// Trading volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Period of the simple moving average that acts as a pullback filter.
	/// </summary>
	public int MaLength
	{
		get => _maLength.Value;
		set => _maLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Chaikin oscillator level required to allow new entries.
	/// </summary>
	public decimal OpenLevel
	{
		get => _openLevel.Value;
		set => _openLevel.Value = value;
	}

	/// <summary>
	/// Chaikin oscillator level that closes existing positions.
	/// </summary>
	public decimal CloseLevel
	{
		get => _closeLevel.Value;
		set => _closeLevel.Value = value;
	}

	/// <summary>
	/// Fast EMA period for Chaikin oscillator reconstruction.
	/// </summary>
	public int ChaikinFastLength
	{
		get => _chaikinFastLength.Value;
		set => _chaikinFastLength.Value = value;
	}

	/// <summary>
	/// Slow EMA period for Chaikin oscillator reconstruction.
	/// </summary>
	public int ChaikinSlowLength
	{
		get => _chaikinSlowLength.Value;
		set => _chaikinSlowLength.Value = value;
	}

	/// <summary>
	/// Candle type used for indicator calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="PipsoverStrategy"/> class.
	/// </summary>
	public PipsoverStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order size used for market entries", "Trading");

		_maLength = Param(nameof(MaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Length", "Simple moving average length", "Indicators");

		_stopLossPoints = Param(nameof(StopLossPoints), 65m)
			.SetGreaterThanZero()
			.SetDisplay("Stop-Loss Points", "Stop-loss distance expressed in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m)
			.SetGreaterThanZero()
			.SetDisplay("Take-Profit Points", "Take-profit distance expressed in price steps", "Risk");

		_openLevel = Param(nameof(OpenLevel), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Open Level", "Chaikin oscillator threshold for entries", "Chaikin");

		_closeLevel = Param(nameof(CloseLevel), 30m)
			.SetGreaterThanZero()
			.SetDisplay("Close Level", "Chaikin oscillator threshold for exits", "Chaikin");

		_chaikinFastLength = Param(nameof(ChaikinFastLength), 3)
			.SetGreaterThanZero()
			.SetDisplay("Chaikin Fast Length", "Fast EMA length for Chaikin oscillator", "Chaikin");

		_chaikinSlowLength = Param(nameof(ChaikinSlowLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Chaikin Slow Length", "Slow EMA length for Chaikin oscillator", "Chaikin");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for calculations", "Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		// Reset cached candle and indicator state.
		_hasPrevCandle = false;
		_prevOpen = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		_prevClose = 0m;
		_prevSma = 0m;
		_prevChaikin = 0m;

		// Reset protective price levels.
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_hasTargets = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Apply configured trading volume to base strategy.
		Volume = TradeVolume;

		// Prepare indicators that replicate the MQL Expert Advisor logic.
		_sma = new SMA { Length = MaLength };
		_accumulationDistribution = new AccumulationDistributionLine();
		_chaikinFast = new EMA { Length = ChaikinFastLength };
		_chaikinSlow = new EMA { Length = ChaikinSlowLength };

		// Subscribe to candle data and bind indicators.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_accumulationDistribution, _sma, ProcessCandle)
			.Start();

		// Optional charting for visual validation.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _sma);
			DrawIndicator(area, _accumulationDistribution);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal adlValue, decimal smaValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Rebuild Chaikin oscillator values via EMA of the ADL indicator.
		var fastResult = _chaikinFast.Process(new DecimalIndicatorValue(_chaikinFast, adlValue, candle.ServerTime) { IsFinal = true });
		var slowResult = _chaikinSlow.Process(new DecimalIndicatorValue(_chaikinSlow, adlValue, candle.ServerTime) { IsFinal = true });
		var chaikinValue = fastResult.ToDecimal() - slowResult.ToDecimal();

		// Wait for all indicators to be fully formed before trading.
		if (!_chaikinFast.IsFormed || !_chaikinSlow.IsFormed || !_sma.IsFormed)
		{
			UpdateState(candle, chaikinValue, smaValue);
			return;
		}

		if (!_hasPrevCandle)
		{
			UpdateState(candle, chaikinValue, smaValue);
			return;
		}

		var step = Security?.PriceStep ?? 1m;
		var stopLossDistance = StopLossPoints * step;
		var takeProfitDistance = TakeProfitPoints * step;

		// Check protective stop-loss and take-profit targets before new decisions.
		if (_hasTargets)
		{
			if (Position > 0)
			{
				if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
				{
					SellMarket(Position);
					ResetTargets();
				}
			}
			else if (Position < 0)
			{
				if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
				{
					BuyMarket(Math.Abs(Position));
					ResetTargets();
				}
			}
			else
			{
				ResetTargets();
			}
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			UpdateState(candle, chaikinValue, smaValue);
			return;
		}

		var prevBullish = _prevClose > _prevOpen;
		var prevBearish = _prevClose < _prevOpen;

		if (Position > 0)
		{
			var shouldExitLong = prevBearish && _prevHigh > _prevSma && _prevChaikin > CloseLevel;
			if (shouldExitLong)
			{
				SellMarket(Position);
				ResetTargets();
			}
		}
		else if (Position < 0)
		{
			var shouldExitShort = prevBullish && _prevLow < _prevSma && _prevChaikin < -CloseLevel;
			if (shouldExitShort)
			{
				BuyMarket(Math.Abs(Position));
				ResetTargets();
			}
		}
		else
		{
			// No position is open, evaluate entry signals.

			var allowLong = prevBullish && _prevLow < _prevSma && _prevChaikin < -OpenLevel;
			var allowShort = prevBearish && _prevHigh > _prevSma && _prevChaikin > OpenLevel;

			if (allowLong)
			{
				BuyMarket();

				var entryPrice = candle.ClosePrice;
				_stopPrice = entryPrice - stopLossDistance;
				_takeProfitPrice = entryPrice + takeProfitDistance;
				_hasTargets = true;
			}
			else if (allowShort)
			{
				SellMarket();

				var entryPrice = candle.ClosePrice;
				_stopPrice = entryPrice + stopLossDistance;
				_takeProfitPrice = entryPrice - takeProfitDistance;
				_hasTargets = true;
			}
		}

		UpdateState(candle, chaikinValue, smaValue);
	}

	private void UpdateState(ICandleMessage candle, decimal chaikinValue, decimal smaValue)
	{
		// Store previous candle data for next iteration checks.
		_prevOpen = candle.OpenPrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevClose = candle.ClosePrice;
		_prevSma = smaValue;
		_prevChaikin = chaikinValue;
		_hasPrevCandle = true;
	}

	private void ResetTargets()
	{
		// Clear stop-loss and take-profit levels once position is closed.
		_stopPrice = 0m;
		_takeProfitPrice = 0m;
		_hasTargets = false;
	}
}