Открыть на GitHub

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

Обзор

Exp i-KlPrice Vol Direct — портирование советника MetaTrader 5 Exp_i-KlPrice_Vol_Direct на платформу StockSharp. Исходная система строит осциллятор KlPrice, умножает его на объём, выполняет несколько этапов сглаживания и реагирует на смену наклона полученной кривой. В версии для StockSharp сохранена вся цепочка расчётов, те же параметры входа и выходов, а торговые команды отправляются через высокоуровневый API после закрытия свечи.

Основные идеи, перенесённые из MQL5:

  • Двухэтапное сглаживание цены и диапазона — ценовой ряд фильтруется выбранным методом Moving Average, диапазон High-Low обрабатывается отдельно, формируя адаптивные динамические границы.
  • Взвешивание по объёму — результат осциллятора умножается на выбранный поток объёма (тиковый или реальный) перед финальным фильтром Jurik, усиливая значимые движения.
  • Цветовая карта направления — стратегия отслеживает знак наклона сглаженного осциллятора. Переход от медвежьего цвета к бычьему закрывает шорты и открывает лонг, обратный переход открывает шорт и закрывает лонг.
  • Задержка по закрытым барам — параметр SignalBar позволяет дождаться дополнительного количества закрытых свечей, полностью повторяя «подтверждающую» логику оригинала.

Цепочка расчётов

  1. Выбор прикладной цены — доступны все двенадцать формул из индикатора MT5 (Close, Open, Median, Demark, TrendFollow и т. д.).
  2. Первое сглаживание — к прикладной цене применяется PriceMethod с периодом PriceLength и фазой PricePhase (для Jurik). Поддержка методов соответствует библиотеке SmoothAlgorithms и отображается на индикаторы StockSharp:
    • SmaSimpleMovingAverage
    • EmaExponentialMovingAverage
    • SmmaSmoothedMovingAverage
    • LwmaWeightedMovingAverage
    • JjmaJurikMovingAverage (фаза учитывается, если доступно свойство)
    • JurxZeroLagExponentialMovingAverage
    • ParmaArnaudLegouxMovingAverage (фаза переводится в смещение ALMA)
    • T3TripleExponentialMovingAverage
    • Vidya → аппроксимация через ExponentialMovingAverage
    • AmaKaufmanAdaptiveMovingAverage
  3. Сглаживание диапазона — аналогичная обработка применяется к диапазону High-Low с параметрами RangeMethod, RangeLength, RangePhase, формируя адаптивную ширину каналов.
  4. Расчёт осциллятора — вычисляется формула (Price - (PriceMA - RangeMA)) / (2 * RangeMA) * 100 - 50, полностью совпадающая с MQL, затем результат умножается на выбранный объём (VolumeSource).
  5. Финальный фильтр Jurik — взвешенный осциллятор и сам объём проходят через Jurik Moving Average длины ResultLength (фаза фиксирована на 100, как в советнике).
  6. Определение цвета — сравниваются текущие и предыдущие значения сглаженного осциллятора. Рост окрашивается в 0 (бычий), падение — в 1 (медвежий), равные значения наследуют прошлый цвет. Цвета сохраняются в хронологическом порядке, что позволяет учитывать задержку SignalBar.

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

Работа в лонг

  • Вход: если цвет на баре SignalBar стал бычьим (0), а предыдущий цвет был медвежьим (1), открываем покупку при AllowLongEntries = true и отсутствии чистого лонга. Объём заявки равен Volume + |Position|, поэтому противоположные позиции закрываются автоматически.
  • Выход: при бычьем цвете и AllowShortExits = true закрываем все открытые шорты.

Работа в шорт

  • Вход: если цвет на баре SignalBar стал медвежьим (1) после бычьего (0), открываем продажу при AllowShortEntries = true и отсутствии чистого шорта.
  • Выход: при медвежьем цвете и AllowLongExits = true закрываем лонги.

Торговля осуществляется по завершённым свечам. Размер позиции управляется свойством Strategy.Volume; режимы money-management из MQL5 сознательно не переносились.

Параметры

