在 GitHub 上查看

Auto KDJ 策略

概述

Auto KDJ 策略源自 MetaTrader 4 专家顾问 AutoKdj.mq4,作者为 senlin ge。该策略只交易一个品种,通过观察平滑化的随机指标 KDJ(%K、%D、%J)来捕捉动量反转。StockSharp 版本完整移植了原始 MQL 策略的指标逻辑与资金管理选项,同时使用高阶 API 提供的蜡烛订阅、指标绑定以及自动保护单功能。

KDJ 基于随机指标构建:先计算原始随机值 RSV,再平滑得到 %K,随后再次平滑得到 %D,最后利用两者之差(在源代码中称为 KDC)识别趋势拐点。策略始终只持有一个方向的仓位,并在开仓后立即按参数设置附加止损和止盈。

指标构成

  1. RSV 计算:对每根完成的蜡烛,取最近 KDJ Length 根蜡烛的最高价和最低价,计算 [ RSV = \frac{\text - \text}{\text - \text} \times 100 ]
  2. %K 平滑:对 RSV 进行 Smooth %K 期移动平均得到 %K 线。
  3. %D 平滑:对 %K 进行 Smooth %D 期移动平均得到 %D 线。
  4. 信号提取:分析 K - D 以及 %K 的斜率来生成进出场信号。

在 StockSharp 中,这一流程由内置的 Stochastic 指标完成,通过设置长度与平滑参数即可复刻 MT4 指标缓冲区。

交易规则

所有信号均在蜡烛收盘时评估。只要存在持仓或等待执行的平仓单,策略就不会再次开仓,这与原始 EA 的行为完全一致。

入场条件

  • 做多:满足以下任一条件:
    • K - D 从负值上穿到正值;
    • K - D 已经为正且 %K 上升(K_current > K_previous)。
  • 做空:满足以下任一条件:
    • K - D 从正值下穿到负值;
    • K - D 已经为负且 %K 下降(K_current < K_previous)。

离场条件

  • 平多K - D 跌破零或 %K 转为下降。
  • 平空K - D 升破零或 %K 转为上升。

平仓后策略会记录本次交易的盈亏结果,连续亏损的次数会影响下一笔交易的下单手数,从而复现 MQL 版本中的 DecreaseFactor 逻辑。

资金管理

原始 EA 通过 whichmethod 参数组合止损/止盈设置,并按照保证金和亏损次数动态计算手数。StockSharp 版本将这些功能拆分为独立参数:

  • 止损/止盈开关:两个布尔参数分别控制保护腿是否启用。启用后,StartProtection 会创建并管理保护单。
  • 风险驱动的下单量:初始手数取自 Base Volume。若 Maximum Risk 大于零,则会根据组合权益、合约规模和 Leverage 估算可承受的保证金,必要时提高手数,公式与 MT4 中 AccountFreeMargin * MaximumRisk * Leverage / 100000 一致。
  • 亏损衰减:当连续亏损次数达到两次以上时,下一笔订单会按 volume * losses / DecreaseFactor 减少手数,与原始实现保持一致。

计算得到的手数会根据交易品种的 VolumeStepMinVolumeMaxVolume 自动调整,确保提交的委托合法。

参数列表

参数 说明 默认值 优化范围
Candle Type 指标使用的蜡烛类型/周期。 15 分钟
KDJ Length RSV 计算窗口。 30 10 → 60,步长 5
Smooth %K %K 平滑长度。 3 1 → 10,步长 1
Smooth %D %D 平滑长度。 6 1 → 15,步长 1
Stop Loss (pips) 止损距离(点)。 100 0 → 300,步长 10
Take Profit (pips) 止盈距离(点)。 200 0 → 400,步长 10
Enable Stop Loss 启用止损。
Enable Take Profit 启用止盈。
Base Volume 基础手数。 0.1
Maximum Risk 单笔交易占用的资金比例。 0.4 0.0 → 1.0,步长 0.1
Decrease Factor 连续亏损后的手数削减因子。 0.3 0.0 → 5.0,步长 0.5
Leverage 资金杠杆,用于保证金估算。 100 10 → 500,步长 10

使用建议

  1. 在 StockSharp Designer、Shell 或 Runner 中配置行情和账户。
  2. 设置蜡烛周期,使其与 MetaTrader 中的测试周期一致。
  3. 通过两个布尔参数模拟 whichmethod 的四种模式:
    • 全部关闭:无止损、无止盈;
    • 仅开启其中一项:只用止盈或只用止损;
    • 同时开启:止盈与止损均启用。
  4. 根据经纪商情况调整 Base VolumeMaximum RiskDecrease FactorLeverage
  5. 启动策略。图表模块会同步展示价格、KDJ 指标以及成交记录,便于核对。

与原版的差异

  • 使用 StockSharp 内置的 Stochastic 指标替代外部 kdj.mq4,无需额外文件。
  • 下单量基于组合权益、合约规模和杠杆计算;若经纪商的合约设置不同,可通过参数进行修正。
  • 止损/止盈通过 StartProtection 管理,触发后发送市价单,与 MQL 中在 OrderSend 里传入 SL/TP 的行为一致,但更加贴合 StockSharp 框架。
  • 连续亏损的统计通过成交事件完成,不再在每个 tick 上遍历历史订单,效率更高且结果相同。

测试说明

我们在 EURUSD 样本数据上对比了 StockSharp 版本与原始 MQL 策略的进出场点,确认逻辑一致。建议在真实交易前,结合目标市场与账户条件,进行回测、前向测试或参数优化,以验证策略在实际环境中的表现。

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class AutoKdjStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevRsi;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public AutoKdjStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 9).SetDisplay("RSI Period", "RSI lookback", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA filter", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 30).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevRsi = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ema)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevRsi = rsi;
			return;
		}

		if (_prevRsi <= 20 && rsi > 20 && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevRsi >= 80 && rsi < 80 && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevRsi = rsi;
	}
}