在 GitHub 上查看

MA Crossover ADX 策略

概述

MA Crossover ADX 策略源自 MetaTrader 专家顾问 MA_Crossover_ADX,在 StockSharp 平台上复刻其逻辑。策略通过 EMA 斜率与 ADX 趋势强度的组合,过滤掉震荡行情,只在确认的趋势阶段建仓。所有计算基于可配置的时间框架蜡烛,并在同一根蜡烛完成后同步处理 EMA 与 ADX,以避免信号错位。策略还会按照点数参数自动附加止损与止盈保护。

指标与数据

  • 指数移动平均线 (EMA): 监控最近三根 EMA 数值,分别对应原策略中的 StateEMA(0)StateEMA(1) 条件,用于判定趋势斜率是否持续。
  • 平均趋向指数 (ADX): 同时提供主线与正/负方向指标 (DI+/DI-),DI 差值重现原策略 StateADX(0) 的多空判断,而主线则限定最小趋势强度。
  • 收盘价序列: 使用前一根蜡烛的收盘价与前一根 EMA 比较,确认价格已脱离均线后再入场,防止均值附近的噪声交易。

全部指标共享同一订阅,只有在 EMA 与 ADX 在相同的蜡烛上完成更新后才会触发信号。

交易规则

做多条件

  1. 当前 EMA 斜率 (EMA[0] - EMA[1]) 为正值。
  2. 前一段 EMA 斜率 (EMA[1] - EMA[2]) 也为正,表示动能持续。
  3. 前一根收盘价高于前一根 EMA。
  4. ADX 主线高于阈值,确认趋势强度。
  5. DI+ 大于 DI-,说明多头方向占优。

满足以上条件且当前无仓位时,策略按设定手数市价买入;若已持有空单,将在出现上述信号时立即平仓。

做空条件

  1. 当前 EMA 斜率为负值。
  2. 前一段 EMA 斜率同样为负。
  3. 前一根收盘价低于前一根 EMA。
  4. ADX 主线高于阈值。
  5. DI- 大于 DI+,表明空头主导。

当全部条件成立且策略为空仓时,将发送市价卖出指令;若已有多单则先行平仓。

离场与风控

  • 多单退出: 一旦满足做空条件即平掉所有多头仓位。
  • 空单退出: 满足做多条件时平仓。
  • 保护单: 通过 StartProtection 根据 PriceStep 将点数参数转换为实际价格距离,自动附加止损与止盈委托,复现原 EA 中的 StopLossTakeProfit 设置。

参数说明

参数 默认值 含义
AdxPeriod 33 ADX 计算周期。
AdxThreshold 22 允许入场的最小 ADX 主线值。
EmaPeriod 39 EMA 计算周期。
StopLossPoints 400 止损距离,单位为点 (乘以 PriceStep)。
TakeProfitPoints 900 止盈距离,单位为点。
TradeVolume 0.1 每次新开仓的下单数量。
CandleType 1 小时蜡烛 指标使用的时间框架。

使用提示

  • 目标品种必须提供 PriceStep,否则策略会退化为使用 1 点作为默认步长来计算保护单。
  • 所有参数均支持优化 (SetCanOptimize(true)),便于回测不同组合的表现。
  • 代码内所有注释均按照项目要求使用英文撰写。
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;

public class MaCrossoverAdxStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public MaCrossoverAdxStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}