Ver en GitHub

Estrategia VR ZVER

Descripción General

La estrategia VR ZVER es un sistema de seguimiento de tendencia que combina tres capas de confirmación: una pila de EMA rápida/lenta/muy lenta, el Oscilador Estocástico y el Índice de Fuerza Relativa (RSI). Todos los filtros activos deben estar de acuerdo antes de que se abra una posición, lo que ayuda a evitar operaciones durante regímenes de mercado agitados y contradictorios. La conversión mantiene la lógica de break-even y protección original mientras usa la API de alto nivel de StockSharp.

Detección del Régimen de Mercado

  1. Estructura EMA – La configuración predeterminada usa medias móviles exponenciales con períodos 3, 5 y 7. Un sesgo largo requiere que la EMA rápida esté por encima de la EMA lenta y que la EMA lenta permanezca por encima de la EMA muy lenta. Un sesgo corto invierte esta relación.
  2. Oscilador Estocástico – El par %K/%D se inspecciona tanto para dirección como para nivel. Las operaciones largas requieren que %K esté por debajo de la banda inferior y por encima de %D, señalando un rebote desde sobreventa. Las operaciones cortas requieren que %K esté por encima de la banda superior y por debajo de %D, apuntando a una reversión desde sobrecompra.
  3. Filtro RSI – El RSI debe estar por debajo del umbral inferior para permitir entradas largas o por encima del umbral superior para habilitar operaciones cortas.

Solo cuando cada filtro habilitado se alinea, la estrategia envía una orden de mercado usando el volumen configurado.

Gestión de Riesgo

  • Stop Loss – Cada entrada proyecta un stop basado en precio usando la configuración StopLossPips multiplicada por el tamaño de pip del instrumento. Las posiciones largas salen cuando el mínimo de la vela perfora el stop, mientras que las posiciones cortas cierran si el máximo de la vela alcanza su stop.
  • Take Profit – Se aplica un nivel de take-profit simétrico. Si la vela actual alcanza el objetivo a favor de la operación, la posición se cierra inmediatamente.
  • Protección de Break-Even – Después de que el precio avanza la distancia BreakevenPips, se arma un modo de break-even. Cualquier retroceso de vuelta al precio de entrada aplanará la posición para preservar el capital.
  • Limpieza de Órdenes – Todas las órdenes activas se cancelan antes de abrir una nueva operación para evitar apilamiento no intencionado.

Parámetros

Parámetro Descripción
CandleType Serie de velas usada para los cálculos.
UseMovingAverage Habilita o deshabilita el filtro de tendencia EMA.
FastMaPeriod, SlowMaPeriod, VerySlowMaPeriod Períodos para las EMAs rápida, lenta y muy lenta.
UseStochastic Activa la capa de confirmación Estocástica.
StochasticKPeriod, StochasticDPeriod, StochasticSlowing Configuraciones de período para el Oscilador Estocástico.
StochasticUpperLevel, StochasticLowerLevel Umbrales de sobrecompra y sobreventa para %K.
UseRsi Habilita o deshabilita la capa de confirmación RSI.
RsiPeriod Período de promediado RSI.
RsiUpperLevel, RsiLowerLevel Umbrales RSI que definen regiones de sobrecompra/sobreventa.
StopLossPips, TakeProfitPips Distancias (en pips) para la colocación de stop-loss y take-profit.
BreakevenPips Progreso de precio requerido antes de activar la protección de break-even.
Volume Cantidad a operar por cada orden de mercado.

Notas de Implementación

  • El tamaño de pip se deriva del paso de precio del instrumento y el número de decimales. Los instrumentos con 3 o 5 lugares decimales aplican automáticamente el ajuste estándar 10x usado en la versión MQL original.
  • Todos los datos del indicador se acceden a través de BindEx, asegurando que la estrategia reaccione solo a velas completadas con valores de indicador finalizados.
  • La estrategia es plana por defecto; las posiciones nunca se invierten sin cerrar primero la exposición existente.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Multi-indicator strategy that combines EMA, Stochastic, and RSI confirmations.
