ADX 出来高乗数戦略
ADX 出来高乗数戦略は、Average Directional Index によるトレンド強度と出来高急増フィルターを組み合わせます。ADX がしきい値を超え、優勢な方向線がトレンド方向を示し、現在の出来高がユーザー定義の係数で乗算した移動平均を上回る場合にのみ取引に参入します。
詳細
- エントリー条件:
- ADX がしきい値を超え DI+ > DI- で出来高が SMA(出来高) * 乗数より大きい → ロング。
- ADX がしきい値を超え DI- > DI+ で出来高が SMA(出来高) * 乗数より大きい → ショート。
- ロング/ショート: 両方。
- エグジット条件:
- 逆シグナルがポジションの反転をトリガーします。
- ストップ: なし。
- デフォルト値:
AdxPeriod= 21AdxThreshold= 26VolumeMultiplier= 1.8VolumePeriod= 20
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: ADX, Volume SMA
- ストップ: いいえ
- 複雑さ: 低
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// ADX Volume Multiplier strategy.
/// When ADX is above AdxThreshold and the candle volume exceeds VolumeMultiplier times its SMA, the strategy goes long if DI+ is above
/// DI- and short if DI- is above DI+; the opposite signal reverses the position.
/// </summary>
public class AdxVolumeMultiplierStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX level a trend must exceed.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Multiple of the average volume the candle volume must exceed.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// Period of the volume SMA.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdxVolumeMultiplierStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "ADX period", "ADX");
_adxThreshold = Param(nameof(AdxThreshold), 26m)
.SetDisplay("ADX Threshold", "ADX level a trend must exceed", "ADX");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.8m)
.SetGreaterThanZero()
.SetDisplay("Volume Multiplier", "Multiple of the average volume the candle volume must exceed", "Volume");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Period of the volume SMA", "Volume");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).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();
_volumeSma = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
_volumeSma = new SimpleMovingAverage { Length = VolumePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, adx);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
var volumeAverage = _volumeSma.Process(candle.TotalVolume, candle.ServerTime, true).ToDecimal();
if (!adxValue.IsFormed || !_volumeSma.IsFormed)
return;
var adxTyped = (IAverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adx || adxTyped.Dx.Plus is not decimal diPlus || adxTyped.Dx.Minus is not decimal diMinus)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var volumeSurge = candle.TotalVolume > volumeAverage * VolumeMultiplier;
var strong = adx > AdxThreshold;
if (strong && volumeSurge && diPlus > diMinus && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (strong && volumeSurge && diMinus > diPlus && 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")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adx_volume_multiplier_strategy(Strategy):
"""
ADX Volume Multiplier strategy.
When ADX is above AdxThreshold and the candle volume exceeds VolumeMultiplier times its SMA, the strategy goes long if DI+ is above
DI- and short if DI- is above DI+; the opposite signal reverses the position.
"""
def __init__(self):
super(adx_volume_multiplier_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 21).SetGreaterThanZero().SetDisplay("ADX Period", "ADX period", "ADX")
self._adx_threshold = self.Param("AdxThreshold", 26.0).SetDisplay("ADX Threshold", "ADX level a trend must exceed", "ADX")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.8).SetGreaterThanZero().SetDisplay("Volume Multiplier", "Multiple of the average volume the candle volume must exceed", "Volume")
self._volume_period = self.Param("VolumePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Period", "Period of the volume SMA", "Volume")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_volume_multiplier_strategy, self).OnReseted()
self._volume_sma = None
def OnStarted2(self, time):
super(adx_volume_multiplier_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
volume_average = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True).GetValue[Decimal](None)
if not adx_value.IsFormed or not self._volume_sma.IsFormed:
return
adx = adx_value.MovingAverage
di_plus = adx_value.Dx.Plus
di_minus = adx_value.Dx.Minus
if adx is None or di_plus is None or di_minus is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
volume_surge = candle.TotalVolume > volume_average * Decimal(self._volume_multiplier.Value)
strong = adx > Decimal(self._adx_threshold.Value)
if strong and volume_surge and di_plus > di_minus and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif strong and volume_surge and di_minus > di_plus and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return adx_volume_multiplier_strategy()