Стратегия KlPrice Reversal
Стратегия представляет собой перевод на C# оригинального эксперта MQL5 exp_i-KlPrice.mq5. Она реализует разворотный подход на основе нормализованного ценового осциллятора. Осциллятор сравнивает текущую цену с сглаженной ценовой полосой, построенной по скользящей средней и среднему истинному диапазону (ATR). Пересечение заданных границ формирует торговые сигналы.
Как работает
Простая скользящая средняя (SMA) сглаживает цену закрытия.
Индикатор Average True Range (ATR) оценивает волатильность рынка.
Осциллятор вычисляется по формуле:
jres = 100 * (Close - (SMA - ATR)) / (2 * ATR) - 50
Значение осциллятора распределяется по пяти зонам:
- 4 – выше верхнего уровня
- 3 – между нулём и верхним уровнем
- 2 – между верхним и нижним уровнями
- 1 – между нижним уровнем и нулём
- 0 – ниже нижнего уровня
Длинная позиция открывается, когда осциллятор выходит из зоны 4. Короткая позиция открывается, когда он выходит из зоны 0. Открытые позиции закрываются при пересечении нуля.
Параметры
| Имя |
Описание |
CandleType |
Таймфрейм ценовых данных. |
PriceMaLength |
Период SMA для сглаживания цены. |
AtrLength |
Период ATR для оценки диапазона. |
UpLevel |
Верхний порог осциллятора. |
DownLevel |
Нижний порог осциллятора. |
EnableBuy |
Разрешить открытие длинных позиций. |
EnableSell |
Разрешить открытие коротких позиций. |
Использование
- Создайте экземпляр
KlPriceReversalStrategy.
- Установите необходимые параметры.
- Подключите стратегию к портфелю и инструменту.
- Запустите стратегию для получения сигналов и регистрации заявок.
Для входа и выхода используются рыночные заявки BuyMarket и SellMarket. Защита позиции активируется через StartProtection().
Примечания
- Реализация приближает оригинальный индикатор MQL, используя встроенные индикаторы StockSharp (
SimpleMovingAverage и AverageTrueRange).
- Все расчёты выполняются только по завершённым свечам.
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 reversal strategy.
/// Calculates a normalized oscillator based on price position relative to EMA and ATR.
/// Buys when leaving overbought zone, sells when leaving oversold zone.
/// </summary>
public class KlPriceReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _priceMaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _upLevel;
private readonly StrategyParam<decimal> _downLevel;
private decimal _prevColor;
private bool _isFirst;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int PriceMaLength { get => _priceMaLength.Value; set => _priceMaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public decimal UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
public decimal DownLevel { get => _downLevel.Value; set => _downLevel.Value = value; }
public KlPriceReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for calculations", "General");
_priceMaLength = Param(nameof(PriceMaLength), 100)
.SetGreaterThanZero()
.SetDisplay("Price MA Length", "EMA period for price smoothing", "Parameters");
_atrLength = Param(nameof(AtrLength), 20)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period for range estimation", "Parameters");
_upLevel = Param(nameof(UpLevel), 50m)
.SetDisplay("Upper Level", "Upper threshold for signals", "Parameters");
_downLevel = Param(nameof(DownLevel), -50m)
.SetDisplay("Lower Level", "Lower threshold for signals", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevColor = 2m;
_isFirst = true;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_isFirst = true;
_prevColor = 2m;
var priceMa = new ExponentialMovingAverage { Length = PriceMaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(priceMa, atr, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue maValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!maValue.IsFormed || !atrValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma = maValue.ToDecimal();
var tr = atrValue.ToDecimal();
if (tr == 0m)
return;
var dwband = ma - tr;
var jres = 100m * (candle.ClosePrice - dwband) / (2m * tr) - 50m;
var color = 2m;
if (jres > UpLevel)
color = 4m;
else if (jres > 0m)
color = 3m;
if (jres < DownLevel)
color = 0m;
else if (jres < 0m)
color = 1m;
if (!_isFirst)
{
// Buy: leaving overbought (was 4, now < 4)
if (_prevColor == 4m && color < 4m && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Sell: leaving oversold (was 0, now > 0)
else if (_prevColor == 0m && color > 0m && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
_prevColor = color;
_isFirst = false;
}
}
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 AverageTrueRange, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class kl_price_reversal_strategy(Strategy):
def __init__(self):
super(kl_price_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame for calculations", "General")
self._price_ma_length = self.Param("PriceMaLength", 100) \
.SetDisplay("Price MA Length", "EMA period for price smoothing", "Parameters")
self._atr_length = self.Param("AtrLength", 20) \
.SetDisplay("ATR Length", "ATR period for range estimation", "Parameters")
self._up_level = self.Param("UpLevel", 50.0) \
.SetDisplay("Upper Level", "Upper threshold for signals", "Parameters")
self._down_level = self.Param("DownLevel", -50.0) \
.SetDisplay("Lower Level", "Lower threshold for signals", "Parameters")
self._prev_color = 2.0
self._is_first = True
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def PriceMaLength(self):
return self._price_ma_length.Value
@PriceMaLength.setter
def PriceMaLength(self, value):
self._price_ma_length.Value = value
@property
def AtrLength(self):
return self._atr_length.Value
@AtrLength.setter
def AtrLength(self, value):
self._atr_length.Value = value
@property
def UpLevel(self):
return self._up_level.Value
@UpLevel.setter
def UpLevel(self, value):
self._up_level.Value = value
@property
def DownLevel(self):
return self._down_level.Value
@DownLevel.setter
def DownLevel(self, value):
self._down_level.Value = value
def OnStarted2(self, time):
super(kl_price_reversal_strategy, self).OnStarted2(time)
self._is_first = True
self._prev_color = 2.0
price_ma = ExponentialMovingAverage()
price_ma.Length = self.PriceMaLength
atr = AverageTrueRange()
atr.Length = self.AtrLength
self.SubscribeCandles(self.CandleType) \
.BindEx(price_ma, atr, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, ma_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not ma_value.IsFormed or not atr_value.IsFormed:
return
ma = float(ma_value)
tr = float(atr_value)
if tr == 0:
return
close = float(candle.ClosePrice)
dwband = ma - tr
jres = 100.0 * (close - dwband) / (2.0 * tr) - 50.0
up = float(self.UpLevel)
dn = float(self.DownLevel)
color = 2.0
if jres > up:
color = 4.0
elif jres > 0:
color = 3.0
if jres < dn:
color = 0.0
elif jres < 0:
color = 1.0
if not self._is_first:
if self._prev_color == 4.0 and color < 4.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_color == 0.0 and color > 0.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_color = color
self._is_first = False
def OnReseted(self):
super(kl_price_reversal_strategy, self).OnReseted()
self._prev_color = 2.0
self._is_first = True
def CreateClone(self):
return kl_price_reversal_strategy()