デュアル Keltner チャンネル戦略
デュアル Keltner チャンネル戦略は、異なる乗数を持つ 2 つの Keltner チャンネルを使用してブレイクアウトを検出します。 価格が外側のバンドを突破し、内側のバンドに戻ったときに取引を開始します。 ストップとターゲットは固定パーセンテージで管理します。
詳細
- エントリー条件: 価格が外側の Keltner バンドを越え、同じ方向に内側のバンドを再び越える。
- ロング/ショート: 両方向。
- エグジット条件: ストップロス、テイクプロフィット、または逆シグナル。
- ストップ: あり、パーセンテージベース。
- デフォルト値:
EmaPeriod = 50InnerMultiplier = 2.75mOuterMultiplier = 3.75mMaxStopPercent = 10mSlTpRatio = 1mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: Keltner
- ストップ: あり
- 複雑さ: 中級
- 時間軸: イントラデイ (5m)
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Dual Keltner Channels strategy.
/// Both channels are EMA(EmaPeriod) plus and minus a multiple of ATR(EmaPeriod): InnerMultiplier for the inner channel and
/// OuterMultiplier for the outer one. A low below the lower outer band arms a long, which is taken when the close then crosses back
/// above the lower inner band; a high above the upper outer band arms a short, taken when the close crosses back below the upper inner
/// band. An opposite signal reverses the position. The stop is MaxStopPercent from the entry and the take profit SlTpRatio times that.
/// </summary>
public class DualKeltnerChannelsStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _innerMultiplier;
private readonly StrategyParam<decimal> _outerMultiplier;
private readonly StrategyParam<decimal> _maxStopPercent;
private readonly StrategyParam<decimal> _slTpRatio;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevClose;
private decimal? _prevInnerUpper;
private decimal? _prevInnerLower;
private bool _longArmed;
private bool _shortArmed;
/// <summary>
/// EMA and ATR period of the channels.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the inner channel.
/// </summary>
public decimal InnerMultiplier
{
get => _innerMultiplier.Value;
set => _innerMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier of the outer channel.
/// </summary>
public decimal OuterMultiplier
{
get => _outerMultiplier.Value;
set => _outerMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal MaxStopPercent
{
get => _maxStopPercent.Value;
set => _maxStopPercent.Value = value;
}
/// <summary>
/// Take profit as a multiple of the stop distance.
/// </summary>
public decimal SlTpRatio
{
get => _slTpRatio.Value;
set => _slTpRatio.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DualKeltnerChannelsStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA and ATR period of the channels", "Indicators");
_innerMultiplier = Param(nameof(InnerMultiplier), 2.75m)
.SetGreaterThanZero()
.SetDisplay("Inner Multiplier", "ATR multiplier of the inner channel", "Indicators");
_outerMultiplier = Param(nameof(OuterMultiplier), 3.75m)
.SetGreaterThanZero()
.SetDisplay("Outer Multiplier", "ATR multiplier of the outer channel", "Indicators");
_maxStopPercent = Param(nameof(MaxStopPercent), 10m)
.SetNotNegative()
.SetDisplay("Max Stop %", "Stop loss percentage from entry price", "Risk");
_slTpRatio = Param(nameof(SlTpRatio), 1m)
.SetNotNegative()
.SetDisplay("SL/TP Ratio", "Take profit as a multiple of the stop distance", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevClose = null;
_prevInnerUpper = null;
_prevInnerLower = null;
_longArmed = false;
_shortArmed = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, atr, ProcessCandle)
.Start();
var stop = MaxStopPercent > 0 ? new Unit(MaxStopPercent, UnitTypes.Percent) : new Unit();
var takePercent = MaxStopPercent * SlTpRatio;
var take = takePercent > 0 ? new Unit(takePercent, UnitTypes.Percent) : new Unit();
StartProtection(take, stop, useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!emaValue.IsFormed || !atrValue.IsFormed)
return;
var ema = emaValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
var innerUpper = ema + atr * InnerMultiplier;
var innerLower = ema - atr * InnerMultiplier;
var outerUpper = ema + atr * OuterMultiplier;
var outerLower = ema - atr * OuterMultiplier;
if (candle.LowPrice < outerLower)
_longArmed = true;
if (candle.HighPrice > outerUpper)
_shortArmed = true;
var prevClose = _prevClose;
var prevInnerUpper = _prevInnerUpper;
var prevInnerLower = _prevInnerLower;
_prevClose = close;
_prevInnerUpper = innerUpper;
_prevInnerLower = innerLower;
if (prevClose is not decimal pc || prevInnerUpper is not decimal piu || prevInnerLower is not decimal pil)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var longSignal = _longArmed && pc <= pil && close > innerLower;
var shortSignal = _shortArmed && pc >= piu && close < innerUpper;
if (longSignal)
{
_longArmed = false;
if (Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
}
else if (shortSignal)
{
_shortArmed = false;
if (Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class dual_keltner_channels_strategy(Strategy):
"""
Dual Keltner Channels strategy.
Both channels are EMA(EmaPeriod) plus and minus a multiple of ATR(EmaPeriod): InnerMultiplier for the inner channel and
OuterMultiplier for the outer one. A low below the lower outer band arms a long, which is taken when the close then crosses back
above the lower inner band; a high above the upper outer band arms a short, taken when the close crosses back below the upper inner
band. An opposite signal reverses the position. The stop is MaxStopPercent from the entry and the take profit SlTpRatio times that.
"""
def __init__(self):
super(dual_keltner_channels_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50).SetGreaterThanZero().SetDisplay("EMA Period", "EMA and ATR period of the channels", "Indicators")
self._inner_multiplier = self.Param("InnerMultiplier", 2.75).SetGreaterThanZero().SetDisplay("Inner Multiplier", "ATR multiplier of the inner channel", "Indicators")
self._outer_multiplier = self.Param("OuterMultiplier", 3.75).SetGreaterThanZero().SetDisplay("Outer Multiplier", "ATR multiplier of the outer channel", "Indicators")
self._max_stop_percent = self.Param("MaxStopPercent", 10.0).SetNotNegative().SetDisplay("Max Stop %", "Stop loss percentage from entry price", "Risk")
self._sl_tp_ratio = self.Param("SlTpRatio", 1.0).SetNotNegative().SetDisplay("SL/TP Ratio", "Take profit as a multiple of the stop distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_close = None
self._prev_inner_upper = None
self._prev_inner_lower = None
self._long_armed = False
self._short_armed = False
def OnReseted(self):
super(dual_keltner_channels_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(dual_keltner_channels_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._ema_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, atr, self._process_candle).Start()
stop_percent = Decimal(self._max_stop_percent.Value)
take_percent = stop_percent * Decimal(self._sl_tp_ratio.Value)
stop = Unit(stop_percent, UnitTypes.Percent) if stop_percent > 0 else Unit()
take = Unit(take_percent, UnitTypes.Percent) if take_percent > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not ema_value.IsFormed or not atr_value.IsFormed:
return
ema = ema_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
inner = Decimal(self._inner_multiplier.Value)
outer = Decimal(self._outer_multiplier.Value)
inner_upper = ema + atr * inner
inner_lower = ema - atr * inner
outer_upper = ema + atr * outer
outer_lower = ema - atr * outer
if candle.LowPrice < outer_lower:
self._long_armed = True
if candle.HighPrice > outer_upper:
self._short_armed = True
pc = self._prev_close
piu = self._prev_inner_upper
pil = self._prev_inner_lower
self._prev_close = close
self._prev_inner_upper = inner_upper
self._prev_inner_lower = inner_lower
if pc is None or piu is None or pil is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
long_signal = self._long_armed and pc <= pil and close > inner_lower
short_signal = self._short_armed and pc >= piu and close < inner_upper
if long_signal:
self._long_armed = False
if self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal:
self._short_armed = False
if self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return dual_keltner_channels_strategy()