KlPrice リバーサル戦略
この戦略はオリジナルのMQL5エキスパートexp_i-KlPrice.mq5のC#変換版です。正規化された価格オシレーターに基づくリバーサルシステムを実装しています。オシレーターは現在の価格を、移動平均とATR(平均真の値幅)から導出された平滑化された価格バンドと比較します。事前定義された境界を越えると取引シグナルが生成されます。
仕組み
単純移動平均(SMA)が終値を平滑化します。
平均真の値幅(ATR)が市場のボラティリティを推定します。
オシレーターは次のように計算されます:
jres = 100 * (Close - (SMA - ATR)) / (2 * ATR) - 50オシレーター値は5つのカラーゾーンにマッピングされます:
- 4 – 上部レベルより上
- 3 – ゼロと上部レベルの間
- 2 – 上部レベルと下部レベルの間
- 1 – 下部レベルとゼロの間
- 0 – 下部レベルより下
オシレーターがゾーン4を離れるとロングポジションが開かれます。ゾーン0を離れるとショートポジションが開かれます。オシレーターがゼロを横切ると既存ポジションが決済されます。
パラメーター
| 名前 | 説明 |
|---|---|
CandleType |
価格データの時間軸。 |
PriceMaLength |
価格平滑化のSMA期間。 |
AtrLength |
価格バンド計算に使用するATR期間。 |
UpLevel |
オシレーターの上部閾値。 |
DownLevel |
オシレーターの下部閾値。 |
EnableBuy |
ロングポジションの開設を許可。 |
EnableSell |
ショートポジションの開設を許可。 |
使用方法
KlPriceReversalStrategyのインスタンスを作成。- 希望するパラメーターを設定。
- 戦略をポートフォリオと銘柄に接続。
- シグナルを受け取り注文を発注するために戦略を開始。
戦略はBuyMarketとSellMarketを通じてマーケットオーダーを使用します。ポジション保護はStartProtection()で有効化されます。
備考
- 実装はStockSharpの組み込みインジケーター(
SimpleMovingAverageとAverageTrueRange)を使用して元のMLQインジケーターを近似しています。 - すべての計算は完了したローソク足のみで実行されます。
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()