Ver en GitHub

iVIDyA Estrategia Simple

Descripción general

Esta estrategia es una adaptación de alto nivel StockSharp del experto MetaTrader "iVIDyA Simple". Opera con un solo símbolo siguiendo un promedio dinámico de índice variable (VIDYA) que se adapta al impulso del mercado a través del oscilador de impulso Chande (CMO). Cada vez que la vela terminada más reciente cruza la línea VIDYA desplazada, la estrategia abre una posición de mercado en la dirección de la ruptura y, opcionalmente, adjunta órdenes protectoras de stop-loss y take-profit.

Lógica de trading

  1. Los datos de las velas se leen desde el período de tiempo configurado (CandleType).
  2. El CMO con período CmoPeriod está vinculado a la serie de velas. Su valor absoluto escala dinámicamente el factor de suavizado de VIDYA. El factor base es igual a 2 / (EmaPeriod + 1) al igual que la implementación original de MQL.
  3. Se mantiene un valor VIDYA móvil. En cada vela terminada el algoritmo:
    • Selecciona el precio aplicado (AppliedPrice) de la vela (cierre, apertura, mediana, etc.).
    • Actualiza VIDYA con el coeficiente de suavizado adaptativo.
    • Almacena valores históricos para emular la opción MA shift de MetaTrader.
  4. La vela se compara con el valor VIDYA desplazado (MaShift barras hacia atrás):
    • Si la vela se abre por debajo de VIDYA y cierra por encima de ella, se genera una señal de compra.
    • Si la vela se abre por encima de VIDYA y cierra por debajo de ella, se genera una señal de venta.
  5. Antes de abrir una nueva posición, la estrategia aplana cualquier exposición opuesta negociando todo el volumen necesario para revertir.
  6. Después de cada entrada, se llama a SetStopLoss y SetTakeProfit cuando las distancias respectivas son positivas.

Esto refleja el asesor experto original que activaba órdenes estrictamente en barras nuevas, utilizaba un VIDYA calculado a partir de CMO y EMA períodos, y adjuntaba paradas opcionales expresadas en puntos.

Parámetros

Nombre Predeterminado Descripción
Volume 1 Volumen de negociación base utilizado para las órdenes. La exposición existente se compensa automáticamente al invertir posiciones.
StopLossPoints 150 Distancia de stop-loss en pasos de precio. Establezca en 0 para desactivar.
TakeProfitPoints 460 Distancia de obtención de beneficios en pasos de precio. Establezca en 0 para desactivar.
CmoPeriod 15 Longitud del oscilador Chande Momentum que determina el peso adaptativo de VIDYA.
EmaPeriod 12 EMA longitud que define el coeficiente de suavizado base en la fórmula VIDYA.
MaShift 1 Número de velas completadas utilizadas para mover la línea VIDYA hacia adelante, coincidiendo con la entrada ma_shift del indicador MetaTrader.
AppliedPrice Close Fuente de precio pasada al cálculo de VIDYA (Close, Open, High, Low, Median, Typical, Weighted).
CandleType TimeSpan.FromMinutes(5) Tipo de vela y período de tiempo utilizados para todos los cálculos y señales.

Notas adicionales

  • Las órdenes de protección se administran a través del API (SetStopLoss/SetTakeProfit) integrado de alto nivel, mientras que el código original MQL realizaba verificaciones manuales de los niveles de congelación.
  • La estrategia se suscribe únicamente a velas terminadas, replicando la restricción de ejecución de "nueva barra" de MetaTrader.
  • El historial de VIDYA se recorta automáticamente para que el uso de memoria se mantenga pequeño incluso cuando MaShift es grande.
  • Todos los comentarios dentro del código están escritos en inglés para cumplir con los requisitos del proyecto.
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "iVIDyA Simple" MetaTrader expert.
/// Computes a Variable Index Dynamic Average using CMO and EMA smoothing.
/// Trades when price crosses above/below the VIDYA line.
/// </summary>
public class IvidyaSimpleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cmoPeriod;
	private readonly StrategyParam<int> _emaPeriod;

	private ChandeMomentumOscillator _cmo;
	private decimal? _prevVidya;
	private decimal? _prevClose;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int CmoPeriod
	{
		get => _cmoPeriod.Value;
		set => _cmoPeriod.Value = value;
	}

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public IvidyaSimpleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series", "General");

		_cmoPeriod = Param(nameof(CmoPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("CMO Period", "Chande Momentum Oscillator length", "Indicator");

		_emaPeriod = Param(nameof(EmaPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "Base EMA length used by VIDYA", "Indicator");
	}

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

		_cmo = new ChandeMomentumOscillator { Length = CmoPeriod };
		_prevVidya = null;
		_prevClose = null;

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

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

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

		if (!_cmo.IsFormed)
			return;

		var close = candle.ClosePrice;

		// VIDYA = alpha * |CMO/100| * price + (1 - alpha * |CMO/100|) * prevVidya
		var alpha = 2m / (EmaPeriod + 1m);
		var momentumFactor = Math.Abs(cmoValue) / 100m;
		var sf = alpha * momentumFactor;

		var prevVidya = _prevVidya ?? close;
		var currentVidya = sf * close + (1m - sf) * prevVidya;

		if (_prevVidya is null || _prevClose is null)
		{
			_prevVidya = currentVidya;
			_prevClose = close;
			return;
		}

		// Price crosses above VIDYA -> buy
		var crossUp = _prevClose <= _prevVidya && close > currentVidya;
		// Price crosses below VIDYA -> sell
		var crossDown = _prevClose >= _prevVidya && close < currentVidya;
		var minDistance = close * 0.001m;

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

		if (crossUp && Math.Abs(close - currentVidya) >= minDistance)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown && Math.Abs(close - currentVidya) >= minDistance)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevVidya = currentVidya;
		_prevClose = close;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_cmo = null;
		_prevVidya = null;
		_prevClose = null;

		base.OnReseted();
	}
}