Открыть на GitHub

Стратегия Exp i-KlPrice Vol

Обзор

Стратегия представляет собой перевод на C# эксперта MetaTrader Exp_i-KlPrice_Vol.mq5. Она воссоздаёт осциллятор KlPrice, измеряющий удалённость цены от волатильной полосы, умножает его на объём свечи и отслеживает цветовые переходы, формируемые адаптивными порогами. Для каждой стороны моделируются два независимых слота позиций, что соответствует работе оригинального советника с двумя magic-номерами.

Логика индикатора

  • Цена преобразуется в соответствии с выбранным режимом AppliedPrice (close, open, median, Demark и т.д.).
  • Преобразованная цена сглаживается средним по методу PriceMaMethod с периодом PriceMaLength.
  • Диапазон свечи (High - Low) сглаживается методом RangeMaMethod/RangeMaLength и служит шириной динамического канала.
  • Базовый осциллятор KlPrice рассчитывается по формуле 100 * (Price - (MA - RangeMA)) / (2 * RangeMA) - 50.
  • Осциллятор умножается на выбранный источник объёма (AppliedVolume.Tick или AppliedVolume.Real).
  • Юриковское сглаживание длиной SmoothingLength применяется как к осциллятору, так и к самому объёму, формируя две адаптивные серии.
  • Пороги вычисляются умножением сглаженного объёма на коэффициенты HighLevel2, HighLevel1, LowLevel1, LowLevel2.
  • Текущий цвет определяется сравнением сглаженного осциллятора с порогами:
    • 4 – значение выше HighLevel2 * volume (сильное бычье давление).
    • 3 – между верхним умеренным и экстремальным уровнями.
    • 2 – зона нейтральности между порогами.
    • 1 – между нижним порогом и нулевой линией.
    • 0 – ниже LowLevel2 * volume (сильное медвежье давление).

Торговые правила

  1. Анализируются цвета на свече с отступом SignalBar (обычно предыдущая завершённая свеча) и ещё на одну свечу старше.
  2. Входы в лонг:
    • Слот 1 открывается, когда цвет меняется с 4 на значение ниже 4, и разрешён параметр AllowLongEntry.
    • Слот 2 открывается при переходе с 3 на значение ниже 3.
  3. Входы в шорт:
    • Слот 1 открывается при переходе цвета с 0 на значение выше 0, если AllowShortEntry активен.
    • Слот 2 открывается при переходе с 1 на значение выше 1.
  4. Закрытие длинных позиций происходит, когда предыдущий цвет был 0 или 1, и включён AllowLongExit.
  5. Закрытие коротких позиций выполняется при предыдущих цветах 4 или 3 и активном AllowShortExit.
  6. Каждый слот запоминает время последнего сигнала, чтобы избежать повторных заявок на той же свече. Если StopLossPoints или TakeProfitPoints больше нуля, для защиты вызывается StartProtection.

Параметры

Имя Тип Значение по умолчанию Описание
PrimaryVolume decimal 0.1 Объём заявок для первого слота.
SecondaryVolume decimal 0.2 Объём заявок для второго слота.
StopLossPoints int 1000 Расстояние защитного стопа в шагах цены.
TakeProfitPoints int 2000 Расстояние тейк-профита в шагах цены.
AllowLongEntry bool true Разрешение на открытие длинных позиций.
AllowShortEntry bool true Разрешение на открытие коротких позиций.
AllowLongExit bool true Закрытие длинных позиций при медвежьих цветах.
AllowShortExit bool true Закрытие коротких позиций при бычьих цветах.
CandleType DataType H8 Таймфрейм свечей для расчётов.
PriceMaMethod SmoothMethod Sma Тип сглаживания применённого значения цены.
PriceMaLength int 100 Период сглаживания цены.
PriceMaPhase int 15 Фазовый параметр для фильтров Юрика.
RangeMaMethod SmoothMethod Jjma Тип сглаживания диапазона свечи.
RangeMaLength int 20 Период сглаживания диапазона.
RangeMaPhase int 100 Фазовый параметр диапазонного фильтра.
SmoothingLength int 20 Длина Юриковского сглаживания осциллятора и объёма.
AppliedPrice AppliedPrice Close Источник цены для вычислений осциллятора.
VolumeType AppliedVolume Tick Источник объёма, умножаемого на осциллятор.
HighLevel2 int 150 Верхний экстремальный множитель.
HighLevel1 int 20 Верхний умеренный множитель.
LowLevel1 int -20 Нижний умеренный множитель.
LowLevel2 int -150 Нижний экстремальный множитель.
SignalBar int 1 Отступ по истории для оценки цветов.

Рекомендации

  • Подключайте стратегию к инструментам, где доступны данные по цене и объёму; при отсутствии реального объёма используется тиковой счётчик.
  • Параметры PrimaryVolume и SecondaryVolume позволяют имитировать две независимые схемы управления капиталом оригинального советника.
  • Меняйте SignalBar, если требуется сместить анализ на более ранние свечи или при синхронизации истории.
  • Методы сглаживания поддерживают фильтры Jurik через отражение, что максимально приближает расчёты к библиотеке SmoothAlgorithms из MQL.
  • Защитные ордера активируются только при положительных значениях StopLossPoints или TakeProfitPoints; нулевые значения полностью отключают защиту.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// KlPrice-Vol strategy using EMA crossover with volume confirmation.
/// Buys on fast EMA crossing above slow EMA with rising volume.
/// Sells on reverse crossover.
/// </summary>
public class ExpIKlPriceVolStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public ExpIKlPriceVolStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

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

		if (!_fast.IsFormed || !_slow.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 60;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// EMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 60;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 60;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}