Esta estrategia es una conversión en C# del experto de MetaTrader Exp_i-KlPrice_Vol.mq5. Reconstruye el oscilador KlPrice
que mide la distancia entre el precio y una banda de volatilidad, multiplica el oscilador por el volumen de la vela y rastrea
las transiciones de color generadas por umbrales adaptativos. Se emulan dos ranuras de posición independientes para cada
dirección, reflejando el comportamiento de doble magic del asesor experto original.
Lógica del Indicador
El precio se transforma usando el modo AppliedPrice seleccionado (close, open, median, Demark, etc.).
El precio transformado se suaviza por el método de media móvil definido en PriceMaMethod y PriceMaLength.
El rango de la vela (High - Low) se suaviza con RangeMaMethod/RangeMaLength. El rango actúa como ancho dinámico de banda.
El oscilador KlPrice base se calcula como 100 * (Price - (MA - RangeMA)) / (2 * RangeMA) - 50.
El oscilador se multiplica por la fuente de volumen seleccionada (AppliedVolume.Tick o AppliedVolume.Real).
Se aplica un suavizador Jurik de longitud SmoothingLength tanto al oscilador como al volumen bruto, creando dos series
adaptativas.
Los umbrales adaptativos se obtienen multiplicando el volumen suavizado por HighLevel2, HighLevel1, LowLevel1 y LowLevel2.
El color actual del oscilador se determina comparando el valor del oscilador suavizado con los umbrales adaptativos:
4 – por encima de HighLevel2 * volume (presión alcista extrema).
3 – entre HighLevel1 * volume y el nivel extremo.
2 – entre los umbrales alcista y bajista.
1 – entre el umbral inferior y la línea neutral.
0 – por debajo de LowLevel2 * volume (presión bajista extrema).
Reglas de Trading
Evaluar el color en SignalBar (generalmente la vela completada anterior) y el color antes de él.
Entradas largas:
La Ranura 1 se abre cuando el color cambia de 4 a cualquier valor por debajo de 4 y AllowLongEntry es true.
La Ranura 2 se abre cuando el color cambia de 3 a por debajo de 3.
Entradas cortas:
La Ranura 1 se abre cuando el color sube de 0 a por encima de 0 y AllowShortEntry es true.
La Ranura 2 se abre cuando el color sube de 1 a por encima de 1.
Las salidas largas ocurren cuando el color anterior era 0 o 1 y AllowLongExit está habilitado.
Las salidas cortas ocurren cuando el color anterior era 4 o 3 y AllowShortExit está habilitado.
Cada ranura rastrea el tiempo del último señal para evitar órdenes duplicadas en la misma vela. Los stops de protección son
opcionales y se manejan a través de StartProtection cuando StopLossPoints o TakeProfitPoints son mayores que cero.
Parámetros
Nombre
Tipo
Valor predeterminado
Descripción
PrimaryVolume
decimal
0.1
Volumen usado por la primera ranura largo/corto.
SecondaryVolume
decimal
0.2
Volumen usado por la segunda ranura.
StopLossPoints
int
1000
Distancia de stop de protección opcional en pasos de precio.
TakeProfitPoints
int
2000
Distancia de take-profit opcional en pasos de precio.
AllowLongEntry
bool
true
Habilitar apertura de posiciones largas.
AllowShortEntry
bool
true
Habilitar apertura de posiciones cortas.
AllowLongExit
bool
true
Cerrar posiciones largas cuando aparecen colores bajistas.
AllowShortExit
bool
true
Cerrar posiciones cortas cuando aparecen colores alcistas.
CandleType
DataType
H8
Marco temporal de vela para cálculos.
PriceMaMethod
SmoothMethod
Sma
Tipo de media móvil usada en el precio aplicado.
PriceMaLength
int
100
Longitud del suavizador de precio.
PriceMaPhase
int
15
Parámetro de fase para filtros basados en Jurik.
RangeMaMethod
SmoothMethod
Jjma
Tipo de media móvil usada en el rango de la vela.
RangeMaLength
int
20
Longitud del suavizador de rango.
RangeMaPhase
int
100
Parámetro de fase para el suavizador de rango.
SmoothingLength
int
20
Longitud de suavizado Jurik aplicada al oscilador y volumen.
AppliedPrice
AppliedPrice
Close
Fuente de precio usada en cálculos del oscilador.
VolumeType
AppliedVolume
Tick
Fuente de volumen multiplicada por el oscilador.
HighLevel2
int
150
Multiplicador extremo superior para el umbral adaptativo.
HighLevel1
int
20
Multiplicador moderado superior.
LowLevel1
int
-20
Multiplicador moderado inferior.
LowLevel2
int
-150
Multiplicador extremo inferior.
SignalBar
int
1
Desplazamiento histórico usado para leer transiciones de color.
Notas de Uso
Conecte la estrategia a un instrumento que proporcione información de precio y volumen; cuando solo está disponible el volumen
de ticks, se usa el contador de ticks como proxy.
Los dos volúmenes de ranura pueden ajustarse independientemente para emular la configuración de gestión de dinero dual del EA
original.
Ajuste SignalBar cuando trabaje con velas parcialmente formadas o cuando resincronice datos históricos.
Los métodos de suavizado admiten filtros Jurik a través de reflexión para replicar el comportamiento de la biblioteca MQL
SmoothAlgorithms.
Dado que StartProtection se invoca solo cuando las distancias de stop o target son positivas, déjelas en cero para
deshabilitar las órdenes de protección.
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;
}
}
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_strategy(Strategy):
def __init__(self):
super(exp_i_kl_price_vol_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_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_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_strategy()