在 GitHub 上查看

晨星/暮星 CCI 策略

概述

该策略基于 MetaTrader 5 的 Expert_AMS_ES_CCI 智能交易系统,在 StockSharp 高级 API 上复刻其逻辑。策略通过识别“晨星”和“暮星”三根蜡烛的反转形态,并结合商品通道指数(CCI)的确认信号,在主图表的已完成K线上做出交易决策。

交易逻辑

  • 晨星形态做多
    • 连续三根K线满足晨星特征:
      • 第一根:实体明显向下(实体长度大于平均实体长度)。
      • 第二根:实体很短,并且整体低于第一根K线。
      • 第三根:收盘价高于第一根K线实体的中点。
    • 信号柱的 CCI 数值低于负的入场阈值(默认 −50)。
  • 暮星形态做空
    • 连续三根K线满足暮星特征:
      • 第一根:实体明显向上。
      • 第二根:实体很短,并且整体高于第一根K线。
      • 第三根:收盘价低于第一根K线实体的中点。
    • 信号柱的 CCI 数值高于正的入场阈值(默认 +50)。
  • 离场条件
    • 空头持仓:当 CCI 向上穿越 −NeutralThreshold 或向下穿越 +NeutralThreshold(默认 ±80)时平仓。
    • 多头持仓:当 CCI 向下穿越 +NeutralThreshold 或跌破 −NeutralThreshold 时平仓。
    • 策略未内置止损/止盈,可结合外部风控模块使用。

指标

  • Commodity Channel Index (CCI):确认趋势强度,默认周期 25。
  • 蜡烛实体长度的简单移动平均:计算最近 BodyAveragePeriod 根K线的平均实体长度(默认 5),用于验证形态强度。

参数

名称 说明 默认值 备注
CciPeriod CCI 指标计算周期。 25 可参与优化。
BodyAveragePeriod 计算平均实体长度的窗口。 5 可参与优化。
EntryThreshold 入场所需的 CCI 绝对值。 50 取正值,策略会比较 ±EntryThreshold。
NeutralThreshold 确认离场的 CCI 绝对阈值。 80 取正值,策略会比较 ±NeutralThreshold。
CandleType 参与计算的K线类型或周期。 1 小时时间框架 可根据需求调整。

其他说明

  • 策略通过 SubscribeCandles 订阅K线,并利用 Bind 同步获取指标数值。
  • 交易指令使用 BuyMarketSellMarket 市价单完成。
  • 代码中的注释全部为英文,符合项目要求。
  • 如需额外风控,可结合 StartProtection 或自定义资金管理逻辑。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Morning/Evening Star + CCI strategy.
/// Buys on morning star with negative CCI, sells on evening star with positive CCI.
/// </summary>
public class MorningEveningStarCciStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciLevel;

	private ICandleMessage _prevCandle;
	private ICandleMessage _prevPrevCandle;

	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 MorningEveningStarCciStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_cciLevel = Param(nameof(CciLevel), 0m)
			.SetDisplay("CCI Level", "CCI threshold for confirmation", "Signals");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevCandle = null;
		_prevPrevCandle = null;
		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 (_prevCandle != null && _prevPrevCandle != null)
		{
			var prevBody = Math.Abs(_prevCandle.ClosePrice - _prevCandle.OpenPrice);
			var prevRange = _prevCandle.HighPrice - _prevCandle.LowPrice;
			var isSmallBody = prevRange > 0 && prevBody < prevRange * 0.3m;

			var firstBearish = _prevPrevCandle.OpenPrice > _prevPrevCandle.ClosePrice;
			var currBullish = candle.ClosePrice > candle.OpenPrice;
			var isMorningStar = firstBearish && isSmallBody && currBullish &&
							   candle.ClosePrice > _prevPrevCandle.OpenPrice * 0.5m + _prevPrevCandle.ClosePrice * 0.5m;

			var firstBullish = _prevPrevCandle.ClosePrice > _prevPrevCandle.OpenPrice;
			var currBearish = candle.OpenPrice > candle.ClosePrice;
			var isEveningStar = firstBullish && isSmallBody && currBearish &&
							   candle.ClosePrice < _prevPrevCandle.OpenPrice * 0.5m + _prevPrevCandle.ClosePrice * 0.5m;

			if (isMorningStar && cciValue < -CciLevel && Position <= 0)
				BuyMarket();
			else if (isEveningStar && cciValue > CciLevel && Position >= 0)
				SellMarket();
		}

		_prevPrevCandle = _prevCandle;
		_prevCandle = candle;
	}
}