在 GitHub 上查看

ABE BE 随机指标吞没策略

该策略把 MetaTrader 顾问 Expert_ABE_BE_Stoch 迁移到 StockSharp 的高级 API。它结合日本蜡烛图与随机指标动量,用于捕捉超买/超卖区域附近的反转。当检测到被随机指标 %D 线强力确认的看涨吞没或看跌吞没形态时触发开仓;仓位建立后,再利用随机指标越过 20 与 80 的阈值来管理离场,完整复刻原始 MQL 专家的“投票”机制。

策略支持做多与做空,并且只在蜡烛收盘后计算信号,从而避免盘中噪声。仓位规模由基础策略的 Volume 控制,可选的止损/止盈参数会把以点数表示的距离转换为 UnitTypes.Price 类型的 Unit 对象,交给 StartProtection 执行。

工作流程

  1. 订阅数据 – 按照设定的蜡烛类型创建订阅,并初始化带有 %K%D 和减速参数的 StochasticOscillator
  2. 识别形态 – 每当蜡烛收盘,判断当前蜡烛的实体是否完全吞没上一根蜡烛的实体。两个辅助方法重现了 MetaTrader 中对看涨/看跌吞没的判定方式。
  3. 动量确认 – 随机指标 %D 作为过滤器:如果 %D 低于超卖阈值(默认 30),并出现看涨吞没,则允许做多;若 %D 高于超买阈值(默认 70),并出现看跌吞没,则允许做空。
  4. 仓位管理 – 缓存上一根蜡烛的 %D 值。若当前 %D 向上穿越 20 或 80,则平掉所有空单;若向下穿越 80 或 20,则平掉所有多单。这与原程序中额外的“平仓票数”完全一致。
  5. 风险控制 – 当 StopLossPointsTakeProfitPoints 大于零时,将距离(以最小报价步长为单位)转换成绝对价格,传给 StartProtection(takeProfit, stopLoss);否则调用 StartProtection() 启用默认保护。

交易规则

  • 做多入场:上一根蜡烛收跌、当前蜡烛收涨,并且当前蜡烛的实体完全覆盖前一根实体,同时 %D 低于 EntryOversoldLevel(默认 30)。如果存在空单则先平仓,然后通过 BuyMarket 建立或翻多。
  • 做空入场:上一根蜡烛收涨、当前蜡烛收跌,且当前实体吞没上一根实体,同时 %D 高于 EntryOverboughtLevel(默认 70)。如果存在多单则先平仓,然后通过 SellMarket 建立或翻空。
  • 多单离场:持有多单时,只要 %D 向下穿越 ExitUpperLevel(默认 80)或 ExitLowerLevel(默认 20),立即用 SellMarket 全部平仓。
  • 空单离场:持有空单时,%D 向上穿越 ExitLowerLevelExitUpperLevel 时,通过 BuyMarket 平仓。
  • 止损/止盈StopLossPointsTakeProfitPoints 以报价步长为单位,0 表示不开启相应的保护。

参数

名称 类型 默认值 说明
CandleType DataType TimeSpan.FromHours(1).TimeFrame() 用于检测形态的蜡烛数据源。
StochasticPeriodK int 47 随机指标 %K 的回溯周期。
StochasticPeriodD int 9 %D 信号线的平滑周期。
StochasticPeriodSlow int 13 %K 施加的额外平滑(减速因子)。
EntryOversoldLevel decimal 30 允许做多信号的 %D 上限。
EntryOverboughtLevel decimal 70 允许做空信号的 %D 下限。
ExitLowerLevel decimal 20 %D 上穿时平空、下穿时平多的下限阈值。
ExitUpperLevel decimal 80 %D 上穿或下穿时触发平仓的上限阈值。
TakeProfitPoints decimal 0 以报价步长表示的止盈距离(0 关闭止盈)。
StopLossPoints decimal 0 以报价步长表示的止损距离(0 关闭止损)。

备注

  • 默认使用 1 小时蜡烛,但策略适用于任何提供 OHLC 数据的品种。
  • 所有计算都基于收盘蜡烛,与原版 MQL 专家的节奏完全一致。
  • 仓位规模建议通过 Volume 或更高层的资金管理模块进行配置。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// ABE BE Stoch strategy: Engulfing pattern with Stochastic confirmation.
/// Bullish engulfing + oversold stochastic for long, bearish engulfing + overbought for short.
/// </summary>
public class AbeBeStochStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevK;
	private bool _hasPrevK;
	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 Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public AbeBeStochStrategy()
	{
		_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");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "Stochastic oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "Stochastic overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
		_prevK = 0m;
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(stoch, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var stochTyped = stochValue as StochasticOscillatorValue;
		if (stochTyped?.K is not decimal kValue) return;

		_candles.Add(candle);
		if (_candles.Count > 5)
			_candles.RemoveAt(0);

		if (_candles.Count >= 2)
		{
			var curr = _candles[^1];
			var prev = _candles[^2];

			// Bullish engulfing: prev bearish, curr bullish, curr body engulfs prev body
			var bullishEngulfing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice <= prev.ClosePrice
				&& curr.ClosePrice >= prev.OpenPrice;

			// Bearish engulfing: prev bullish, curr bearish, curr body engulfs prev body
			var bearishEngulfing = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice >= prev.ClosePrice
				&& curr.ClosePrice <= prev.OpenPrice;

			if (bullishEngulfing && kValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishEngulfing && kValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		// Exit on stochastic cross
		if (_hasPrevK)
		{
			if (Position > 0 && _prevK >= Overbought && kValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevK <= Oversold && kValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevK = kValue;
		_hasPrevK = true;
	}
}