La Estrategia Exp i-KlPrice Vol Directo es una adaptación de StockSharp del asesor experto de MetaTrader 5
Exp_i-KlPrice_Vol_Direct. El sistema original multiplica un oscilador KlPrice personalizado por el volumen, lo suaviza con
varias etapas de media móvil y reacciona a los cambios en la pendiente de la línea resultante. El port mantiene la cadena de
procesamiento de múltiples etapas, expone los mismos parámetros configurables y ejecuta operaciones a través de la API de alto
nivel de StockSharp en velas completadas.
Ideas clave preservadas de la versión MQL5:
Suavizado de dos etapas de precio y rango – los datos de precio se filtran por una media móvil configurable, el rango
máximo-mínimo se suaviza por separado y los resultados forman bandas dinámicas adaptativas.
Ponderación de volumen – la salida del oscilador se multiplica por el flujo de volumen seleccionado (tick o real) antes
de un filtro Jurik final, amplificando los movimientos que ocurren en mayor actividad.
Mapa de color direccional – la estrategia monitorea el signo de la pendiente del oscilador suavizado. Un cambio de
color bajista a alcista abre largos y cierra cortos; el cambio opuesto abre cortos y cierra largos.
Retraso de señal – SignalBar permite al usuario requerir velas cerradas adicionales antes de actuar, coincidiendo con
la lógica de "barra confirmada" del código fuente.
Pipeline de Procesamiento
Selección de Precio Aplicado – elegir entre las mismas doce fórmulas de precio aplicado que el indicador MQL (Close,
Open, Median, Demark, TrendFollow, etc.).
Suavizado Primario – aplicar PriceMethod sobre PriceLength barras con PricePhase opcional (efectivo para filtros
basados en Jurik).
Suavizado de Rango – repetir el mismo procedimiento para el rango de la vela (High - Low) usando RangeMethod,
RangeLength y RangePhase.
Construcción del Oscilador – calcular (Price - (PriceMA - RangeMA)) / (2 * RangeMA) * 100 - 50, idéntico a la
fórmula MQL, y multiplicar por el flujo de volumen seleccionado (VolumeSource).
Filtro Jurik Final – el oscilador ponderado por volumen y el flujo de volumen bruto pasan por medias móviles Jurik
con período ResultLength.
Detección de Color – comparar el valor más reciente del oscilador suavizado con el anterior. Los valores crecientes
colorean la barra de alcista (0), los decrecientes de bajista (1), los iguales heredan el color anterior.
Lógica de Trading
Lado Largo
Entrada: cuando el color en la barra de señal (SignalBar) es alcista (0) y el color inmediatamente anterior es bajista
(1), abrir una posición larga si AllowLongEntries = true y la posición neta actual no es positiva.
Salida: si el color de la barra de señal es alcista y AllowShortExits = true, cerrar cualquier posición corta abierta.
Lado Corto
Entrada: cuando el color de la barra de señal se vuelve bajista (1) después de ser alcista (0), abrir una posición
corta si AllowShortEntries = true y la posición neta actual no es negativa.
Salida: si el color de la barra de señal es bajista y AllowLongExits = true, cerrar la exposición larga existente.
Referencia de Parámetros
Parámetro
Descripción
Valor predeterminado
CandleType
Marco temporal de las velas analizadas.
H4
VolumeSource
Flujo de volumen para la ponderación (Tick o Real).
Tick
PriceMethod / PriceLength / PricePhase
Algoritmo de suavizado primario, período y fase Jurik para el precio aplicado.
Sma, 100, 15
RangeMethod / RangeLength / RangePhase
Algoritmo de suavizado, período y fase para el rango de la vela.
Jjma, 20, 100
ResultLength
Período Jurik para el oscilador ponderado por volumen y el flujo de volumen.
20
PriceMode
Fórmula de precio aplicado (Close, Open, Median, Demark, TrendFollow0/1, etc.).
Close
HighLevel2, HighLevel1, LowLevel1, LowLevel2
Multiplicadores de nivel para diagnóstico visual; no alteran señales.
0, 0, 0, 0
SignalBar
Número de velas cerradas a saltar antes de evaluar el cambio de color.
1
AllowLongEntries / AllowShortEntries
Indicadores de permiso para abrir operaciones largas/cortas.
true
AllowLongExits / AllowShortExits
Indicadores de permiso para cerrar posiciones existentes en color opuesto.
true
StopLossPoints / TakeProfitPoints
Desplazamientos de protección en puntos de precio pasados a StartProtection.
1000, 2000
Gestión de Riesgos
Los niveles de stop-loss y take-profit se traducen en desplazamientos UnitTypes.Point y son gestionados por
StartProtection. Establecer cualquier valor en 0 para deshabilitar la protección respectiva.
El tamaño de posición está completamente controlado por Strategy.Volume.
Los colores se evalúan solo cuando la estrategia está formada, en línea y el trading está permitido.
Limitaciones y Diferencias vs. MQL5
Las aproximaciones de suavizado más exóticas pueden desviarse ligeramente de la salida de MT5.
Los datos de volumen de StockSharp exponen solo el volumen total.
Los modos de gestión de dinero del EA original no están portados.
Las órdenes se envían inmediatamente después del cierre de la vela de señal.
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()