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
Los datos de las velas se leen desde el período de tiempo configurado (CandleType).
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.
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.
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.
Antes de abrir una nueva posición, la estrategia aplana cualquier exposición opuesta negociando todo el volumen necesario para revertir.
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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ChandeMomentumOscillator
from StockSharp.Algo.Strategies import Strategy
class ividya_simple_strategy(Strategy):
def __init__(self):
super(ividya_simple_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._cmo_period = self.Param("CmoPeriod", 20)
self._ema_period = self.Param("EmaPeriod", 30)
self._prev_vidya = 0.0
self._prev_close = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CmoPeriod(self):
return self._cmo_period.Value
@CmoPeriod.setter
def CmoPeriod(self, value):
self._cmo_period.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
def OnReseted(self):
super(ividya_simple_strategy, self).OnReseted()
self._prev_vidya = 0.0
self._prev_close = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(ividya_simple_strategy, self).OnStarted2(time)
self._prev_vidya = 0.0
self._prev_close = 0.0
self._has_prev = False
cmo = ChandeMomentumOscillator()
cmo.Length = self.CmoPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cmo, self._process_candle).Start()
def _process_candle(self, candle, cmo_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
cmo_val = float(cmo_value)
# VIDYA = alpha * |CMO/100| * price + (1 - alpha * |CMO/100|) * prevVidya
alpha = 2.0 / (self.EmaPeriod + 1.0)
momentum_factor = abs(cmo_val) / 100.0
sf = alpha * momentum_factor
prev_vidya = self._prev_vidya if self._has_prev else close
current_vidya = sf * close + (1.0 - sf) * prev_vidya
if self._has_prev:
cross_up = self._prev_close <= self._prev_vidya and close > current_vidya
cross_down = self._prev_close >= self._prev_vidya and close < current_vidya
min_distance = close * 0.001
if cross_up and abs(close - current_vidya) >= min_distance and self.Position <= 0:
self.BuyMarket()
elif cross_down and abs(close - current_vidya) >= min_distance and self.Position >= 0:
self.SellMarket()
self._prev_vidya = current_vidya
self._prev_close = close
self._has_prev = True
def CreateClone(self):
return ividya_simple_strategy()