Keltner Volume 戦略
Keltner Channels + Volume 戦略の実装。平均を超える出来高で下部 Keltner Channel より下で引けたときに買い。平均を超える出来高で上部 Keltner Channel より上で引けたときに売り。
テストでは年平均リターン約 58% を示しています。株式市場で最も優れたパフォーマンスを発揮します。
Keltner Channel の境界は潜在的な反転点を示し、出来高の増加がその確信を裏付けます。価格がバンドに触れ出来高が拡大したときにシステムがトレードを行います。
ボラティリティバンド付近での出来高確認を求めるトレーダーに適したセットアップです。ストップは ATR から計算されます。
詳細
- エントリー条件:
- ロング:
Close < LowerBand && Volume > AvgVolume - ショート:
Close > UpperBand && Volume > AvgVolume
- ロング:
- ロング/ショート: 両方
- エグジット条件:
- 価格が EMA を突き抜ける
- ストップ:
StopLossAtrを使用した ATR ベース - デフォルト値:
EmaPeriod= 20AtrPeriod= 14Multiplier= 2.0mVolumeAvgPeriod= 20StopLossAtr= 2CandleType= TimeSpan.FromMinutes(5).TimeFrame() チャネルはEMAの上下にMultiplier倍のATRを加えたもので、AvgVolumeは直前VolumeAvgPeriod本の平均出来高です。ストップはエントリー時の終値からStopLossAtr倍のATRの位置にあり、足の終値で判定します。 保有ポジションと逆方向のエントリーシグナルはドテンになります。
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: Keltner Channel, 出来高
- ストップ: はい
- 複雑さ: 中級
- 時間軸: 中期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Keltner Volume strategy.
/// The Keltner Channel is the EmaPeriod EMA plus and minus Multiplier times the AtrPeriod ATR. A close below the lower band on volume above
/// the average of the previous VolumeAvgPeriod candles goes long and a close above the upper band on such volume goes short, reversing
/// an opposite position. The position closes when price crosses the EMA. The stop lies StopLossAtr ATR from the entry close
/// and is checked on candle closes.
/// </summary>
public class KeltnerVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<decimal> _stopLossAtr;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
private decimal _stopPrice;
/// <summary>
/// Period of the channel EMA.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Period of the channel and stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the channel width.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal StopLossAtr
{
get => _stopLossAtr.Value;
set => _stopLossAtr.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public KeltnerVolumeStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period of the channel EMA", "Keltner");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the channel and stop ATR", "Keltner");
_multiplier = Param(nameof(Multiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "ATR multiplier of the channel width", "Keltner");
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Average Period", "Previous candles the volume is averaged over", "Volume");
_stopLossAtr = Param(nameof(StopLossAtr), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "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();
_volumes.Clear();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
_stopPrice = default;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, atr, ProcessCandle)
.Start();
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;
// Volume is compared with the candles before this one.
var average = _volumes.Count == VolumeAvgPeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumeAvgPeriod)
_volumes.RemoveAt(0);
if (!emaValue.IsFormed || !atrValue.IsFormed || average is not decimal avgVolume)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var middle = emaValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var upper = middle + Multiplier * atr;
var lower = middle - Multiplier * atr;
var close = candle.ClosePrice;
var surge = candle.TotalVolume > avgVolume;
if (close < lower && surge && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - StopLossAtr * atr;
}
else if (close > upper && surge && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + StopLossAtr * atr;
}
else if (Position > 0 && (close > middle || (StopLossAtr > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close < middle || (StopLossAtr > 0 && close >= _stopPrice)))
{
BuyMarket(-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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class keltner_volume_strategy(Strategy):
"""
Keltner Volume strategy.
The Keltner Channel is the EmaPeriod EMA plus and minus Multiplier times the AtrPeriod ATR. A close below the lower band on volume above
the average of the previous VolumeAvgPeriod candles goes long and a close above the upper band on such volume goes short, reversing
an opposite position. The position closes when price crosses the EMA. The stop lies StopLossAtr ATR from the entry close
and is checked on candle closes.
"""
def __init__(self):
super(keltner_volume_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "Period of the channel EMA", "Keltner")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the channel and stop ATR", "Keltner")
self._multiplier = self.Param("Multiplier", 2.0).SetGreaterThanZero().SetDisplay("Multiplier", "ATR multiplier of the channel width", "Keltner")
self._volume_avg_period = self.Param("VolumeAvgPeriod", 20).SetGreaterThanZero().SetDisplay("Volume Average Period", "Previous candles the volume is averaged over", "Volume")
self._stop_loss_atr = self.Param("StopLossAtr", 2.0).SetNotNegative().SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "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._volumes = []
self._stop_price = Decimal(0)
def OnReseted(self):
super(keltner_volume_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(keltner_volume_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, atr, self._process_candle).Start()
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
# Volume is compared with the candles before this one.
period = self._volume_avg_period.Value
average = None
if len(self._volumes) == period:
total = Decimal(0)
for volume in self._volumes:
total += volume
average = total / Decimal(period)
self._volumes.append(candle.TotalVolume)
if len(self._volumes) > period:
self._volumes.pop(0)
if not ema_value.IsFormed or not atr_value.IsFormed or average is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
middle = ema_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
multiplier = Decimal(self._multiplier.Value)
upper = middle + multiplier * atr
lower = middle - multiplier * atr
close = candle.ClosePrice
surge = candle.TotalVolume > average
stop_atr = Decimal(self._stop_loss_atr.Value)
if close < lower and surge and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif close > upper and surge and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (close > middle or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (close < middle or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return keltner_volume_strategy()