The Exp i-KlPrice Vol Direct Strategy is a StockSharp adaptation of the MetaTrader 5 expert advisor Exp_i-KlPrice_Vol_Direct. The original system multiplies a custom KlPrice oscillator by volume, smooths it with several moving-average stages, and reacts to changes in the slope of the resulting line. The port keeps the multi-stage processing chain, exposes the same configurable parameters, and executes trades through StockSharp’s high-level API on completed candles.
Key ideas preserved from the MQL5 version:
Two-stage smoothing of price and range – price data is filtered by a configurable moving average, the high-low range is smoothed separately, and the results form adaptive dynamic bands.
Volume weighting – the oscillator output is multiplied by the selected volume stream (tick or real) before a final Jurik filter, amplifying moves that occur on higher activity.
Directional colour map – the strategy monitors the sign of the smoothed oscillator slope. A flip from bearish to bullish colour opens longs and closes shorts; the opposite flip opens shorts and closes longs.
Signal delay – SignalBar lets the user require additional closed candles before acting, matching the “confirmed bar” logic of the source code.
Processing Pipeline
Applied Price Selection – choose from the same twelve applied-price formulas as the MQL indicator (Close, Open, Median, Demark, TrendFollow, etc.).
Primary Smoothing – apply PriceMethod over PriceLength bars with optional PricePhase (effective for Jurik-based filters). The supported algorithms map the original SmoothAlgorithms library to StockSharp indicators:
Sma → SimpleMovingAverage
Ema → ExponentialMovingAverage
Smma → SmoothedMovingAverage
Lwma → WeightedMovingAverage
Jjma → JurikMovingAverage (phase honoured when available)
Jurx → ZeroLagExponentialMovingAverage
Parma → ArnaudLegouxMovingAverage (phase mapped to ALMA offset)
T3 → TripleExponentialMovingAverage
Vidya → approximated with ExponentialMovingAverage
Ama → KaufmanAdaptiveMovingAverage
Range Smoothing – repeat the same procedure for the candle range (High - Low) using RangeMethod, RangeLength, and RangePhase. The smoothed range builds adaptive bands around the price smoother.
Oscillator Construction – compute (Price - (PriceMA - RangeMA)) / (2 * RangeMA) * 100 - 50, identical to the MQL formula, and multiply by the selected volume stream (VolumeSource).
Final Jurik Filter – the weighted oscillator and the raw volume stream are both passed through Jurik moving averages with period ResultLength (phase fixed to 100, mirroring the EA).
Colour Detection – compare the latest smoothed oscillator value with the previous one. Rising values colour the bar bullish (0), falling values colour it bearish (1), equal values inherit the prior colour. Colours are stored chronologically to honour the SignalBar delay.
Trading Logic
Long Side
Entry: When the colour at the signal bar (SignalBar) is bullish (0) and the immediately older colour is bearish (1), open a long position if AllowLongEntries = true and the current net position is non-positive. The order size equals Volume + |Position| to flatten opposing exposure first.
Exit: If the signal-bar colour is bullish and AllowShortExits = true, close any open short positions.
Short Side
Entry: When the signal-bar colour turns bearish (1) after being bullish (0), open a short position if AllowShortEntries = true and the current net position is non-negative.
Exit: If the signal-bar colour is bearish and AllowLongExits = true, close existing long exposure.
Trades are generated on finished candles. The strategy relies on the base Strategy.Volume property for position sizing; the MQL money-management modes are intentionally not replicated.
Parameter Reference
Parameter
Description
Default
CandleType
Timeframe of the analysed candles.
H4
VolumeSource
Volume stream used for weighting (Tick or Real; both map to candle.TotalVolume).
Tick
PriceMethod / PriceLength / PricePhase
Primary smoothing algorithm, period, and Jurik phase for the applied price.
Sma, 100, 15
RangeMethod / RangeLength / RangePhase
Smoothing algorithm, period, and phase for the candle range.
Jjma, 20, 100
ResultLength
Jurik period applied to the volume-weighted oscillator and the volume stream.
20
PriceMode
Applied-price formula (Close, Open, Median, Demark, TrendFollow0/1, etc.).
Close
HighLevel2, HighLevel1, LowLevel1, LowLevel2
Level multipliers retained for visual diagnostics; they do not alter signals.
0, 0, 0, 0
SignalBar
Number of fully closed candles to skip before evaluating the colour flip.
1
AllowLongEntries / AllowShortEntries
Permission flags for opening long/short trades.
true
AllowLongExits / AllowShortExits
Permission flags for closing existing positions on opposite colour.
true
StopLossPoints / TakeProfitPoints
Protective offsets in price points (multiplied by PriceStep) passed to StartProtection.
1000, 2000
Risk Management
Stop-loss and take-profit levels are translated into UnitTypes.Point offsets and managed by StartProtection. Set either value to 0 to disable the respective protection.
The position size is entirely controlled by Strategy.Volume; adjust it to match the instrument contract size.
Colours are evaluated only when the strategy is formed, online, and trading is allowed (IsFormedAndOnlineAndAllowTrading() safeguard).
Limitations & Differences vs. MQL5
Smoothing approximations: Jurik, SMA, EMA, SMMA, LWMA, and KAMA match directly. Jurx, Parma, and Vidya are mapped to the closest StockSharp indicators (ZeroLag EMA, ALMA, EMA respectively), so exotic combinations may deviate slightly from the MT5 output.
Volume data: StockSharp candles expose only total volume; if tick counts are required, feed custom candles where TotalVolume represents tick volume.
Money management: Margin modes (MM, MarginMode) from the original EA are not ported. Use Volume or external portfolio management to size trades.
Execution timing: Orders are sent immediately after the signal candle closes, instead of scheduling at the start of the next bar (TimeShiftSec in MT5). Behaviour is equivalent for market orders in most setups.
Usage Notes
Attach the strategy to the desired security, configure Volume, and ensure Security.PriceStep is correct.
Choose the candle timeframe via CandleType. Multi-timeframe behaviour is not required; the strategy subscribes to a single series.
Tune the smoothing methods and lengths to reproduce the indicator behaviour you expect. Use the chart layers (DrawCandles, DrawIndicator) to verify the oscillator visually.
Adjust SignalBar if you need faster (0) or more confirmed (≥1) reactions.
Start in paper trading to validate risk parameters before deploying to production.
Optimisation Ideas
Optimise PriceLength, RangeLength, and ResultLength together to balance responsiveness and noise reduction.
Test different applied-price formulas (PriceMode) to find the variant that tracks your market best.
Experiment with SignalBar delays to smooth whipsaws in choppy conditions.
Use HighLevel* and LowLevel* values to build custom visual overlays or filters in analytics dashboards.
Safety Checklist
Confirm that the account has permission to trade the selected volume and that the broker enforces comparable tick/real volume semantics.
Monitor execution latency; the strategy assumes fills close to market price because orders are sent on bar close.
Keep logs of colour transitions to ensure the smoothing approximations match expectations when upgrading StockSharp or adding new indicators.
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;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_i_kl_price_vol_direct_strategy(Strategy):
def __init__(self):
super(exp_i_kl_price_vol_direct_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 50) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 200) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(exp_i_kl_price_vol_direct_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_i_kl_price_vol_direct_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# EMA crossover
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_i_kl_price_vol_direct_strategy()