Ver en GitHub

Estrategia Oscilador de Peso Directo

Descripción general

Esta estrategia reproduce el experto de MetaTrader Exp_WeightOscillator_Direct dentro del API de alto nivel de StockSharp. Combina cuatro osciladores clásicos—RSI, Money Flow Index, Williams %R y DeMarker—en un único compuesto ponderado. La señal compuesta se suaviza mediante una media móvil configurable y se usa para detectar oscilaciones de momentum. Un compuesto en alza abre operaciones largas (o cierra cortos) cuando la estrategia trabaja en modo "Direct", mientras que el modo "Against" invierte la lógica para trading contrario.

Cadena de indicadores

  1. Relative Strength Index (RSI) – escala normalizada 0..100.
  2. Money Flow Index (MFI) – oscilador sensible a la liquidez en rango 0..100.
  3. Williams %R (WPR) – desplazado por +100 para alinearse con la escala 0..100.
  4. DeMarker – multiplicado por 100 para coincidir con los otros osciladores.
  5. Media de suavizado – una de las medias móviles soportadas (Simple, Exponencial, Suavizado, Ponderado, Jurik, Kaufman).
  6. Oscilador compuesto – promedio ponderado de las entradas normalizadas, suavizado para eliminar ruido.

El valor del oscilador ponderado se almacena para cada vela terminada. Las señales analizan los últimos tres valores almacenados, opcionalmente omitiendo un número de barras más recientes mediante el parámetro Signal Bar para imitar el comportamiento del experto original.

Lógica de trading

  1. Esperar hasta que todos los indicadores y la media móvil de suavizado estén completamente formados.
  2. Calcular el oscilador compuesto suavizado para la barra terminada actual y añadirlo al historial.
  3. Recuperar tres valores históricos: current, previous, prior, con índices controlados por Signal Bar.
  4. Detectar cambios de pendiente:
    • Ascendente cuando previous < prior y current > previous.
    • Descendente cuando previous > prior y current < previous.
  5. Según el Trend Mode seleccionado:
    • Direct: operar con la pendiente (ascendente → señal larga, descendente → señal corta).
    • Against: operar contra la pendiente (ascendente → corto, descendente → largo).
  6. Aplicar los interruptores de entrada/salida:
    • Cerrar la exposición opuesta si el interruptor Close correspondiente está habilitado.
    • Abrir nuevas posiciones solo si el interruptor Allow respectivo está habilitado. El tamaño de la orden equivale a Volume + |Position| para que la estrategia pueda girar de corto a largo (o viceversa) con una sola orden de mercado.
  7. Las protecciones opcionales de stop-loss y take-profit se activan a través de StartProtection usando distancias expresadas en pasos de precio.

Parámetros

Grupo Nombre Descripción
General Candle Type Marco temporal para suscripción de datos y cálculos de indicadores.
Trading Trend Mode Direct sigue la pendiente del oscilador, Against opera contratendencia.
Trading Signal Bar Número de barras cerradas más recientes a omitir (1 = última barra cerrada).
Oscillator RSI / MFI / WPR / DeMarker Weight Contribución relativa de cada oscilador en la mezcla ponderada. Cero deshabilita un componente.
Oscillator RSI / MFI / WPR / DeMarker Period Longitud de lookback para cada oscilador.
Oscillator Smoothing Method Media móvil aplicada al compuesto (Simple, Exponencial, Suavizado, Ponderado, Jurik, Kaufman).
Oscillator Smoothing Length Período para la media de suavizado.
Risk Management Stop Loss Points Distancia en pasos de precio; 0 deshabilita el stop.
Risk Management Take Profit Points Distancia en pasos de precio; 0 deshabilita el objetivo.
Trading Allow Long/Short Entries Habilitar o deshabilitar la apertura de nuevas posiciones largas/cortas.
Trading Close Shorts/Longs on Signal Permitir cerrar exposición existente cuando llega una señal opuesta.

Todos los parámetros numéricos están expuestos como objetos StrategyParam, permitiendo optimización dentro del Designer de StockSharp.

Notas de uso

  • Configure la propiedad Volume base antes de iniciar la estrategia. Las órdenes de mercado se escalarán automáticamente al revertir posiciones.
  • La estrategia se suscribe exactamente a una serie de velas devuelta por GetWorkingSecurities().
  • Los stops protectores usan el PriceStep del instrumento para convertir distancias en puntos a valores de precio absolutos.
  • Cuando Trend Mode está configurado en Against, solo cambia la polaridad de la señal; todas las demás mecánicas permanecen idénticas al asesor experto original.
  • Williams %R y DeMarker se normalizan para compartir la misma escala 0..100 que RSI/MFI, coincidiendo con la lógica del indicador original.

