Auf GitHub ansehen

Exp i-KlPrice Vol Strategy

Overview

This strategy is a C# conversion of the MetaTrader expert Exp_i-KlPrice_Vol.mq5. It rebuilds the KlPrice oscillator that measures the distance between price and a volatility band, multiplies the oscillator by candle volume and tracks colour transitions generated by adaptive thresholds. Two independent position slots are emulated for each direction, mirroring the dual-magic behaviour of the original expert advisor.

Indicator Logic

  • Price is transformed using the selected AppliedPrice mode (close, open, median, Demark, etc.).
  • The transformed price is smoothed by the moving-average method defined in PriceMaMethod and PriceMaLength.
  • Candle range (High - Low) is smoothed with RangeMaMethod/RangeMaLength. The range acts as a dynamic band width.
  • The base KlPrice oscillator is calculated as 100 * (Price - (MA - RangeMA)) / (2 * RangeMA) - 50.
  • The oscillator is multiplied by the selected volume source (AppliedVolume.Tick or AppliedVolume.Real).
  • A Jurik smoother of length SmoothingLength is applied both to the oscillator and to the raw volume, creating two adaptive series.
  • Adaptive thresholds are obtained by multiplying the smoothed volume by HighLevel2, HighLevel1, LowLevel1, and LowLevel2.
  • The current oscillator colour is determined by comparing the smoothed oscillator value with the adaptive thresholds:
    • 4 – above HighLevel2 * volume (extreme bullish pressure).
    • 3 – between HighLevel1 * volume and the extreme level.
    • 2 – between the bullish and bearish thresholds.
    • 1 – between the lower threshold and the neutral line.
    • 0 – below LowLevel2 * volume (extreme bearish pressure).

Trading Rules

  1. Evaluate the colour at SignalBar (usually the previous completed candle) and the colour before it.
  2. Long entries:
    • Slot 1 opens when colour changes from 4 to any value below 4 and AllowLongEntry is true.
    • Slot 2 opens when colour changes from 3 to below 3.
  3. Short entries:
    • Slot 1 opens when colour rises from 0 to above 0 and AllowShortEntry is true.
    • Slot 2 opens when colour rises from 1 to above 1.
  4. Long exits occur when the earlier colour was 0 or 1 and AllowLongExit is enabled.
  5. Short exits occur when the earlier colour was 4 or 3 and AllowShortExit is enabled.
  6. Each slot keeps track of the last signal time to avoid duplicate orders on the same candle. Protective stops are optional and handled through StartProtection when StopLossPoints or TakeProfitPoints are greater than zero.

Parameters

Name Type Default Description
PrimaryVolume decimal 0.1 Volume used by the first long/short slot.
SecondaryVolume decimal 0.2 Volume used by the second slot.
StopLossPoints int 1000 Optional protective stop distance in price steps.
TakeProfitPoints int 2000 Optional take-profit distance in price steps.
AllowLongEntry bool true Enable opening long positions.
AllowShortEntry bool true Enable opening short positions.
AllowLongExit bool true Close long positions when bearish colours appear.
AllowShortExit bool true Close short positions when bullish colours appear.
CandleType DataType H8 Candle timeframe for calculations.
PriceMaMethod SmoothMethod Sma Moving-average type used on the applied price.
PriceMaLength int 100 Length of the price smoother.
PriceMaPhase int 15 Phase parameter for Jurik-based filters.
RangeMaMethod SmoothMethod Jjma Moving-average type used on the candle range.
RangeMaLength int 20 Length of the range smoother.
RangeMaPhase int 100 Phase parameter for the range smoother.
SmoothingLength int 20 Jurik smoothing length applied to oscillator and volume.
AppliedPrice AppliedPrice Close Price source used in oscillator calculations.
VolumeType AppliedVolume Tick Volume source multiplied by the oscillator.
HighLevel2 int 150 Upper extreme multiplier for the adaptive threshold.
HighLevel1 int 20 Upper moderate multiplier.
LowLevel1 int -20 Lower moderate multiplier.
LowLevel2 int -150 Lower extreme multiplier.
SignalBar int 1 Historical offset used to read colour transitions.

Usage Notes

  • Attach the strategy to a security that provides both price and volume information; when only tick volume is available, the tick counter is used as a proxy.
  • The two slot volumes can be tuned independently to emulate the original EA’s dual money-management setup.
  • Adjust SignalBar when working with partially formed candles or when re-synchronising historical data.
  • The smoothing methods support Jurik filters through reflection to replicate the behaviour of the MQL SmoothAlgorithms library.
  • Since StartProtection is invoked only when stop or target distances are positive, leave them at zero to disable protective orders.
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;
	}
}