在 GitHub 上查看

FT CCI 策略

该策略是 MetaTrader 5 专家顾问“FT_CCI (barabashkakvn's edition)”的 StockSharp 移植版本。它以商品通道指数(CCI)为核心,在指标远离均值时捕捉反转机会。当 CCI 跌破下轨时转为做多,当 CCI 突破上轨时转为做空。止损和止盈以点(pips)为单位输入,并自动转换为绝对价格偏移。

概览

  • 核心指标:CCI,可配置平滑周期(默认 14)。
  • 交易方向:双向。策略在任意时刻只持有一个净头寸,遇到反向信号时直接反手。
  • 执行方式:在所选时间框架的收盘价处发送市价单。
  • 风险控制:可选的止损与止盈(单位 pips)。当参数为 0 时,关闭对应保护。
  • 默认时间框架:30 分钟 K 线(对应原始脚本中的 Period() 选择)。

工作原理

做多条件

  1. 订阅所选时间框架的已完成 K 线。
  2. 使用典型价格更新 CCI 指标。
  3. 当 CCI 值小于或等于设定的下轨(默认 -210)时:
    • 平掉所有空头仓位。
    • 按配置的交易手数建立或加仓多单。
  4. 多单持有至出现做空信号、止损/止盈触发或手动停止策略。

做空条件

  1. 监控同一指标在已完成 K 线上的表现。
  2. 当 CCI 值大于或等于设定的上轨(默认 +210)时:
    • 平掉所有多头仓位。
    • 按配置的手数建立或加仓空单。
  3. 空单持有至出现做多信号或保护性订单被触发。

仓位管理

  • 止损与止盈以 pips 表示。策略会根据证券的 PriceStep(对于 3 位和 5 位报价乘以 10)计算出单个 pip 的价格距离,再转换为绝对价差并交给 StartProtection
  • 保护只在启动时设置一次,因此每次新开仓都会继承相同的止损/止盈距离。
  • 反手订单的数量为 配置手数 + |当前持仓|,确保一次市价单即可平掉原有仓位并建立反向头寸。

参数

名称 说明
Candle Type 指标计算与信号使用的 K 线类型。
Trade Volume 新开仓的手数(lots)。在反手时同样使用。
CCI Period CCI 指标的平滑周期。
CCI Upper Threshold 触发做空的 CCI 阈值。
CCI Lower Threshold 触发做多的 CCI 阈值。
Stop Loss (pips) 止损距离(pips)。设为 0 时禁用。
Take Profit (pips) 止盈距离(pips)。设为 0 时禁用。

所有参数均支持在 StockSharp 参数管理器中进行优化。

使用建议

  • 推荐用于流动性较高的外汇货币对或指数,30 分钟至 4 小时时间框架的 CCI 极值最为明显。
  • 阈值 ±210 重现原始 FT_CCI 配置。降低阈值可提高灵敏度,抬高阈值可过滤掉较弱的波动。
  • 请确保证券元数据提供了有效的 PriceStep,策略需要该值将 pips 转换为绝对价格。
  • 策略按净持仓模型设计。若账户允许对冲,请合理调整手数,使反手单能够完全抵消旧仓位。

其他说明

  • 只有当 CCI 完全形成后才会处理信号,前几根 K 线会被忽略以累积足够历史数据。
  • 止损与止盈不是必需的;若保持为 0,策略将像原脚本一样,仅在出现反向信号时平仓。
  • 在 StockSharp 中将策略附加到图表时,会自动绘制 K 线、CCI 指标和实际成交,方便可视化分析。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// CCI breakout strategy. Opens long when CCI drops below lower band, shorts when above upper band.
/// </summary>
public class FtCciStrategy : Strategy
{
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _upperThreshold;
	private readonly StrategyParam<decimal> _lowerThreshold;
	private readonly StrategyParam<DataType> _candleType;

	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
	public decimal UpperThreshold { get => _upperThreshold.Value; set => _upperThreshold.Value = value; }
	public decimal LowerThreshold { get => _lowerThreshold.Value; set => _lowerThreshold.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FtCciStrategy()
	{
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "Averaging period for CCI", "Indicator");

		_upperThreshold = Param(nameof(UpperThreshold), 210m)
			.SetDisplay("CCI Upper", "CCI level for short entries", "Indicator");

		_lowerThreshold = Param(nameof(LowerThreshold), -210m)
			.SetDisplay("CCI Lower", "CCI level for long entries", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle Type", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var cci = new CommodityChannelIndex { Length = CciPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, cci);
			DrawOwnTrades(area);
		}
	}

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

		if (cciValue <= LowerThreshold && Position <= 0)
			BuyMarket();
		else if (cciValue >= UpperThreshold && Position >= 0)
			SellMarket();

		// Exit when CCI returns to zero
		if (Position > 0 && cciValue >= 0)
			SellMarket();
		else if (Position < 0 && cciValue <= 0)
			BuyMarket();
	}
}