Esta estratégia é uma conversão em C# do especialista MetaTrader Exp_i-KlPrice_Vol.mq5. Reconstrói o oscilador KlPrice
que mede a distância entre o preço e uma banda de volatilidade, multiplica o oscilador pelo volume da candle e rastreia
transições de cor geradas por limiares adaptativos. Dois slots de posição independentes são emulados para cada direção,
espelhando o comportamento dual-magic do consultor especialista original.
Lógica do Indicador
O preço é transformado usando o modo AppliedPrice selecionado (close, open, median, Demark, etc.).
O preço transformado é suavizado pelo método de média móvel definido em PriceMaMethod e PriceMaLength.
O intervalo da candle (High - Low) é suavizado com RangeMaMethod/RangeMaLength. O intervalo age como largura dinâmica
de banda.
O oscilador KlPrice base é calculado como 100 * (Price - (MA - RangeMA)) / (2 * RangeMA) - 50.
O oscilador é multiplicado pela fonte de volume selecionada (AppliedVolume.Tick ou AppliedVolume.Real).
Um suavizador Jurik de comprimento SmoothingLength é aplicado tanto ao oscilador quanto ao volume bruto, criando duas
séries adaptativas.
Limiares adaptativos são obtidos multiplicando o volume suavizado por HighLevel2, HighLevel1, LowLevel1 e LowLevel2.
A cor atual do oscilador é determinada comparando o valor do oscilador suavizado com os limiares adaptativos:
4 – acima de HighLevel2 * volume (pressão altista extrema).
3 – entre HighLevel1 * volume e o nível extremo.
2 – entre os limiares altista e baixista.
1 – entre o limiar inferior e a linha neutra.
0 – abaixo de LowLevel2 * volume (pressão baixista extrema).
Regras de Trading
Avaliar a cor em SignalBar (geralmente a candle completada anterior) e a cor antes dela.
Entradas compradas:
O Slot 1 abre quando a cor muda de 4 para qualquer valor abaixo de 4 e AllowLongEntry é true.
O Slot 2 abre quando a cor muda de 3 para abaixo de 3.
Entradas vendidas:
O Slot 1 abre quando a cor sobe de 0 para acima de 0 e AllowShortEntry é true.
O Slot 2 abre quando a cor sobe de 1 para acima de 1.
Saídas compradas ocorrem quando a cor anterior era 0 ou 1 e AllowLongExit está habilitado.
Saídas vendidas ocorrem quando a cor anterior era 4 ou 3 e AllowShortExit está habilitado.
Cada slot rastreia o tempo do último sinal para evitar ordens duplicadas na mesma candle. Stops de proteção são opcionais
e são tratados através de StartProtection quando StopLossPoints ou TakeProfitPoints são maiores que zero.
Parâmetros
Nome
Tipo
Padrão
Descrição
PrimaryVolume
decimal
0.1
Volume usado pelo primeiro slot comprado/vendido.
SecondaryVolume
decimal
0.2
Volume usado pelo segundo slot.
StopLossPoints
int
1000
Distância de stop de proteção opcional em passos de preço.
TakeProfitPoints
int
2000
Distância de take-profit opcional em passos de preço.
AllowLongEntry
bool
true
Habilitar abertura de posições compradas.
AllowShortEntry
bool
true
Habilitar abertura de posições vendidas.
AllowLongExit
bool
true
Fechar posições compradas quando aparecem cores baixistas.
AllowShortExit
bool
true
Fechar posições vendidas quando aparecem cores altistas.
CandleType
DataType
H8
Período de candle para cálculos.
PriceMaMethod
SmoothMethod
Sma
Tipo de média móvel usada no preço aplicado.
PriceMaLength
int
100
Comprimento do suavizador de preço.
PriceMaPhase
int
15
Parâmetro de fase para filtros baseados em Jurik.
RangeMaMethod
SmoothMethod
Jjma
Tipo de média móvel usada no intervalo da candle.
RangeMaLength
int
20
Comprimento do suavizador de intervalo.
RangeMaPhase
int
100
Parâmetro de fase para o suavizador de intervalo.
SmoothingLength
int
20
Comprimento de suavização Jurik aplicado ao oscilador e volume.
AppliedPrice
AppliedPrice
Close
Fonte de preço usada nos cálculos do oscilador.
VolumeType
AppliedVolume
Tick
Fonte de volume multiplicada pelo oscilador.
HighLevel2
int
150
Multiplicador extremo superior para o limiar adaptativo.
HighLevel1
int
20
Multiplicador moderado superior.
LowLevel1
int
-20
Multiplicador moderado inferior.
LowLevel2
int
-150
Multiplicador extremo inferior.
SignalBar
int
1
Offset histórico usado para ler transições de cor.
Notas de Uso
Conecte a estratégia a um instrumento que forneça informações de preço e volume; quando apenas o volume de ticks está
disponível, o contador de ticks é usado como proxy.
Os dois volumes de slot podem ser ajustados independentemente para emular a configuração de gestão de dinheiro dual do EA
original.
Ajuste SignalBar ao trabalhar com candles parcialmente formadas ou ao ressincronizar dados históricos.
Os métodos de suavização suportam filtros Jurik através de reflexão para replicar o comportamento da biblioteca MQL
SmoothAlgorithms.
Como StartProtection é invocado apenas quando as distâncias de stop ou alvo são positivas, deixe-as em zero para
desabilitar as ordens de proteção.
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()