Ver en GitHub

DeMarker gana posición en la estrategia de volumen

Descripción general

Esta estrategia es una versión StockSharp del asesor experto MetaTrader "DeMarker ganando volumen de posición". Utiliza el oscilador DeMarker para detectar extremos de sobreventa y sobrecompra, acumulando exposición gradualmente cuando el mercado permanece en una condición tensa. La implementación opera con velas completas y garantiza que solo se procese una señal por barra.

La versión C# se centra en la lógica discrecional central del script original y al mismo tiempo adopta el StockSharp API de alto nivel. La gestión de órdenes, el crecimiento del volumen y el comportamiento de reversión opcional están disponibles a través de parámetros de estrategia, lo que permite que el algoritmo se adapte a diferentes mercados y marcos temporales.

Parámetros

  • Período DeMarker: número de velas utilizadas por el indicador DeMarker.
  • Nivel superior: umbral del oscilador que prepara una exposición corta (predeterminado 0.7).
  • Nivel inferior: umbral del oscilador que prepara una exposición prolongada (predeterminado 0.3).
  • Volumen comercial: volumen de órdenes de mercado enviadas en cada señal.
  • Solo una posición: cuando está habilitada, la estrategia se aplana antes de abrir una nueva operación para que la exposición neta nunca mezcle posiciones largas y cortas.
  • Señales inversas: intercambia activadores de compra y venta, convirtiendo la estrategia en una versión contraria o de seguimiento de tendencias.
  • Tipo de vela: período de tiempo de las velas utilizadas para la evaluación del indicador y la señal.

Lógica de trading

  1. Se abre una suscripción de vela para el período de tiempo seleccionado y se introduce en un indicador DeMarker.
  2. Cuando se cierra la última vela terminada, el valor actual de DeMarker se compara con los niveles configurados.
  3. Sin reversión:
    • Si DeMarker está por debajo del nivel inferior, la estrategia intenta construir o agregar una posición larga.
    • Si DeMarker está por encima del nivel superior, la estrategia intenta construir o agregar una posición corta.
  4. Con la reversión habilitada, el significado de los niveles se invierte (los mínimos extremos activan cortos y los máximos extremos activan largos).
  5. El algoritmo recuerda la hora de la barra de la última operación ejecutada para evitar múltiples entradas en la misma vela.

Gestión de Puestos

  • Antes de cambiar de dirección, la estrategia comprueba el beneficio no realizado de la posición existente. La exposición opuesta se cierra solo si el precio de la vela actual sale de la operación con un resultado positivo, reflejando el comportamiento protector del EA original.
  • Los promedios de posición se rastrean internamente. Cuando se agregan órdenes adicionales en la misma dirección, el precio promedio se recalcula para evaluar la rentabilidad correctamente.
  • El parámetro opcional Solo una posición fuerza un estado plano antes de ingresar a una nueva operación, lo cual es útil cuando se ejecuta en modo de posición neta.
  • StartProtection() se invoca una vez que comienza la estrategia para garantizar que la liquidación de emergencia permanezca disponible si la posición deja de ser cero y el algoritmo se detiene.

Notas

  • La conversión está diseñada para StockSharp API de alto nivel y no depende de colecciones personalizadas ni de sondeos de valores de indicadores directos.
  • Los modelos de dimensionamiento de riesgo de la versión MetaTrader (margen fijo, riesgo porcentual, etc.) se simplifican intencionalmente al parámetro constante Trade Volume. Ajuste el tamaño de la posición externamente si se requiere un control dinámico del riesgo.
  • Debido a que las ejecuciones se simulan con órdenes de mercado a precios de cierre de velas, recuerde validar la configuración con respecto a los requisitos reales de ejecución y deslizamiento del corredor.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Uses the DeMarker oscillator to accumulate positions when extreme levels are reached.
/// </summary>
public class DeMarkerGainingPositionVolumeStrategy : Strategy
{
	private readonly StrategyParam<int> _deMarkerPeriod;
	private readonly StrategyParam<decimal> _upperLevel;
	private readonly StrategyParam<decimal> _lowerLevel;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private decimal? _prevOscillator;

	/// <summary>
	/// Number of candles used by the DeMarker indicator.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// DeMarker level that triggers short entries.
	/// </summary>
	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	/// <summary>
	/// DeMarker level that triggers long entries.
	/// </summary>
	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

	/// <summary>
	/// Candle type that defines the timeframe for the oscillator.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public DeMarkerGainingPositionVolumeStrategy()
	{
		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
			.SetDisplay("DeMarker Period", "Number of bars used by the oscillator.", "Indicator");

		_upperLevel = Param(nameof(UpperLevel), 0.7m)
			.SetDisplay("Upper Level", "Threshold that prepares short exposure.", "Indicator");

		_lowerLevel = Param(nameof(LowerLevel), 0.3m)
			.SetDisplay("Lower Level", "Threshold that prepares long exposure.", "Indicator");

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

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

		_prevOscillator = null;
		_rsi = new RelativeStrengthIndex { Length = DeMarkerPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_rsi, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		if (!_rsi.IsFormed)
		{
			_prevOscillator = rsiValue / 100m;
			return;
		}

		var oscillatorValue = rsiValue / 100m;
		if (_prevOscillator is null)
		{
			_prevOscillator = oscillatorValue;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// Oscillator crosses below the lower level => oversold => buy
		if (_prevOscillator > LowerLevel && oscillatorValue <= LowerLevel)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		// Oscillator crosses above the upper level => overbought => sell
		else if (_prevOscillator < UpperLevel && oscillatorValue >= UpperLevel)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevOscillator = oscillatorValue;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_rsi = null;
		_prevOscillator = null;

		base.OnReseted();
	}
}