在 GitHub 上查看

蜡烛图 + 随机指标确认策略

本策略将 MetaTrader 专家顾问 Expert_CP_Stoch 移植到 StockSharp 的高层 API 中。它把日本蜡烛图反转形态与随机指标 %D 信号线结合,先识别前三根完成蜡烛中的多头或空头形态,再要求随机指标进入超买/超卖区域后才入场。当出现反向形态,或者 %D 穿越预设的退出阈值时立即平仓。

默认参数与原版一致:%K = 33、%D = 37、平滑值 = 30、超卖/超买阈值 = 30/70、退出阈值 = 20/80。StockSharp 的随机指标基于高/低/收价格,等效于 MQL 中的 STO_LOWHIGH 设定。蜡烛形态识别会使用最近 12 根蜡烛的平均实体长度来过滤信号,确保与原始逻辑一致。

细节

  • 入场条件
    • 做多:检测到以下任一多头形态(如三白兵、刺透形态、晨星十字、看涨吞没、看涨母子、晨星、看涨会面线),且上一根收盘后的 %D 低于超卖阈值(默认 30)。
    • 做空:检测到以下任一空头形态(三只黑乌鸦、乌云盖顶、暮星十字、看跌吞没、看跌母子、暮星、看跌会面线),且上一根收盘后的 %D 高于超买阈值(默认 70)。
  • 离场条件
    • 做多:一旦出现空头形态,或 %D 跌破上限 80 / 下限 20 即刻平仓。
    • 做空:一旦出现多头形态,或 %D 突破下限 20 / 上限 80 即刻平仓。
  • 方向:双向交易,规则对称。
  • 止损:无固定止损/止盈,仅依赖形态与随机指标信号。可在外层框架中启用 StartProtection 之类的保护模块。
  • 默认参数
    • Body Average Period = 12 根蜡烛用于计算平均实体。
    • Stochastic %K = 33,Stochastic %D = 37,Stochastic Smoothing = 30。
    • Oversold Threshold = 30,Overbought Threshold = 70。
    • Lower Exit Level = 20,Upper Exit Level = 80。
  • 过滤信息
    • 类型:蜡烛形态 + 随机指标确认。
    • 方向:多空皆可。
    • 指标:随机指标 + 多种蜡烛形态。
    • 止损:仅靠信号退出,无机械止损/止盈。
    • 复杂度:较高(大量条件及历史统计)。
    • 周期:任意周期,默认 1 小时。
    • 季节性:无。
    • 神经网络:无。
    • 背离:无,依靠随机指标水平确认。
    • 风险等级:中高,因缺少硬性止损。

工作流程

  1. 订阅指定的蜡烛数据,并绑定随机指标(%K、%D 及平滑参数)。
  2. 维护最近三根完成蜡烛以及蜡烛实体/收盘价的滚动平均,复现 MQL 中的形态判断方法。
  3. 每根新蜡烛完成时逐一检测多头与空头形态,计算平均实体、蜡烛中点、跳空等条件。
  4. 读取前两根蜡烛的 %D 值,用于判断是否处于超买/超卖区并监控阈值交叉。
  5. 当形态与指标条件同时满足时,通过高层 BuyMarket/SellMarket 方法开仓或平仓。
  6. 如需风险控制,可在外部策略管理器中额外启用组合保护模块。

使用建议

  • 在启动阶段需要至少 Body Average Period + 3 根历史蜡烛,否则平均实体尚未形成,形态判断会返回 false。
  • 随机指标过滤使用上一根蜡烛的 %D 值,与 MQL 中 StochSignal(1) 的写法完全一致。
  • 蜡烛形态对跳空和数据质量较敏感,低流动性品种可能出现噪音信号。
  • 参数优化时,可优先调整超买/超卖阈值和随机指标周期,保持形态逻辑不变以保证可比性。
  • 若需要 STO_CLOSECLOSE(仅用收盘价)版本,可在后续扩展中替换随机指标实现。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Candlestick + Stochastic strategy.
/// Buys on bullish engulfing with low stochastic, sells on bearish engulfing with high stochastic.
/// </summary>
public class CandlestickStochasticStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<decimal> _stochLow;
	private readonly StrategyParam<decimal> _stochHigh;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private ICandleMessage _prevCandle;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
	public decimal StochLow { get => _stochLow.Value; set => _stochLow.Value = value; }
	public decimal StochHigh { get => _stochHigh.Value; set => _stochHigh.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public CandlestickStochasticStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_stochPeriod = Param(nameof(StochPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Stoch Period", "Stochastic K period", "Indicators");
		_stochLow = Param(nameof(StochLow), 40m)
			.SetDisplay("Stoch Low", "Stochastic oversold level", "Signals");
		_stochHigh = Param(nameof(StochHigh), 60m)
			.SetDisplay("Stoch High", "Stochastic overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCandle = null;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevCandle = null;
		_candlesSinceTrade = SignalCooldownCandles;
		var rsi = new RelativeStrengthIndex { Length = StochPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_prevCandle != null)
		{
			var bullishEngulf = _prevCandle.OpenPrice > _prevCandle.ClosePrice &&
								candle.ClosePrice > candle.OpenPrice &&
								candle.ClosePrice > _prevCandle.OpenPrice &&
								candle.OpenPrice < _prevCandle.ClosePrice;

			var bearishEngulf = _prevCandle.ClosePrice > _prevCandle.OpenPrice &&
								candle.OpenPrice > candle.ClosePrice &&
								candle.OpenPrice > _prevCandle.ClosePrice &&
								candle.ClosePrice < _prevCandle.OpenPrice;

			if (bullishEngulf && stochValue < StochLow && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishEngulf && stochValue > StochHigh && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevCandle = candle;
	}
}