在 GitHub 上查看

Auto KD Crossover Strategy

概述

Auto KD Crossover 策略复现了 MQL5 示例 autoKD_EA
策略使用 StochasticOscillator 指标,根据 %K 与 %D 线的交叉生成交易信号。

基础计算使用 RSV 公式:
RSV = (收盘价 - N 期最低价) / (N 期最高价 - N 期最低价) * 100
其中最高价与最低价在 KDPeriod 根K线内求得。%K 为 RSV 的移动平均,周期为 KPeriod;%D 为 %K 的移动平均,周期为 DPeriod

参数

名称 说明 默认值
KDPeriod 计算 RSV 的基础周期数。 30
KPeriod %K 线的平滑周期。 3
DPeriod %D 线的平滑周期。 6
CandleType 使用的K线类型与周期。 5 分钟
Volume 继承自 Strategy 的下单数量。 Strategy.Volume

所有参数均可用于优化。

交易逻辑

  1. 订阅指定的K线并计算随机指标。
  2. 当上一根K线的 %K 低于 %D 而当前 %K 向上穿越 %D 时,开多。
  3. 当上一根K线的 %K 高于 %D 而当前 %K 向下穿越 %D 时,开空。
  4. 策略一次仅持有一个方向的仓位。反向交叉将平掉当前仓位并开立相反仓位。
  5. StartProtection() 调用启用 StockSharp 提供的默认风控机制。

可视化

策略自动在图表上绘制K线、随机指标及成交点位。

备注

  • 可用于任意品种和时间框架。
  • 请根据市场波动性调整参数。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on %K and %D crossover from the Stochastic oscillator.
/// </summary>
public class AutoKdStrategy : Strategy
{
	private readonly StrategyParam<int> _kdPeriod;
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevK;
	private decimal? _prevD;

	/// <summary>
	/// Lookback period for RSV calculation.
	/// </summary>
	public int KdPeriod
	{
		get => _kdPeriod.Value;
		set => _kdPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing period for %K.
	/// </summary>
	public int KPeriod
	{
		get => _kPeriod.Value;
		set => _kPeriod.Value = value;
	}

	/// <summary>
	/// Smoothing period for %D.
	/// </summary>
	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	/// <summary>
	/// Type of candles used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="AutoKdStrategy"/>.
	/// </summary>
	public AutoKdStrategy()
	{
		_kdPeriod = Param(nameof(KdPeriod), 30)
		.SetGreaterThanZero()
		.SetDisplay("KD Period", "Base period for RSV", "Parameters")
		
		.SetOptimize(10, 60, 5);

		_kPeriod = Param(nameof(KPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("K Period", "%K smoothing", "Parameters")
		
		.SetOptimize(1, 10, 1);

		_dPeriod = Param(nameof(DPeriod), 6)
		.SetGreaterThanZero()
		.SetDisplay("D Period", "%D smoothing", "Parameters")
		
		.SetOptimize(1, 10, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for analysis", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevK = null;
		_prevD = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var stochastic = new StochasticOscillator();
		stochastic.K.Length = KdPeriod;
		stochastic.D.Length = DPeriod;

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(stochastic, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
		DrawCandles(area, subscription);
		DrawIndicator(area, stochastic);
		DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		var stoch = (StochasticOscillatorValue)stochValue;
		if (stoch.K is not decimal k || stoch.D is not decimal d)
			return;

		if (_prevK is decimal prevK && _prevD is decimal prevD)
		{
			if (prevK < prevD && k > d && Math.Min(prevK, k) < 30m && Position <= 0)
				BuyMarket(Volume + Math.Abs(Position));
			else if (prevK > prevD && k < d && Math.Max(prevK, k) > 70m && Position >= 0)
				SellMarket(Volume + Math.Abs(Position));
		}

		_prevK = k;
		_prevD = d;
	}
}