在 GitHub 上查看
iCHO Trend CCIDualOnMA Filter 策略
该策略是 MetaTrader 专家顾问 “iCHO Trend CCIDualOnMA Filter” 的 StockSharp 高级 API 版本。它利用 Chaikin 振荡器的零轴过滤来识别趋势,再配合两条建立在平滑价格上的商品通道指数(CCI)确认信号,只有当动能与趋势一致时才允许入场。
交易逻辑
- Chaikin 核心:对累积/派发线应用两条可配置的移动平均线,它们的差值即为 Chaikin 振荡器。上穿/下穿零轴表示主导资金流方向发生改变。
- 双 CCI 过滤:两条 CCI 使用同一条经移动平均平滑后的价格序列,但长度不同。做多必须满足:快速 CCI 自负值区域反弹并上穿慢速 CCI,同时 Chaikin 振荡器保持在零轴之上;做空条件与之相反。
- 信号反转:保留原始 EA 的
Reverse 参数,可将多空逻辑互换,用于测试逆势模式。
- 仓位管理:可选设置在开仓前平掉反向仓位,并限制同一时间仅持有一笔仓位。策略还遵循“每根 K 线最多一笔交易”的规则,以贴合 MetaTrader 行为。
- 交易时段:可限定策略只在指定的日内时间段内交易,支持跨越午夜的时间窗口。
参数
| 参数 |
说明 |
FastChaikinLength |
Chaikin 振荡器中快速均线的周期。 |
SlowChaikinLength |
Chaikin 振荡器中慢速均线的周期。 |
ChaikinMethod |
应用于累积/派发线的均线类型(Simple、Exponential、Smoothed、LinearWeighted)。 |
FastCciLength |
快速 CCI 的周期。 |
SlowCciLength |
慢速 CCI 的周期。 |
MaLength |
为 CCI 提供输入的预处理均线长度。 |
MaMethod |
价格预处理所用的均线类型。 |
MaPrice |
参与平滑的价格类型(收盘价、开盘价、最高价、最低价、中位价、典型价、加权价)。 |
UseClosedBar |
仅在蜡烛收盘后处理信号(默认启用,等同于 EA 的 SignalsBarCurrent=bar_1)。 |
ReverseSignals |
交换多空信号方向。 |
CloseOpposite |
在开新仓之前关闭相反方向的仓位。 |
OnlyOnePosition |
限制同时只持有一笔仓位。 |
TradeMode |
允许的交易方向(仅做多、仅做空、双向)。 |
UseTimeFilter |
启用交易时段过滤。 |
StartHour, StartMinute, EndHour, EndMinute |
交易时段的起止时间(起点包含,终点不包含),使用交易所时区,可跨日。 |
CandleType |
计算时使用的蜡烛类型/周期。 |
说明
- 策略完全基于高阶
SubscribeCandles 订阅和内置指标,无需复制缓冲区或访问历史数据。
- 价格预处理流程与 MetaTrader 指标
CCIDualOnMA 保持一致,即先对所选价格类型进行移动平均再送入 CCI。
- 默认参数对应原始 EA:Chaikin 3/10 EMA、CCI 14 与 50、预处理 SMA(12),以及 10:01–15:02 的交易时段。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Combines a Chaikin oscillator zero-line filter with CCI confirmation.
/// Buys when Chaikin crosses above zero and CCI is rising, sells on opposite.
/// </summary>
public class iCHO_Trend_CCIDualOnMA_FilterStrategy : Strategy
{
private readonly StrategyParam<int> _cciLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _cciLevel;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal? _prevCci;
private decimal? _prevPrevCci;
private int _candlesSinceTrade;
public int CciLength
{
get => _cciLength.Value;
set => _cciLength.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal CciLevel
{
get => _cciLevel.Value;
set => _cciLevel.Value = value;
}
public int SignalCooldownCandles
{
get => _signalCooldownCandles.Value;
set => _signalCooldownCandles.Value = value;
}
public iCHO_Trend_CCIDualOnMA_FilterStrategy()
{
_cciLength = Param(nameof(CciLength), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Length", "CCI period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "Data");
_cciLevel = Param(nameof(CciLevel), 100m)
.SetDisplay("CCI Level", "CCI threshold for entry", "Indicators");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = null;
_prevPrevCci = null;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCci = null;
_prevPrevCci = null;
_candlesSinceTrade = SignalCooldownCandles;
var cci = new CommodityChannelIndex { Length = CciLength };
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 (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
if (_prevCci.HasValue && _prevPrevCci.HasValue)
{
if (_prevPrevCci.Value < -CciLevel && _prevCci.Value < -CciLevel && cciValue > -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (_prevPrevCci.Value > CciLevel && _prevCci.Value > CciLevel && cciValue < CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
_prevPrevCci = _prevCci;
_prevCci = cciValue;
}
}
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 i_cho__trend_cci_dual_on_ma__filter_strategy(Strategy):
def __init__(self):
super(i_cho__trend_cci_dual_on_ma__filter_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._cci_length = self.Param("CciLength", 14)
self._cci_level = self.Param("CciLevel", 100.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 4)
self._prev_cci = 0.0
self._prev_prev_cci = 0.0
self._candles_since_trade = 4
self._has_two = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciLength(self):
return self._cci_length.Value
@CciLength.setter
def CciLength(self, value):
self._cci_length.Value = value
@property
def CciLevel(self):
return self._cci_level.Value
@CciLevel.setter
def CciLevel(self, value):
self._cci_level.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(i_cho__trend_cci_dual_on_ma__filter_strategy, self).OnReseted()
self._prev_cci = 0.0
self._prev_prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_two = False
def OnStarted2(self, time):
super(i_cho__trend_cci_dual_on_ma__filter_strategy, self).OnStarted2(time)
self._prev_cci = 0.0
self._prev_prev_cci = 0.0
self._candles_since_trade = self.SignalCooldownCandles
self._has_two = False
cci = CommodityChannelIndex()
cci.Length = self.CciLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
cci_val = float(cci_value)
level = float(self.CciLevel)
if self._has_two:
# Both prev_prev and prev were below -level, now crossing above -level
if self._prev_prev_cci < -level and self._prev_cci < -level and cci_val > -level and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
# Both prev_prev and prev were above +level, now crossing below +level
elif self._prev_prev_cci > level and self._prev_cci > level and cci_val < level and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
self._prev_prev_cci = self._prev_cci
self._prev_cci = cci_val
if not self._has_two:
if self._prev_prev_cci != 0.0 or self._prev_cci != 0.0:
self._has_two = True
def CreateClone(self):
return i_cho__trend_cci_dual_on_ma__filter_strategy()