Открыть на GitHub

Стратегия iVIDyA Simple

Обзор

Эта стратегия представляет собой порт советника MetaTrader «iVIDyA Simple» на высокоуровневый API StockSharp. Она работает с одним инструментом, отслеживая переменное среднее VIDYA, которое адаптируется к рыночному импульсу с помощью осциллятора Чанде. Когда последняя завершённая свеча пересекает смещённую линию VIDYA, стратегия открывает рыночную позицию в сторону пробоя и при необходимости добавляет защитные стоп-лосс и тейк-профит.

Логика торговли

  1. Используются свечи таймфрейма, заданного параметром CandleType.
  2. Осциллятор Чанде (CmoPeriod) привязывается к серии свечей. Его модуль изменяет коэффициент сглаживания VIDYA, базовое значение которого равно 2 / (EmaPeriod + 1) — как в оригинале.
  3. На каждой завершённой свече стратегия:
    • Выбирает тип цены (AppliedPrice) из свечи (close, open, median и т. д.).
    • Пересчитывает значение VIDYA с адаптивным коэффициентом.
    • Сохраняет историю VIDYA, чтобы повторить поведение параметра ma_shift в MetaTrader.
  4. Текущая свеча сравнивается со значением VIDYA, смещённым на MaShift баров назад:
    • Если свеча открылась ниже VIDYA и закрылась выше, формируется сигнал покупки.
    • Если свеча открылась выше VIDYA и закрылась ниже, формируется сигнал продажи.
  5. Перед входом в сделку стратегия закрывает противоположную позицию, доводя итоговый объём до требуемого значения.
  6. После открытия позиции вызываются SetStopLoss и SetTakeProfit, если соответствующие дистанции больше нуля.

Таким образом воспроизводится исходный советник, который работал только по новым барам, рассчитывал VIDYA через CMO и EMA и использовал стопы в пунктах.

Параметры

Имя Значение по умолчанию Описание
Volume 1 Базовый торговый объём. При реверсе стратегия автоматически перекрывает существующую позицию.
StopLossPoints 150 Размер стоп-лосса в шагах цены. 0 — отключить.
TakeProfitPoints 460 Размер тейк-профита в шагах цены. 0 — отключить.
CmoPeriod 15 Период осциллятора Чанде, определяющий адаптивный вес VIDYA.
EmaPeriod 12 Период EMA, задающий базовый коэффициент сглаживания в формуле VIDYA.
MaShift 1 Количество завершённых свечей для смещения линии VIDYA вперёд (аналог ma_shift).
AppliedPrice Close Тип цены для расчёта VIDYA (Close, Open, High, Low, Median, Typical, Weighted).
CandleType TimeSpan.FromMinutes(5) Тип и таймфрейм свечей для анализа и сигналов.

Дополнительные сведения

  • Защитные ордера устанавливаются через высокоуровневые методы SetStopLoss и SetTakeProfit, тогда как в MQL коде проверялись уровни freeze/stops вручную.
  • Стратегия обрабатывает только завершённые свечи, полностью повторяя условие «нового бара» из MetaTrader.
  • История VIDYA автоматически обрезается, поэтому потребление памяти остаётся низким даже при большом MaShift.
  • Все комментарии в коде оставлены на английском языке в соответствии с требованиями проекта.
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();
	}
}