/// </summary>
public class VrZverStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _useMovingAverage;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _verySlowMaPeriod;
	private readonly StrategyParam<bool> _useStochastic;
	private readonly StrategyParam<int> _stochasticKPeriod;
	private readonly StrategyParam<int> _stochasticDPeriod;
	private readonly StrategyParam<int> _stochasticSlowing;
	private readonly StrategyParam<int> _stochasticUpperLevel;
	private readonly StrategyParam<int> _stochasticLowerLevel;
	private readonly StrategyParam<bool> _useRsi;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _rsiUpperLevel;
	private readonly StrategyParam<int> _rsiLowerLevel;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _breakevenPips;

	private ExponentialMovingAverage _fastMa = null!;
	private ExponentialMovingAverage _slowMa = null!;
	private ExponentialMovingAverage _verySlowMa = null!;
	private StochasticOscillator _stochastic = null!;
	private RelativeStrengthIndex _rsi = null!;

	private decimal _pipSize;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortTakePrice;
	private decimal? _longBreakevenTrigger;
	private decimal? _shortBreakevenTrigger;
	private bool _longBreakevenArmed;
	private bool _shortBreakevenArmed;

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

	/// <summary>
	/// Enable moving average confirmation.
	/// </summary>
	public bool UseMovingAverage
	{
		get => _useMovingAverage.Value;
		set => _useMovingAverage.Value = value;
	}

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Very slow EMA period.
	/// </summary>
	public int VerySlowMaPeriod
	{
		get => _verySlowMaPeriod.Value;
		set => _verySlowMaPeriod.Value = value;
	}

	/// <summary>
	/// Enable Stochastic confirmation.
	/// </summary>
	public bool UseStochastic
	{
		get => _useStochastic.Value;
		set => _useStochastic.Value = value;
	}

	/// <summary>
	/// Stochastic oscillator %K period.
	/// </summary>
	public int StochasticKPeriod
	{
		get => _stochasticKPeriod.Value;
		set => _stochasticKPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %D smoothing period.
	/// </summary>
	public int StochasticDPeriod
	{
		get => _stochasticDPeriod.Value;
		set => _stochasticDPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K smoothing period.
	/// </summary>
	public int StochasticSlowing
	{
		get => _stochasticSlowing.Value;
		set => _stochasticSlowing.Value = value;
	}

	/// <summary>
	/// Upper threshold for Stochastic %K.
	/// </summary>
	public int StochasticUpperLevel
	{
		get => _stochasticUpperLevel.Value;
		set => _stochasticUpperLevel.Value = value;
	}

	/// <summary>
	/// Lower threshold for Stochastic %K.
	/// </summary>
	public int StochasticLowerLevel
	{
		get => _stochasticLowerLevel.Value;
		set => _stochasticLowerLevel.Value = value;
	}

	/// <summary>
	/// Enable RSI confirmation.
	/// </summary>
	public bool UseRsi
	{
		get => _useRsi.Value;
		set => _useRsi.Value = value;
	}

	/// <summary>
	/// RSI period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI upper threshold.
	/// </summary>
	public int RsiUpperLevel
	{
		get => _rsiUpperLevel.Value;
		set => _rsiUpperLevel.Value = value;
	}

	/// <summary>
	/// RSI lower threshold.
	/// </summary>
	public int RsiLowerLevel
	{
		get => _rsiLowerLevel.Value;
		set => _rsiLowerLevel.Value = value;
	}

	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Breakeven activation distance in pips.
	/// </summary>
	public int BreakevenPips
	{
		get => _breakevenPips.Value;
		set => _breakevenPips.Value = value;
	}


	/// <summary>
	/// Initializes a new instance of <see cref="VrZverStrategy"/>.
	/// </summary>
	public VrZverStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles", "General");

		_useMovingAverage = Param(nameof(UseMovingAverage), true)
		.SetDisplay("Use EMA", "Enable EMA trend filter", "Signals");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Fast EMA period", "Signals");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Slow EMA period", "Signals");

		_verySlowMaPeriod = Param(nameof(VerySlowMaPeriod), 7)
		.SetGreaterThanZero()
		.SetDisplay("Very Slow EMA", "Very slow EMA period", "Signals");

		_useStochastic = Param(nameof(UseStochastic), true)
		.SetDisplay("Use Stochastic", "Enable stochastic confirmation", "Signals");

		_stochasticKPeriod = Param(nameof(StochasticKPeriod), 42)
		.SetGreaterThanZero()
		.SetDisplay("Stoch %K", "Stochastic %K period", "Signals");

		_stochasticDPeriod = Param(nameof(StochasticDPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Stoch %D", "Stochastic %D period", "Signals");

		_stochasticSlowing = Param(nameof(StochasticSlowing), 7)
		.SetGreaterThanZero()
		.SetDisplay("Stoch Smoothing", "Stochastic %K smoothing", "Signals");

		_stochasticUpperLevel = Param(nameof(StochasticUpperLevel), 55)
		.SetDisplay("Stoch Upper", "Upper stochastic threshold", "Signals");

		_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 50)
		.SetDisplay("Stoch Lower", "Lower stochastic threshold", "Signals");

		_useRsi = Param(nameof(UseRsi), true)
		.SetDisplay("Use RSI", "Enable RSI confirmation", "Signals");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "RSI averaging period", "Signals");

		_rsiUpperLevel = Param(nameof(RsiUpperLevel), 55)
		.SetDisplay("RSI Upper", "Upper RSI threshold", "Signals");

		_rsiLowerLevel = Param(nameof(RsiLowerLevel), 50)
		.SetDisplay("RSI Lower", "Lower RSI threshold", "Signals");

		_stopLossPips = Param(nameof(StopLossPips), 50)
		.SetNotNegative()
		.SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 70)
		.SetNotNegative()
		.SetDisplay("Take Profit", "Take profit distance in pips", "Risk");

		_breakevenPips = Param(nameof(BreakevenPips), 20)
		.SetNotNegative()
		.SetDisplay("Breakeven", "Breakeven activation distance", "Risk");

	}

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

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

		_pipSize = 0m;
		ResetPositionState();
	}

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

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
		_verySlowMa = new ExponentialMovingAverage { Length = VerySlowMaPeriod };

		_stochastic = new StochasticOscillator
		{
			K = { Length = StochasticKPeriod },
			D = { Length = StochasticDPeriod },
		};

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		_pipSize = CalculatePipSize();

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_fastMa, _slowMa, _verySlowMa, _stochastic, _rsi, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _slowMa);
			DrawIndicator(area, _verySlowMa);
			DrawIndicator(area, _stochastic);
			DrawIndicator(area, _rsi);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(
	ICandleMessage candle,
	IIndicatorValue fastValue,
	IIndicatorValue slowValue,
	IIndicatorValue verySlowValue,
	IIndicatorValue stochasticValue,
	IIndicatorValue rsiValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!fastValue.IsFinal || !slowValue.IsFinal || !verySlowValue.IsFinal || !stochasticValue.IsFinal || !rsiValue.IsFinal)
		return;

		// Extract indicator outputs for the completed candle.
		var fast = fastValue.GetValue<decimal>();
		var slow = slowValue.GetValue<decimal>();
		var verySlow = verySlowValue.GetValue<decimal>();

		var stochastic = (StochasticOscillatorValue)stochasticValue;
		if (stochastic.K is not decimal stochK || stochastic.D is not decimal stochD)
		return;

		var rsi = rsiValue.GetValue<decimal>();

		var longClosed = HandleLongPosition(candle);
		if (!longClosed && Position > 0)
		return;

		var shortClosed = HandleShortPosition(candle);
		if (!shortClosed && Position < 0)
		return;

		if (Position == 0)
		ResetPositionState();

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (Position != 0)
		return;

		if (!UseMovingAverage && !UseStochastic && !UseRsi)
		return;

		// All enabled filters must align to confirm the long scenario.
		var longSignal = (!UseMovingAverage || fast > slow && slow > verySlow)
		&& (!UseStochastic || stochD < stochK && stochK < StochasticLowerLevel)
		&& (!UseRsi || rsi < RsiLowerLevel);

		var shortSignal = (!UseMovingAverage || verySlow > slow && slow > fast)
		&& (!UseStochastic || stochD > stochK && stochK > StochasticUpperLevel)
		&& (!UseRsi || rsi > RsiUpperLevel);

		if (longSignal && Volume > 0)
		{
			EnterLong(candle.ClosePrice);
		}
		else if (shortSignal && Volume > 0)
		{
			EnterShort(candle.ClosePrice);
		}
	}

	private void EnterLong(decimal price)
	{
		BuyMarket();

		_longEntryPrice = price;
		_longStopPrice = StopLossPips > 0 ? price - StopLossPips * _pipSize : null;
		_longTakePrice = TakeProfitPips > 0 ? price + TakeProfitPips * _pipSize : null;
		_longBreakevenTrigger = BreakevenPips > 0 ? price + BreakevenPips * _pipSize : null;
		_longBreakevenArmed = false;
	}

	private void EnterShort(decimal price)
	{
		SellMarket();

		_shortEntryPrice = price;
		_shortStopPrice = StopLossPips > 0 ? price + StopLossPips * _pipSize : null;
		_shortTakePrice = TakeProfitPips > 0 ? price - TakeProfitPips * _pipSize : null;
		_shortBreakevenTrigger = BreakevenPips > 0 ? price - BreakevenPips * _pipSize : null;
		_shortBreakevenArmed = false;
	}

	private bool HandleLongPosition(ICandleMessage candle)
	{
		if (Position <= 0 || _longEntryPrice is null)
		return false;

		// Exit the long trade if the protective stop is touched within the candle range.
		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		// Take profit when the upper target is reached.
		if (_longTakePrice is decimal take && candle.HighPrice >= take)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		if (!_longBreakevenArmed && _longBreakevenTrigger is decimal trigger && candle.HighPrice >= trigger)
		_longBreakevenArmed = true;

		// Once breakeven is armed, protect the entry price if momentum fades.
		if (_longBreakevenArmed && candle.LowPrice <= _longEntryPrice.Value)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		return false;
	}

	private bool HandleShortPosition(ICandleMessage candle)
	{
		if (Position >= 0 || _shortEntryPrice is null)
		return false;

		// Cover the short trade if the stop level is breached.
		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		// Lock in profits if the target is met on the downside move.
		if (_shortTakePrice is decimal take && candle.LowPrice <= take)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		if (!_shortBreakevenArmed && _shortBreakevenTrigger is decimal trigger && candle.LowPrice <= trigger)
		_shortBreakevenArmed = true;

		// Breakeven logic mirrors the long side to guard against reversals.
		if (_shortBreakevenArmed && candle.HighPrice >= _shortEntryPrice.Value)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		return false;
	}

	private void ResetPositionState()
	{
		_longEntryPrice = null;
		_shortEntryPrice = null;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakePrice = null;
		_shortTakePrice = null;
		_longBreakevenTrigger = null;
		_shortBreakevenTrigger = null;
		_longBreakevenArmed = false;
		_shortBreakevenArmed = false;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		if (step <= 0)
		step = 0.0001m;

		var decimals = Security?.Decimals ?? 0;
		return decimals is 3 or 5 ? step * 10m : step;
	}
}