Diferencias con el experto MQL

  • El indicador original admitía tipos de suavizado adicionales (ParMA, JurX, VIDYA, T3). En StockSharp la estrategia ofrece contrapartes de alta calidad (Jurik y Kaufman) mientras usa Jurik por defecto para compatibilidad.
  • Money Flow Index siempre usa el volumen agregado de la vela. MetaTrader podía alternar entre volúmenes de tick y reales; esta elección depende de la fuente de datos en StockSharp.
  • La gestión de riesgos se implementa a través de StartProtection (basado en pasos de precio) en lugar de solicitudes basadas en puntos, pero ofrece el mismo comportamiento cuando PriceStep coincide con el tamaño del contrato del instrumento.

Cómo empezar

  1. Adjunte la estrategia a una cartera y un valor que admita el tipo de vela configurado.
  2. Ajuste los pesos/períodos del indicador y habilite o deshabilite los interruptores de entrada.
  3. Elija el método y longitud de suavizado que mejor se adapten a la volatilidad del instrumento.
  4. Configure las distancias de stop-loss/take-profit en pasos de precio si se requiere protección.
  5. Ejecute la estrategia; las señales solo se ejecutarán en velas terminadas, asegurando un comportamiento determinista.
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>
/// Trading strategy that combines several oscillators into a weighted composite signal.
/// </summary>
public class WeightOscillatorDirectStrategy : Strategy
{
	/// <summary>
	/// Defines how the strategy reacts to the oscillator slope.
	/// </summary>
	public enum WeightOscillatorTrendModes
	{
		/// <summary>
		/// Trade in the direction of the oscillator slope.
		/// </summary>
		Direct,

		/// <summary>
		/// Trade against the oscillator slope.
		/// </summary>
		Against,
	}

	/// <summary>
	/// Available smoothing methods for the blended oscillator.
	/// </summary>
	public enum WeightOscillatorSmoothingMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed (RMA) moving average.
		/// </summary>
		Smoothed,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Weighted,

		/// <summary>
		/// Jurik moving average.
		/// </summary>
		Jurik,

