在 GitHub 上查看
ADX 简单趋势策略
策略概述
ADX 简单趋势策略 来自 MetaTrader 平台上的 “ADX Simple” EA。策略通过平均趋向指标(Average Directional Index,ADX)判断趋势是否增强,并利用正向/负向动向指标(DI+、DI-)确定做多或做空。只有当 ADX 主线抬升时才允许开仓,从而避免在没有趋势的市场中频繁交易。移植到 StockSharp 后,逻辑保持简洁,同时完全遵循平台的高阶 API 规范。
使用的指标
- Average Directional Index (ADX),周期可配置(默认 25)。
- ADX 主线用于确认趋势强度是否增加。
- 内置的 DI+ 和 DI- 序列用来识别多空优势。
- K 线类型 通过
CandleType 参数选择(默认 15 分钟 K 线)。
交易规则
多头进场
- 等待 K 线收盘并得到最终的 ADX 数值。
- 确认 DI+ 高于 DI-。
- 检查当前 ADX 值是否严格大于上一根 K 线的 ADX 值。
- 在当前无仓位的情况下,按
Strategy.Volume 开多。
空头进场
- 等待 K 线收盘并得到最终的 ADX 数值。
- 确认 DI- 高于 DI+。
- 检查 ADX 是否高于上一根 K 线的数值。
- 当无持仓时,按
Strategy.Volume 开空。
离场逻辑
- 平多仓:当 DI- 上穿 DI+,表明空方力量占优。
- 平空仓:当 DI+ 上穿 DI-,表明多方力量占优。
- 退出时不再判断 ADX 斜率,完全符合原始 EA 的实现。
仓位管理
- 任意时刻仅存在一个方向的持仓(或空仓)。
- 订单规模通过
Strategy.Volume 控制(默认值 1),请在启动策略前根据标的设置合适的交易量。
- 策略未内置止损或止盈,建议通过上层框架或自定义扩展实现风险控制。
参数说明
| 参数 |
类型 |
默认值 |
说明 |
AdxPeriod |
int |
25 |
ADX 及 DI 指标的计算周期。 |
CandleType |
DataType |
15 分钟 K 线 |
驱动指标计算的 K 线订阅。 |
与原始 MQL 版本的差异
- 资金管理:原版按账户余额动态调整手数;此处统一使用
Strategy.Volume,将资金管理交给宿主系统。
- 订单监控:StockSharp 直接使用
Position 属性判断持仓,不再遍历订单池。
- 数据处理:策略只在 K 线收盘且指标计算完成后才会做出决策。
- 可视化支持:提供
CreateChartArea、DrawCandles、DrawIndicator 等函数,便于调试和监控。
使用建议
- 选择趋势性较强的市场品种(例如主要外汇对、股指或商品期货)。
- 根据交易节奏调整
CandleType 和 AdxPeriod 参数。
- 在策略容器中配置全局风险控制(回撤限制、最大亏损等)。
- 通过图表观察 DI 交叉和 ADX 斜率,确保策略行为符合预期。
扩展思路
- 引入波动率过滤(ATR、标准差等),过滤震荡行情。
- 在
ProcessCandle 中添加 StartProtection 或自定义止损/止盈逻辑。
- 订阅更高周期的 K 线作为方向过滤器,实现多周期共振。
本文档提供了关于 ADX 简单趋势策略的详尽说明,便于在 StockSharp 生态内部署、监控及二次开发。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ADX Simple: Trend following with EMA crossover and ATR momentum filter.
/// </summary>
public class AdxSimpleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public AdxSimpleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 30)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (fastVal < slowVal && _prevFast >= _prevSlow)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class adx_simple_strategy(Strategy):
def __init__(self):
super(adx_simple_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 30) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(adx_simple_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if fv < sv and self._prev_fast >= self._prev_slow:
self.SellMarket()
self._entry_price = 0.0
elif close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self.BuyMarket()
self._entry_price = 0.0
elif close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(adx_simple_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return adx_simple_strategy()