Параметр Описание Значение по умолчанию
CandleType Таймфрейм анализируемых свечей. H4
VolumeSource Источник объёма для весов (Tick или Real; в обоих случаях используется candle.TotalVolume). Tick
PriceMethod / PriceLength / PricePhase Метод, период и фаза сглаживания прикладной цены. Sma, 100, 15
RangeMethod / RangeLength / RangePhase Метод, период и фаза сглаживания диапазона High-Low. Jjma, 20, 100
ResultLength Период финального фильтра Jurik для осциллятора и объёма. 20
PriceMode Формула прикладной цены (Close, Open, Median, Demark, TrendFollow0/1 и др.). Close
HighLevel2, HighLevel1, LowLevel1, LowLevel2 Множители уровней, сохранённые для диагностики, на сигналы не влияют. 0, 0, 0, 0
SignalBar Количество закрытых свечей перед анализом цветовой карты. 1
AllowLongEntries / AllowShortEntries Разрешение на открытие лонгов/шортов. true
AllowLongExits / AllowShortExits Разрешение на закрытие позиций при противоположном цвете. true
StopLossPoints / TakeProfitPoints Стоп-лосс и тейк-профит в пунктах (умножаются на PriceStep). 1000, 2000

Управление рисками

  • Стоп-лосс и тейк-профит преобразуются в UnitTypes.Point и активируются через StartProtection. Нулевое значение отключает соответствующий барьер.
  • Объём сделки задаётся через свойство Volume; убедитесь, что он соответствует спецификации контракта.
  • Торговые решения принимаются только при выполнении условий IsFormedAndOnlineAndAllowTrading().

Ограничения и отличия от MQL5

  • Аппроксимация методов сглаживания: для SMA, EMA, SMMA, LWMA, Jurik и KAMA есть прямые аналоги. Методы Jurx, Parma, Vidya заменены ближайшими вариантами StockSharp (ZeroLag EMA, ALMA, EMA), поэтому сочетания с экзотическими параметрами могут незначительно отличаться от MT5.
  • Данные по объёму: стандартные свечи StockSharp содержат только общий объём. Если требуется тиковый счётчик, используйте пользовательские свечи с передачей значения через TotalVolume.
  • Money-management: режимы MM и MarginMode из оригинала не реализованы. Управление размером позиции осуществляется стандартными средствами портфеля.
  • Момент исполнения: заявки отправляются сразу после закрытия сигнальной свечи, а не по смещению TimeShiftSec, как в MT5. Для рыночных ордеров поведение эквивалентно.

Рекомендации по использованию

  1. Присоедините стратегию к нужному инструменту, задайте Volume и убедитесь, что Security.PriceStep соответствует спецификации.
  2. Выберите таймфрейм через CandleType. Стратегия работает с одной свечной серией.
  3. Настройте методы и длины сглаживания, чтобы добиться желаемой формы индикатора. Используйте отображение свечей и индикатора на графике для визуальной проверки.
  4. Подберите SignalBar — 0 для более быстрого реагирования, ≥1 для подтверждения сигналов.
  5. Запускайте стратегию в демо-режиме перед боевой эксплуатацией, чтобы оценить риск-параметры.

Идеи для оптимизации

  • Совместно оптимизируйте PriceLength, RangeLength и ResultLength, балансируя между скоростью реакции и шумами.
  • Экспериментируйте с формулами прикладной цены (PriceMode), чтобы подобрать наиболее информативную для конкретного инструмента.
  • Тестируйте разные значения SignalBar, снижая число ложных срабатываний в боковых участках.
  • Значения HighLevel*/LowLevel* можно использовать для построения пользовательских визуальных фильтров.

Контрольный список безопасности

  • Убедитесь, что счёт допускает торговлю указанным объёмом и что брокер корректно интерпретирует выбранный тип объёма.
  • Отслеживайте задержки исполнения: стратегия предполагает исполнение заявок близко к рыночной цене, поскольку работает на закрытии свечи.
  • Периодически анализируйте логи изменения цветов, чтобы подтвердить корректность аппроксимаций после обновлений StockSharp или смены параметров.
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>
/// Exp i-KlPrice Vol Direct strategy using EMA crossover with volume-weighted confirmation.
/// Buys when fast EMA crosses above slow EMA. Sells on reverse crossover.
/// </summary>
public class ExpIKlPriceVolDirectStrategy : 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 a new instance of the <see cref="ExpIKlPriceVolDirectStrategy"/> class.
	/// </summary>
	public ExpIKlPriceVolDirectStrategy()
	{
		_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;
	}
}