在 GitHub 上查看
Auto KDJ 策略
概述
Auto KDJ 策略源自 MetaTrader 4 专家顾问 AutoKdj.mq4,作者为 senlin ge。该策略只交易一个品种,通过观察平滑化的随机指标 KDJ(%K、%D、%J)来捕捉动量反转。StockSharp 版本完整移植了原始 MQL 策略的指标逻辑与资金管理选项,同时使用高阶 API 提供的蜡烛订阅、指标绑定以及自动保护单功能。
KDJ 基于随机指标构建:先计算原始随机值 RSV,再平滑得到 %K,随后再次平滑得到 %D,最后利用两者之差(在源代码中称为 KDC)识别趋势拐点。策略始终只持有一个方向的仓位,并在开仓后立即按参数设置附加止损和止盈。
指标构成
- RSV 计算:对每根完成的蜡烛,取最近
KDJ Length 根蜡烛的最高价和最低价,计算
[
RSV = \frac{\text - \text}{\text - \text} \times 100
]
- %K 平滑:对 RSV 进行
Smooth %K 期移动平均得到 %K 线。
- %D 平滑:对 %K 进行
Smooth %D 期移动平均得到 %D 线。
- 信号提取:分析
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 减少手数,与原始实现保持一致。
计算得到的手数会根据交易品种的 VolumeStep、MinVolume、MaxVolume 自动调整,确保提交的委托合法。
参数列表
| 参数 |
说明 |
默认值 |
优化范围 |
| 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 |
使用建议
- 在 StockSharp Designer、Shell 或 Runner 中配置行情和账户。
- 设置蜡烛周期,使其与 MetaTrader 中的测试周期一致。
- 通过两个布尔参数模拟
whichmethod 的四种模式:
- 全部关闭:无止损、无止盈;
- 仅开启其中一项:只用止盈或只用止损;
- 同时开启:止盈与止损均启用。
- 根据经纪商情况调整
Base Volume、Maximum Risk、Decrease Factor 和 Leverage。
- 启动策略。图表模块会同步展示价格、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;
}
}
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.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class auto_kdj_strategy(Strategy):
def __init__(self):
super(auto_kdj_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 9) \
.SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA filter", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 30) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_rsi = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(auto_kdj_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(auto_kdj_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._has_prev = False
self._cooldown_remaining = 0
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, ema, self.process_candle).Start()
def process_candle(self, candle, rsi, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi)
ema_val = float(ema)
if not self._has_prev:
self._prev_rsi = rsi_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_rsi = rsi_val
return
if self._prev_rsi <= 20 and rsi_val > 20 and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_rsi >= 80 and rsi_val < 80 and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_rsi = rsi_val
def CreateClone(self):
return auto_kdj_strategy()