在 GitHub 上查看

ABE BE CCI 吞没策略

本策略将 MetaTrader 5 专家顾问 Expert_ABE_BE_CCI(目录 MQL/306)移植到 StockSharp。原始 EA 结合了看涨/看跌吞没形态与 Commodity Channel Index (CCI) 指标,并使用固定手数管理。C# 版本保留相同的判定规则,同时利用 StockSharp 的高层订阅与指标绑定功能,使实现更加清晰。

系统仅处理选定周期的已完成 K 线。每根新 K 线都会计算最近 BodyAveragePeriod 根的实体均值、收盘价均值以及周期为 CciPeriod 的 CCI。当满足以下条件时才认定为有效吞没:当前实体超过均值、收盘突破前一根的开盘、被吞没蜡烛的中点位于均值的正确一侧——这些条件与 MQL 中 CCandlePattern 的检查一致。多头需看涨吞没且 CCI 低于 EntryOversoldLevel,空头需看跌吞没且 CCI 高于 EntryOverboughtLevel。退出规则复制 EA 的 40 分“投票”:CCI 穿越 ±ExitLevel 会立即平掉当前持仓。

执行流程

  1. 订阅 CandleType 指定的蜡烛,并同时计算:
    • BodyAveragePeriod 长度的实体平均值;
    • 相同窗口的收盘价均值;
    • 周期为 CciPeriod 的 CCI。
  2. 每当一根新蜡烛收盘:
    • 检查上一根蜡烛颜色相反且被当前实体完全包覆;
    • 验证实体大于均值且收盘越过上一根开盘;
    • 通过上一根蜡烛中点与均线的位置关系判断趋势背景;
    • 使用 CCI 与相应阈值确认动量。
  3. 交易处理:
    • 条件满足且当前无多单时,先平空再按 Volume 开多;
    • 条件满足且当前无空单时,先平多再按 Volume 开空;
    • CCI 穿越 +ExitLevel 或跌破 -ExitLevel 时平多,CCI 自下向上穿越 -ExitLevel 或跌破 +ExitLevel 时平空。

默认参数

参数 默认值 说明
CciPeriod 49 CCI 指标长度。
BodyAveragePeriod 11 计算实体均值与收盘均值的窗口。
EntryOversoldLevel -50 看涨吞没的 CCI 确认阈值。
EntryOverboughtLevel 50 看跌吞没的 CCI 确认阈值。
ExitLevel 80 CCI 触发离场的绝对值。
CandleType 1 小时 订阅的蜡烛类型。

备注

  • 下单数量沿用原策略:Volume 表示基础手数,方向反转时先关闭已有仓位。
  • MQL 中的 TrailingNoneMoneyFixedLot 未单独移植,StockSharp 已提供等价的下单行为。
  • 源码中的注释全部使用英文,缩进为制表符,指标值通过 Bind 获取,无需调用 GetValue,符合仓库要求。
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 CCI strategy: Engulfing pattern with CCI confirmation.
/// Bullish engulfing + negative CCI for long, bearish engulfing + positive CCI for short.
/// </summary>
public class AbeBeCciStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _entryLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevCci;
	private bool _hasPrevCci;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
	public decimal EntryLevel { get => _entryLevel.Value; set => _entryLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public AbeBeCciStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_entryLevel = Param(nameof(EntryLevel), 100m)
			.SetDisplay("Entry Level", "CCI threshold for entry", "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();
		_prevCci = 0m;
		_hasPrevCci = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevCci = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var cci = new CommodityChannelIndex { Length = CciPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

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

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

			var bullishEngulfing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice <= prev.ClosePrice
				&& curr.ClosePrice >= prev.OpenPrice;

			var bearishEngulfing = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice >= prev.ClosePrice
				&& curr.ClosePrice <= prev.OpenPrice;

			if (bullishEngulfing && cciValue < -EntryLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishEngulfing && cciValue > EntryLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		if (_hasPrevCci)
		{
			if (Position > 0 && _prevCci > EntryLevel && cciValue < EntryLevel && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevCci < -EntryLevel && cciValue > -EntryLevel && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevCci = cciValue;
		_hasPrevCci = true;
	}
}