KlPrice Umkehr-Strategie
Diese Strategie ist eine C#-Konvertierung des ursprünglichen MQL5-Experten exp_i-KlPrice.mq5. Sie implementiert ein Umkehrsystem basierend auf einem normalisierten Kursoszillator. Der Oszillator vergleicht den aktuellen Kurs mit einem geglätteten Kursband, das aus einem gleitenden Durchschnitt und dem Average True Range (ATR) abgeleitet wird. Das Überschreiten vordefinierter Grenzen erzeugt Handelssignale.
Funktionsweise
Ein einfacher gleitender Durchschnitt (SMA) glättet den Schlusskurs.
Ein Average True Range (ATR) schätzt die Marktvolatilität.
Der Oszillator wird berechnet als:
jres = 100 * (Close - (SMA - ATR)) / (2 * ATR) - 50
Der Oszillatorwert wird fünf Farbzonen zugeordnet:
- 4 – über dem oberen Level
- 3 – zwischen null und dem oberen Level
- 2 – zwischen dem oberen und unteren Level
- 1 – zwischen dem unteren Level und null
- 0 – unter dem unteren Level
Eine Long-Position wird eröffnet, wenn der Oszillator die Zone 4 verlässt. Eine Short-Position wird eröffnet, wenn er die Zone 0 verlässt. Bestehende Positionen werden geschlossen, wenn der Oszillator null kreuzt.
Parameter
| Name |
Beschreibung |
CandleType |
Zeitrahmen für Preisdaten. |
PriceMaLength |
SMA-Periode zur Preisglättung. |
AtrLength |
ATR-Periode zur Berechnung des Kursbandes. |
UpLevel |
Oberer Schwellenwert des Oszillators. |
DownLevel |
Unterer Schwellenwert des Oszillators. |
EnableBuy |
Eröffnung von Long-Positionen erlauben. |
EnableSell |
Eröffnung von Short-Positionen erlauben. |
Verwendung
- Eine Instanz von
KlPriceReversalStrategy erstellen.
- Gewünschte Parameter einstellen.
- Strategie an ein Portfolio und ein Wertpapier anhängen.
- Strategie starten, um Signale zu erhalten und Orders zu platzieren.
Die Strategie verwendet Market-Orders über BuyMarket und SellMarket. Der Positionsschutz wird durch StartProtection() aktiviert.
Hinweise
- Die Implementierung approximiert den ursprünglichen MQL-Indikator mithilfe der integrierten StockSharp-Indikatoren (
SimpleMovingAverage und AverageTrueRange).
- Alle Berechnungen werden nur auf abgeschlossenen Kerzen durchgeführt.
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()