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;
private readonly StrategyParam<bool> _enableBuy, _enableSell;
private Order _pending;
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 bool EnableBuy { get => _enableBuy.Value; set => _enableBuy.Value = value; }
public bool EnableSell { get => _enableSell.Value; set => _enableSell.Value = value; }
public KlPriceReversalStrategy()
{
_enableBuy = Param(nameof(EnableBuy), true);
_enableSell = Param(nameof(EnableSell), true);
_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;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_isFirst = true;
_prevColor = 2m;
var priceMa = new SimpleMovingAverage { 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 && (_pending is null || _pending.State is OrderStates.Done or OrderStates.Failed))
{
if (Position > 0m && color <= 2m) _pending = SellMarket(Position);
else if (Position < 0m && color >= 2m) _pending = BuyMarket(-Position);
else if (_prevColor == 4m && color < 4m && Position == 0m && EnableBuy) _pending = BuyMarket(Volume);
else if (_prevColor == 0m && color > 0m && Position == 0m && EnableSell) _pending = SellMarket(Volume);
}
_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, OrderStates
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class kl_price_reversal_strategy(Strategy):
def __init__(self):
super(kl_price_reversal_strategy, self).__init__()
self._enable_buy = self.Param("EnableBuy", True)
self._enable_sell = self.Param("EnableSell", True)
self._pending = None
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
self._pending = None
@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 = SimpleMovingAverage()
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 and self.IsFormedAndOnlineAndAllowTrading() and (self._pending is None or self._pending.State in (OrderStates.Done, OrderStates.Failed)):
if self.Position > 0 and color <= 2:
self._pending = self.SellMarket(self.Position)
elif self.Position < 0 and color >= 2:
self._pending = self.BuyMarket(-self.Position)
elif self._prev_color == 4 and color < 4 and self.Position == 0 and self._enable_buy.Value:
self._pending = self.BuyMarket(self.Volume)
elif self._prev_color == 0 and color > 0 and self.Position == 0 and self._enable_sell.Value:
self._pending = self.SellMarket(self.Volume)
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()