在 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 线)。

交易规则

多头进场

  1. 等待 K 线收盘并得到最终的 ADX 数值。
  2. 确认 DI+ 高于 DI-。
  3. 检查当前 ADX 值是否严格大于上一根 K 线的 ADX 值。
  4. 在当前无仓位的情况下,按 Strategy.Volume 开多。

空头进场

  1. 等待 K 线收盘并得到最终的 ADX 数值。
  2. 确认 DI- 高于 DI+。
  3. 检查 ADX 是否高于上一根 K 线的数值。
  4. 当无持仓时,按 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 线收盘且指标计算完成后才会做出决策。
  • 可视化支持:提供 CreateChartAreaDrawCandlesDrawIndicator 等函数,便于调试和监控。

使用建议

  1. 选择趋势性较强的市场品种(例如主要外汇对、股指或商品期货)。
  2. 根据交易节奏调整 CandleTypeAdxPeriod 参数。
  3. 在策略容器中配置全局风险控制(回撤限制、最大亏损等)。
  4. 通过图表观察 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;
	}
}