		/// <summary>
		/// Kaufman adaptive moving average.
		/// </summary>
		Kaufman,
	}
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<WeightOscillatorTrendModes> _trendMode;
	private readonly StrategyParam<int> _signalBar;

	private readonly StrategyParam<decimal> _rsiWeight;
	private readonly StrategyParam<int> _rsiPeriod;

	private readonly StrategyParam<decimal> _mfiWeight;
	private readonly StrategyParam<int> _mfiPeriod;

	private readonly StrategyParam<decimal> _wprWeight;
	private readonly StrategyParam<int> _wprPeriod;

	private readonly StrategyParam<decimal> _deMarkerWeight;
	private readonly StrategyParam<int> _deMarkerPeriod;

	private readonly StrategyParam<WeightOscillatorSmoothingMethods> _smoothingMethod;
	private readonly StrategyParam<int> _smoothingLength;

	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private readonly StrategyParam<bool> _buyOpenEnabled;
	private readonly StrategyParam<bool> _sellOpenEnabled;
	private readonly StrategyParam<bool> _buyCloseEnabled;
	private readonly StrategyParam<bool> _sellCloseEnabled;

	private RelativeStrengthIndex _rsi = null!;
	private MoneyFlowIndex _mfi = null!;
	private WilliamsR _wpr = null!;
	private DeMarker _deMarker = null!;
	private IIndicator _smoothing = null!;

	private readonly List<decimal> _oscillatorHistory = new();

	/// <summary>
	/// Initializes a new instance of the <see cref="WeightOscillatorDirectStrategy"/> class.
	/// </summary>
	public WeightOscillatorDirectStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");

		_trendMode = Param(nameof(TrendMode), WeightOscillatorTrendModes.Direct)
		.SetDisplay("Trend Mode", "Trade with the oscillator slope or against it", "Trading");

		_signalBar = Param(nameof(SignalBar), 2)
		.SetDisplay("Signal Bar", "Number of closed bars to skip before evaluating signals", "Trading")
		.SetRange(1, 5)
		;

		_rsiWeight = Param(nameof(RsiWeight), 1m)
		.SetDisplay("RSI Weight", "Weight of RSI in the composite score", "Oscillator")
		.SetRange(0m, 5m)
		;

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetDisplay("RSI Period", "Number of bars used for RSI", "Oscillator")
		.SetRange(2, 200)
		;

		_mfiWeight = Param(nameof(MfiWeight), 1m)
		.SetDisplay("MFI Weight", "Weight of Money Flow Index", "Oscillator")
		.SetRange(0m, 5m)
		;

		_mfiPeriod = Param(nameof(MfiPeriod), 14)
		.SetDisplay("MFI Period", "Number of bars used for MFI", "Oscillator")
		.SetRange(2, 200)
		;

		_wprWeight = Param(nameof(WprWeight), 1m)
		.SetDisplay("WPR Weight", "Weight of Williams %R", "Oscillator")
		.SetRange(0m, 5m)
		;

		_wprPeriod = Param(nameof(WprPeriod), 14)
		.SetDisplay("WPR Period", "Number of bars used for Williams %R", "Oscillator")
		.SetRange(2, 200)
		;

		_deMarkerWeight = Param(nameof(DeMarkerWeight), 1m)
		.SetDisplay("DeMarker Weight", "Weight of DeMarker oscillator", "Oscillator")
		.SetRange(0m, 5m)
		;

		_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
		.SetDisplay("DeMarker Period", "Number of bars used for DeMarker", "Oscillator")
		.SetRange(2, 200)
		;

		_smoothingMethod = Param(nameof(SmoothingMethod), WeightOscillatorSmoothingMethods.Jurik)
		.SetDisplay("Smoothing Method", "Moving average applied to the blended oscillator", "Oscillator");

		_smoothingLength = Param(nameof(SmoothingLength), 10)
		.SetDisplay("Smoothing Length", "Length of the smoothing moving average", "Oscillator")
		.SetRange(1, 200)
		;

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
		.SetDisplay("Stop Loss Points", "Protective stop in price steps (0 disables)", "Risk Management")
		.SetRange(0, 10000)
		;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
		.SetDisplay("Take Profit Points", "Profit target in price steps (0 disables)", "Risk Management")
		.SetRange(0, 20000)
		;

		_buyOpenEnabled = Param(nameof(BuyOpenEnabled), true)
		.SetDisplay("Allow Long Entries", "Enable opening long positions", "Trading");

		_sellOpenEnabled = Param(nameof(SellOpenEnabled), true)
		.SetDisplay("Allow Short Entries", "Enable opening short positions", "Trading");

		_buyCloseEnabled = Param(nameof(BuyCloseEnabled), true)
		.SetDisplay("Close Shorts on Long Signal", "Allow closing shorts when a long signal appears", "Trading");

		_sellCloseEnabled = Param(nameof(SellCloseEnabled), true)
		.SetDisplay("Close Longs on Short Signal", "Allow closing longs when a short signal appears", "Trading");
	}

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

	/// <summary>
	/// Defines whether the strategy trades with or against the oscillator direction.
	/// </summary>
	public WeightOscillatorTrendModes TrendMode
	{
		get => _trendMode.Value;
		set => _trendMode.Value = value;
	}

	/// <summary>
	/// Number of closed bars to skip when evaluating the composite oscillator.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Weight assigned to RSI.
	/// </summary>
	public decimal RsiWeight
	{
		get => _rsiWeight.Value;
		set => _rsiWeight.Value = value;
	}

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

	/// <summary>
	/// Weight assigned to MFI.
	/// </summary>
	public decimal MfiWeight
	{
		get => _mfiWeight.Value;
		set => _mfiWeight.Value = value;
	}

	/// <summary>
	/// MFI lookback period.
	/// </summary>
	public int MfiPeriod
	{
		get => _mfiPeriod.Value;
		set => _mfiPeriod.Value = value;
	}

	/// <summary>
	/// Weight assigned to Williams %R.
	/// </summary>
	public decimal WprWeight
	{
		get => _wprWeight.Value;
		set => _wprWeight.Value = value;
	}

	/// <summary>
	/// Williams %R lookback period.
	/// </summary>
	public int WprPeriod
	{
		get => _wprPeriod.Value;
		set => _wprPeriod.Value = value;
	}

	/// <summary>
	/// Weight assigned to DeMarker oscillator.
	/// </summary>
	public decimal DeMarkerWeight
	{
		get => _deMarkerWeight.Value;
		set => _deMarkerWeight.Value = value;
	}

	/// <summary>
	/// DeMarker lookback period.
	/// </summary>
	public int DeMarkerPeriod
	{
		get => _deMarkerPeriod.Value;
		set => _deMarkerPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing method applied to the blended oscillator.
	/// </summary>
	public WeightOscillatorSmoothingMethods SmoothingMethod
	{
		get => _smoothingMethod.Value;
		set => _smoothingMethod.Value = value;
	}

	/// <summary>
	/// Length of the smoothing moving average.
	/// </summary>
	public int SmoothingLength
	{
		get => _smoothingLength.Value;
		set => _smoothingLength.Value = value;
	}

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

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

	/// <summary>
	/// Enables opening long positions.
	/// </summary>
	public bool BuyOpenEnabled
	{
		get => _buyOpenEnabled.Value;
		set => _buyOpenEnabled.Value = value;
	}

	/// <summary>
	/// Enables opening short positions.
	/// </summary>
	public bool SellOpenEnabled
	{
		get => _sellOpenEnabled.Value;
		set => _sellOpenEnabled.Value = value;
	}

	/// <summary>
	/// Enables closing short positions on a long signal.
	/// </summary>
	public bool BuyCloseEnabled
	{
		get => _buyCloseEnabled.Value;
		set => _buyCloseEnabled.Value = value;
	}

	/// <summary>
	/// Enables closing long positions on a short signal.
	/// </summary>
	public bool SellCloseEnabled
	{
		get => _sellCloseEnabled.Value;
		set => _sellCloseEnabled.Value = value;
	}

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

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

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

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_mfi = new MoneyFlowIndex { Length = MfiPeriod };
		_wpr = new WilliamsR { Length = WprPeriod };
		_deMarker = new DeMarker { Length = DeMarkerPeriod };
		_smoothing = CreateSmoothingIndicator();

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

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

		var step = Security?.PriceStep ?? 1m;
		var takeProfit = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : null;
		var stopLoss = StopLossPoints > 0 ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : null;

		StartProtection(stopLoss, takeProfit);
	}

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

		var totalWeight = RsiWeight + MfiWeight + WprWeight + DeMarkerWeight;
		if (totalWeight <= 0)
		{
		this.LogInfo("Total oscillator weight must be positive to generate signals.");
		return;
		}

		// Williams %R is negative in StockSharp, so shift it into the 0..100 range.
		var normalizedWpr = wprValue + 100m;
		// DeMarker returns 0..1; scale to match other oscillators.
		var normalizedDeMarker = deMarkerValue * 100m;

		var blended = (RsiWeight * rsiValue + MfiWeight * mfiValue + WprWeight * normalizedWpr + DeMarkerWeight * normalizedDeMarker) / totalWeight;

		var smoothedValue = _smoothing.Process(new DecimalIndicatorValue(_smoothing, blended, candle.OpenTime) { IsFinal = true });
		if (!smoothedValue.IsFinal)
		return;

		var oscillator = smoothedValue.ToDecimal();

		_oscillatorHistory.Add(oscillator);
		if (_oscillatorHistory.Count > 512)
		_oscillatorHistory.RemoveAt(0);

		var requiredCount = SignalBar + 2;
		if (_oscillatorHistory.Count < requiredCount)
		return;

		var current = GetHistoryValue(SignalBar);
		var previous = GetHistoryValue(SignalBar + 1);
		var prior = GetHistoryValue(SignalBar + 2);

		// Rising when slope turns up over the last two steps.
		var rising = previous < prior && current > previous;
		// Falling when slope turns down over the last two steps.
		var falling = previous > prior && current < previous;

		bool longSignal;
		bool shortSignal;

		if (TrendMode == WeightOscillatorTrendModes.Direct)
		{
		longSignal = rising;
		shortSignal = falling;
		}
		else
		{
		longSignal = falling;
		shortSignal = rising;
		}

		if (longSignal)
		{
		if (BuyCloseEnabled && Position < 0)
		{
		BuyMarket(Math.Abs(Position));
		}

		if (BuyOpenEnabled && Position <= 0)
		{
		BuyMarket(Volume > 0m ? Volume : 1m);
		}
		}

		if (shortSignal)
		{
		if (SellCloseEnabled && Position > 0)
		{
		SellMarket(Math.Abs(Position));
		}

		if (SellOpenEnabled && Position >= 0)
		{
		SellMarket(Volume > 0m ? Volume : 1m);
		}
		}
	}

	private IIndicator CreateSmoothingIndicator()
	{
		return SmoothingMethod switch
		{
			WeightOscillatorSmoothingMethods.Simple => new SMA { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Exponential => new EMA { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Smoothed => new SmoothedMovingAverage { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Weighted => new WeightedMovingAverage { Length = SmoothingLength },
			WeightOscillatorSmoothingMethods.Kaufman => new KaufmanAdaptiveMovingAverage { Length = SmoothingLength },
			_ => new JurikMovingAverage { Length = SmoothingLength },
		};
	}

	private decimal GetHistoryValue(int shift)
	{
		return _oscillatorHistory[_oscillatorHistory.Count - shift];
	}
}