在 GitHub 上查看
FT CCI 策略
该策略是 MetaTrader 5 专家顾问“FT_CCI (barabashkakvn's edition)”的 StockSharp 移植版本。它以商品通道指数(CCI)为核心,在指标远离均值时捕捉反转机会。当 CCI 跌破下轨时转为做多,当 CCI 突破上轨时转为做空。止损和止盈以点(pips)为单位输入,并自动转换为绝对价格偏移。
概览
- 核心指标:CCI,可配置平滑周期(默认 14)。
- 交易方向:双向。策略在任意时刻只持有一个净头寸,遇到反向信号时直接反手。
- 执行方式:在所选时间框架的收盘价处发送市价单。
- 风险控制:可选的止损与止盈(单位 pips)。当参数为 0 时,关闭对应保护。
- 默认时间框架:30 分钟 K 线(对应原始脚本中的
Period() 选择)。
工作原理
做多条件
- 订阅所选时间框架的已完成 K 线。
- 使用典型价格更新 CCI 指标。
- 当 CCI 值小于或等于设定的下轨(默认 -210)时:
- 平掉所有空头仓位。
- 按配置的交易手数建立或加仓多单。
- 多单持有至出现做空信号、止损/止盈触发或手动停止策略。
做空条件
- 监控同一指标在已完成 K 线上的表现。
- 当 CCI 值大于或等于设定的上轨(默认 +210)时:
- 空单持有至出现做多信号或保护性订单被触发。
仓位管理
- 止损与止盈以 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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class ft_cci_strategy(Strategy):
def __init__(self):
super(ft_cci_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 14) \
.SetDisplay("CCI Period", "Averaging period for CCI", "Indicator")
self._upper_threshold = self.Param("UpperThreshold", 210.0) \
.SetDisplay("CCI Upper", "CCI level for short entries", "Indicator")
self._lower_threshold = self.Param("LowerThreshold", -210.0) \
.SetDisplay("CCI Lower", "CCI level for long entries", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle Type", "General")
@property
def cci_period(self):
return self._cci_period.Value
@property
def upper_threshold(self):
return self._upper_threshold.Value
@property
def lower_threshold(self):
return self._lower_threshold.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(ft_cci_strategy, self).OnStarted2(time)
cci = CommodityChannelIndex()
cci.Length = self.cci_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(cci, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
def on_process(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if cci_value <= self.lower_threshold and self.Position <= 0:
self.BuyMarket()
elif cci_value >= self.upper_threshold and self.Position >= 0:
self.SellMarket()
# Exit when CCI returns to zero
if self.Position > 0 and cci_value >= 0:
self.SellMarket()
elif self.Position < 0 and cci_value <= 0:
self.BuyMarket()
def CreateClone(self):
return ft_cci_strategy()