VWAP ADX Trend Strength 策略
VWAP ADX Trend Strength 策略基于 VWAP with ADX Trend Strength。
测试表明年均收益约为 46%,该策略在股票市场表现最佳。
当 its indicators confirms trend changes 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 AdxPeriod, AdxThreshold 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
AdxPeriod = 14AdxThreshold = 25mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: multiple indicators
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend-following strategy that trades only when ADX confirms strong directional pressure around VWAP.
/// </summary>
public class VwapWithAdxTrendStrengthStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private AverageDirectionalIndex _adx;
private VolumeWeightedMovingAverage _vwap;
private int _cooldown;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Minimum ADX required to trade.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Bars to wait after each order.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public VwapWithAdxTrendStrengthStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetRange(2, 100)
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetRange(1m, 100m)
.SetDisplay("ADX Threshold", "Threshold for strong trend identification", "Signals");
_cooldownBars = Param(nameof(CooldownBars), 72)
.SetRange(1, 500)
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetRange(0.5m, 10m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "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()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adx = null;
_vwap = null;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Security is not specified.");
_adx = new AverageDirectionalIndex { Length = AdxPeriod };
_vwap = new VolumeWeightedMovingAverage();
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _adx);
DrawOwnTrades(area);
}
StartProtection(new Unit(0, UnitTypes.Absolute), new Unit(StopLossPercent, UnitTypes.Percent), false);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
var typedAdx = (AverageDirectionalIndexValue)adxValue;
if (typedAdx.MovingAverage is not decimal adx ||
typedAdx.Dx.Plus is not decimal diPlus ||
typedAdx.Dx.Minus is not decimal diMinus)
return;
var vwap = _vwap.Process(candle).ToDecimal();
if (!_adx.IsFormed || !_vwap.IsFormed)
return;
if (ProcessState != ProcessStates.Started)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var price = candle.ClosePrice;
var isStrongTrend = adx >= AdxThreshold;
var bullishTrend = diPlus > diMinus;
var bearishTrend = diMinus > diPlus;
var aboveVwap = price > vwap;
var belowVwap = price < vwap;
if (Position == 0)
{
if (isStrongTrend && bullishTrend && aboveVwap)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (isStrongTrend && bearishTrend && belowVwap)
{
SellMarket();
_cooldown = CooldownBars;
}
return;
}
if (Position > 0 && (!aboveVwap || adx < AdxThreshold * 0.8m || bearishTrend))
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && (!belowVwap || adx < AdxThreshold * 0.8m || bullishTrend))
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageDirectionalIndex, VolumeWeightedMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class vwap_adx_trend_strength_strategy(Strategy):
"""
Trend-following strategy that trades only when ADX confirms strong directional pressure around VWAP.
"""
def __init__(self):
super(vwap_adx_trend_strength_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetRange(2, 100) \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetRange(1.0, 100.0) \
.SetDisplay("ADX Threshold", "Threshold for strong trend identification", "Signals")
self._cooldown_bars = self.Param("CooldownBars", 72) \
.SetRange(1, 500) \
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetRange(0.5, 10.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vwap_adx_trend_strength_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(vwap_adx_trend_strength_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = int(self._adx_period.Value)
self._vwap = VolumeWeightedMovingAverage()
self._cooldown = 0
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.DrawIndicator(area, adx)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(0, UnitTypes.Absolute),
Unit(self._stop_loss_percent.Value, UnitTypes.Percent),
False
)
def _process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
adx_typed = adx_value
adx_ma = adx_typed.MovingAverage
di_plus = adx_typed.Dx.Plus
di_minus = adx_typed.Dx.Minus
if adx_ma is None or di_plus is None or di_minus is None:
return
adx_val = float(adx_ma)
di_plus_val = float(di_plus)
di_minus_val = float(di_minus)
vwap_result = self._vwap.Process(CandleIndicatorValue(self._vwap, candle))
vwap_val = float(vwap_result)
if not self.IsFormedAndOnlineAndAllowTrading():
return
if not self._vwap.IsFormed:
return
if self._cooldown > 0:
self._cooldown -= 1
return
price = float(candle.ClosePrice)
threshold = float(self._adx_threshold.Value)
is_strong_trend = adx_val >= threshold
bullish_trend = di_plus_val > di_minus_val
bearish_trend = di_minus_val > di_plus_val
above_vwap = price > vwap_val
below_vwap = price < vwap_val
cd = int(self._cooldown_bars.Value)
if self.Position == 0:
if is_strong_trend and bullish_trend and above_vwap:
self.BuyMarket()
self._cooldown = cd
elif is_strong_trend and bearish_trend and below_vwap:
self.SellMarket()
self._cooldown = cd
return
if self.Position > 0 and (not above_vwap or adx_val < threshold * 0.8 or bearish_trend):
self.SellMarket(Math.Abs(self.Position))
self._cooldown = cd
elif self.Position < 0 and (not below_vwap or adx_val < threshold * 0.8 or bullish_trend):
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = cd
def CreateClone(self):
return vwap_adx_trend_strength_strategy()