在 GitHub 上查看

锤子线与上吊线 CCI 确认策略

该策略在 StockSharp 中复刻 MetaTrader 的“AH HM CCI”专家顾问。它同时监控锤子线(Hammer)和上吊线(Hanging Man)形态,并 使用顺势而为的商品通道指数(CCI)作为入场确认。只有当蜡烛形态与 CCI 指示的动量变化一致时才会开仓,从而过滤掉噪声信号。

策略仅在完成的 K 线基础上运行,使用短周期简单移动平均线(SMA)来判断当前趋势。上一根 K 线若在下行趋势中形成锤子线,且 CCI 低于阈值,则开多;若在上行趋势中形成上吊线且 CCI 高于阈值,则开空。平仓逻辑同样遵循原始专家,通过 CCI 穿越配置好的 两个水平来管理仓位。

交易逻辑

  1. 趋势过滤:上一根 K 线的中点必须位于 SMA(收盘价)之下(做多)或之上(做空)。
  2. 形态识别:检查上一根 K 线,要求:
    • K 线实体完全位于整根蜡烛的上三分之一;
    • 与再前一根 K 线的开盘价/收盘价之间存在缺口;
    • 形态所处位置符合趋势背景(锤子线出现在下跌末端,上吊线出现在上涨末端)。
  3. CCI 确认:上一根 K 线的 CCI 必须低于多头阈值或高于空头阈值,默认值分别为 40 和 60,与 MetaTrader 模板一致。
  4. 离场规则:当 CCI 向上穿越或向下跌破两个退出阈值时,平掉当前仓位。由下向上穿越关闭多单,由上向下跌破关闭空单。

参数

参数 说明 默认值
CandleType 用于识别形态的蜡烛类型与周期。 TimeSpan.FromMinutes(15)
CciPeriod 计算 CCI 所用的周期数。 11
MaPeriod 趋势过滤 SMA 的周期。 5
LongConfirmationThreshold 允许锤子线入场的 CCI 上限。 40
ShortConfirmationThreshold 允许上吊线入场的 CCI 下限。 60
ExitUpperThreshold CCI 自下向上穿越时触发的退出水平。 70
ExitLowerThreshold 用于提前离场的辅助 CCI 水平。 30

所有参数均可用于优化,阈值允许设置为负值,便于根据不同市场或波动性进行微调。

下单管理

  • 开仓:采用 Volume + |Position| 的市价单,保证反向开仓时只需一笔交易即可完成。
  • 平仓:完全依赖 CCI 的穿越来还原原始专家的处理方式。若需要固定止损/止盈,可额外调用 StartProtection

使用建议

  • 适用于具有明显影线和缺口的高流动性品种。
  • 在更长周期上,可增大 CciPeriodMaPeriod 以降低噪声。
  • 调低 LongConfirmationThreshold 或调高 ShortConfirmationThreshold 可以减少交易次数、提高信号质量。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Hammer/Hanging Man + CCI strategy.
/// Buys on hammer with negative CCI, sells on hanging man with positive CCI.
/// </summary>
public class HammerHangingManCciStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;
	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 CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public HammerHangingManCciStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_cciLevel = Param(nameof(CciLevel), 100m)
			.SetDisplay("CCI Level", "CCI threshold", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_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++;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var range = candle.HighPrice - candle.LowPrice;
		if (range <= 0 || body <= 0) return;

		var upperShadow = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
		var lowerShadow = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;

		var isHammer = lowerShadow > body * 2.5m && upperShadow < body * 0.5m;
		var isHangingMan = upperShadow > body * 2.5m && lowerShadow < body * 0.5m;

		if (isHammer && cciValue < -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (isHangingMan && cciValue > CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
	}
}