English Deutsch 日本語
preview
价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA

价格行为分析工具包开发(第三十部分):商品通道指数(CCI)零线的EA

MetaTrader 5交易系统 |
69 0
Christian Benjamin
Christian Benjamin

引言

在本文中,我们构建了一款基于MQL5语言的工具,旨在简化价格行为交易流程。该系统通过自动化实现一项综合策略,该策略融合四大要素:双商品通道指数(CCI)、34周期指数移动平均线(EMA)、平均真实波幅(ATR)以及纯价格行为分析。在逐一解析各指标并完整阐述交易策略后,我们将详细演示EA的逐步开发过程,包括测试、结果分析及结论总结。文章末页的表格汇总了我们已开发的其他工具。

请查阅以下目录内容。


指标概览

商品通道指数(CCI)

CCI是一种动量振荡指标,通过将当前价格与其近期平均价格进行比较,以识别潜在的超买、超卖或趋势反转信号。CCI的计算方法如下:

CCI=(典型价格 − SMA)/(0.015 × 平均偏差)

典型价格(TP) —— 通过将某一周期内的最高价、最低价和收盘价相加,再将总和除以3计算得出:

           h + l + c  
TP =       ---------
               3

SMA —— 简单移动平均线
SMA通过选取特定窗口期(如最近20个交易周期)内的典型价格,将其相加后除以周期数计算得出。该过程可平滑日常价格波动,使潜在趋势更易于辨识。

平均偏差
平均偏差是典型价格与其简单移动平均线之间绝对偏差的平均值。

这一统计量反映了各典型价格与SMA的平均偏离程度,为所选周期内的价格波动性(波动率)提供了直观度量。

常数因子0.015

通过乘以该固定系数,可将CCI输出值压缩至-100至+100的区间范围内,使指标更易解读,同时突出极端偏离值(显示显著超买或超卖状态)。

该指标由唐纳德·兰伯特(Donald Lambert)于1980年开发并首次发布,最初用于分析商品的周期性波动,现已广泛应用于股票、外汇及其他资产类别。在本策略中,我们同时使用两条CCI线:快速CCI(25周期)捕捉短期动能,慢速CCI(50周期)评估整体市场强度。多数CCI读数位于-100至+100区间,超出该范围通常表明市场势头异常强劲或疲软。尽管这些极端水平可作为波动性过滤器,但零线仍是本策略的核心触发信号。CCI从负值区域上穿零线,预示潜在多头转向;反之,下穿零线则标志空头压力显现。

由于零线交叉信号比±100区间突破来得更早,虽然能让EA更快入场,但也容易带来虚假信号(“锯齿”波动)。为了过滤掉这些噪声,EA可以选择加入额外的确认条件:比如价格向上突破+100能强化做多信号,向下跌破-100则巩固做空信号。简单来说,零线交叉负责发出核心预警,而±100区间则起到辅助过滤的作用,两者并不冲突。

图例2:CCI指标

以下内容将解释如何在MQL5中创建两个CCI指标句柄、正确管理它们,并获取其数值。 

  • OnInit()中创建CCI句柄

handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);

  • 在OnTick()函数中使用CopyBuffer()复制缓冲区值

CopyBuffer(handleCCI_Long,  0, 0, 2, cciL);
CopyBuffer(handleCCI_Short, 0, 0, 1, cciS);

34周期指数移动平均线(EMA)

EMA是一种趋势跟踪指标,它通过对近期K线赋予更大权重,从而能快速响应新的价格信息。这种指数加权方式可过滤掉短期市场杂音,使主要趋势更清晰可见。在一小时图表中,常使用34周期EMA,因为其时间跨度大致相当于一个交易周。仅沿34周期EMA指示的方向交易:当价格位于EMA上方时做多,当价格位于EMA下方时做空。该规则作为持续的趋势过滤器,可避免策略进行逆势交易。

图例3:34周期EMA

以下是在MQL5中构建34周期EMA指标句柄的方法

  • 声明:

input int EMAPeriod = 34;

  • 初始化:

handleEMA = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);

  • 复制缓冲区(相同逻辑):

double ema34[1];
if(CopyBuffer(handleEMA, 0, 0, 1, ema34) < 1) return;
double ema_now = ema34[0];

平均真实波幅(ATR)

ATR是由小威尔斯·怀尔德(J. Welles Wilder Jr.)开发的一种波动性衡量指标。他在1978年出版的《技术交易系统新概念》一书中首次介绍了该指标。ATR默认通过对选定回溯期(通常为14个周期)内的真实波幅取平均值来计算,因此它能反映市场的“原始”价格波动情况。

在本文中,我们使用ATR来确定并设置SL和TP水平。以下是在MQL5中实现该指标的方法:

  • 声明

int handleATR = INVALID_HANDLE;
input int ATR_Period = 14;

  • 初始化

handleATR = iATR(_Symbol, _Period, ATR_Period);
if(handleATR == INVALID_HANDLE) return INIT_FAILED;

  • 复制缓冲区

double atrBuf[1];
if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) > 0)
{
   double atrValue = atrBuf[0];
   // use atrValue here…
}

  • 清理

if(handleATR != INVALID_HANDLE)
    IndicatorRelease(handleATR);


策略逻辑

在本章节中,我们将阐述CCI零线的EA如何通过结合两个CCI指标与一条34周期指数移动平均线(EMA)来生成入场信号。当价格上穿CCI零线时,表明市场势头正向上转变,暗示可能出现看涨信号或买入压力增加。相反,当价格下穿CCI零线时,则表明市场动能正向下转变,预示可能出现看跌趋势或卖压增大。 

相反,当价格下穿CCI的零线时,则表明市场势头正向下转变,预示可能出现看跌趋势或卖压增大。另一方面,如果价格跌破34周期EMA.,则表明势头减弱,可能预示看跌趋势的开始,暗示可能出现卖出信号。

所用指标

  • CCI(25)—— 快速商品通道指数
  • CCI(50)—— 慢速商品通道指数
  • EMA(34)—— 基于收盘价的34周期指数移动平均线

买入信号条件

  • CCI(25)位于零线上方:CCI(25)必须位于零线之上,表明短期看涨势头。
  • CCI(50)零线穿越:CCI(50)必须向上穿越零线,确认长期趋势转变。
  • 价格确认:当前K线必须收盘于EMA(34)上方,验证看涨倾向。

图例4:买入信号条件

当新K线同时满足上述三个条件时,EA会记录一个买入信号。

卖出信号条件

  • CCI(25)位于零线下方:CCI(25)必须处于零线之下,表明短期看跌势头。
  • CCI(50)零线穿越:CCI(50)必须向下穿越零线,确认长期趋势转向看跌。
  • 价格确认:当前K线必须收盘于EMA(34)下方,验证看跌倾向。

图例5:卖出信号条件

当新K线同时满足上述三个条件时,EA会记录一个卖出信号。


EA设计

当EA启动时,首先设置必要的指标句柄,以便从图表中获取实时数据。这一步在OnInit()函数中完成。这些句柄包括两个CCI指标::一个使用较长周期(50)以识别大趋势,另一个使用较短周期(25)作为确认指标;以及一个周期为34的EMA,用作趋势过滤器。具体而言,代码通过调用iCCI()和iMA()等函数创建这些句柄,这些函数会向MetaTrader内部引擎请求相应的指标。

正确创建这些句柄至关重要;若任何句柄初始化失败(返回INVALID_HANDLE),EA将停止运行,以防止在操作过程中产生错误信号或发生错误。此外,仅当启用基于ATR的止损和止盈管理(通过UseATR参数控制)时,才会创建可选的ATR句柄。

handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);
handleEMA       = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(UseATR)
    handleATR = iATR(_Symbol, _Period, ATR_Period);

  • 这种方法确保EA在每一次价格变动(tick)时获取必要的数据,从而做出明智的交易决策。

该EA还具备一项功能,即限制交易活动在特定时段内进行,尤其是针对美元兑日元(USDJPY)的东京交易时段,这样有助于避免在交易量较低的时段进行交易,因为这些时段可能导致价格出现不可预测的波动。这一功能是通过在OnTick()函数中检查当前服务器时间来实现的。如果启用了时段过滤器,代码会将当前时间转换为结构化格式(MqlDateTime),并将小时部分与用户定义的开始和结束小时进行比较。如果当前小时不在该时段范围内,EA将直接退出,跳过进一步的分析或交易操作。

if(UseTokyoSessionFilter && _Symbol == "USDJPY")
{
    datetime now = TimeTradeServer();
    MqlDateTime tm;
    TimeToStruct(now, tm);
    int hr = tm.hour;
    if(hr < TokyoStartHour || hr >= TokyoEndHour)
        return;
}

  • 仅在指定时段内进行交易,有助于降低在非高峰时段流动性不足的市场中常见的滑点风险和虚假信号风险。

为防止在同一根K线内进行多次评估,该EA采用了一种简单而有效的逻辑:记录记录上一根已处理K线的时间,并且只有在出现新K线时才会继续执行后续操作。此功能通过一个静态变量lastTime实现,该变量在函数调用之间会保留其值。当新的一笔价格变动(tick)到来时,代码会将当前K线的时间(通过iTime()函数获取)与存储的lastTime值进行比较。如果两者相同,则函数提前返回,确保每根K线只进行一次计算和决策。

static datetime lastTime=0;
datetime t = iTime(_Symbol, _Period, 0);
if(t == lastTime) return; // same bar, skip
lastTime = t;

  • 通过优化性能、防止重复信号生成,以及使执行过程与交易者基于典型时间框架的分析保持一致,这种方法显著提升了交易策略的整体效率和精准度。

每当新K线形成时,该EA会调用CopyBuffer()函数,提取最新的25周期CCI、50周期CCI和34周期EMA的数值(包括前一根K线的数据,以便检测交叉信号)。其中,慢速的50周期CCI用于界定大趋势方向,而快速的25周期CCI则用于确认入场势头。EMA则用于过滤市场噪音,确保价格走势与趋势方向一致。如果您启用了基于ATR的止损功能,该EA还会获取当前的14周期ATR值。

double cciL[2], cciS[1], emaVal[1];
if(CopyBuffer(handleCCI_Long, 0, 0, 2, cciL) < 2) return;
if(CopyBuffer(handleCCI_Short, 0, 0, 1, cciS) < 1) return;
if(CopyBuffer(handleEMA, 0, 0, 1, emaVal) < 1) return;

double atrValue = 0.0;
if(UseATR)
{
    double atrBuf[1];
    if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) < 1) return;
    atrValue = atrBuf[0];
}

  • 这些指标读数共同驱动所有交易决策:CCI用于识别趋势反转或延续,EMA用于验证趋势方向一致性,而ATR则用于动态调整风险规模。

交易逻辑的核心在于检测长期CCI指标穿越零线的时刻,这表示潜在的趋势转变。CCI向上穿越零线(由负转正)预示着看涨走势,而向下穿越则表明看跌势头。代码通过比较长期CCI缓冲区的先前值和当前值来捕捉这些时刻。一旦检测到穿越信号,EA会应用额外的过滤条件:短期CCI必须与之相符(看涨时为正,看跌时为负),且当前价格必须分别位于EMA之上或之下。

double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

if(crossUp && cciS[0] > 0 && price > emaVal[0])
    RegisterSignal(true, price, atrValue);
if(crossDown && cciS[0] < 0 && price < emaVal[0])
    RegisterSignal(false, price, atrValue);

  • 这种分层验证机制有助于过滤掉虚假信号,确保仅在多个指标达成一致且趋势过滤器支持该走势时才入场交易。

一旦识别出有效信号,EA将计算止损(SL)和止盈(TP)的合理价位。如果启用了基于ATR的资金管理功能,这些价位将通过当前ATR值乘以用户自定义的乘数(例如1.5)动态生成。止损位会设置在入场价一定距离之外 —— 对于多头头寸为入场价下方,对于空头头寸为入场价上方。止盈位则根据风险回报比按比例设置在更远的位置,通常为止损距离的1.5倍。

double dist = UseATR ? atrValue * ATR_Multiplier : SLBufferPoints * _Point;
double slPrice = isBuy ? price - dist : price + dist;
double tpPrice = isBuy ? price + dist * RiskRewardRatio : price - dist * RiskRewardRatio;

  • 随后,EA会在图表上生成可视化标识:在当前K线外侧绘制箭头标记入场点位,并绘制水平线显示SL和TP价位。

ObjectCreate(0, name, OBJ_ARROW, 0, barTime, arrowPrice);
ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
ObjectSetInteger(0, name, OBJPROP_COLOR, isBuy ? BuyArrowColor : SellArrowColor);
ObjectCreate(0, "SL_Line", OBJ_HLINE, 0, 0, slPrice);
ObjectCreate(0, "TP_Line", OBJ_HLINE, 0, 0, tpPrice);

  • 这些可视化提示有助于交易者直观地验证信号,并确保EA的逻辑与实际市场状况相符。

在绘制完可视化标记后,EA会弹出包含详细信息的警报,包括入场价格、SL和TP价位。

Alert("CCI ZeroLine EMA + R:R 1:1.5 " + (isBuy ? "BUY" : "SELL") +
      StringFormat(" @%.5f | SL: %.5f | TP: %.5f", price, slPrice, tpPrice));

还会更新内部跟踪数组,这些数组存储每笔交易的SL和TP价位,同时记录一个布尔值表示,用以表明交易结果是否已确定。这些数组对于监控正在进行的交易至关重要,特别是在回测或实盘交易期间,它们使EA能够同时管理多个未平仓头寸,并评估其表现。

signalSL[totalSignals] = slPrice;
signalTP[totalSignals] = tpPrice;
resolved[totalSignals] = false;

ResolveSignals()函数负责跟踪活跃交易。它会遍历所有未平仓信号,检查近期K线的最高价和最低价,以判断是否触及TP或SL价位。如果最高价超过TP价位,则将该笔交易标记为盈利;若最低价跌破SL价位,则标记为亏损。这一过程会更新总信号数和盈利信号数的计数器,从而为策略的有效性提供统计反馈。这种监控对于回测至关重要,因为精确的交易结果记录会影响整体表现指标,如胜率、盈利因子和回撤幅度。

double high = iHigh(_Symbol, _Period, idx);
double low = iLow(_Symbol, _Period, idx);
if(high >= signalTP[i]) { winSignals++; resolved[i]=true; }
if(low <= signalSL[i]) { resolved[i]=true; }

当EA被移除或关闭图表时,OnDeinit()函数通过使用IndicatorRelease()确保释放所有指标句柄。还会删除运行期间创建的所有图形对象,例如箭头和水平线。妥善管理资源可以防止内存泄漏,保持图表整洁,确保后续运行或其他EA正常工作,避免残留对象造成干扰。

IndicatorRelease(handleCCI_Long);
IndicatorRelease(handleCCI_Short);
ObjectDelete(0, "SL_Line");
ObjectDelete(0, "TP_Line");

完整代码

//+------------------------------------------------------------------+
//|                                                  CCI Zero-Line EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>
#include <Tools\Datetime.mqh>

CTrade trade;

//--- Session filter for USDJPY (Tokyo session)
input bool UseTokyoSessionFilter = false;  // enable Tokyo session filter
input int  TokyoStartHour        = 0;      // session start hour (server time)
input int  TokyoEndHour          = 9;      // session end hour   (server time, exclusive)

//--- SL/TP settings
input bool   UseATR           = true;   // true = ATR-based SL/TP, false = fixed points
input int    ATR_Period       = 14;     // ATR look-back
input double ATR_Multiplier   = 1.5;    // SL/TP distance = ATR × this
input double SLBufferPoints   = 10.0;   // fallback SL offset in pips if UseATR=false
input double RiskRewardRatio  = 1.5;    // TP = SL × 1.5 (1:1.5 RR)

//--- Indicator periods
input int CCI_LongPeriod   = 50;  // CCI long period (zero-line cross)
input int CCI_ShortPeriod  = 25;  // CCI short period (confirmation)
input int EMAPeriod        = 34;  // EMA period for trend filter

//--- Arrow colors
input color BuyArrowColor  = clrLime;
input color SellArrowColor = clrRed;

//--- Indicator handles
int handleCCI_Long = INVALID_HANDLE;
int handleCCI_Short= INVALID_HANDLE;
int handleEMA      = INVALID_HANDLE;
int handleATR      = INVALID_HANDLE;

//--- Signal-tracking arrays
int    totalSignals = 0;
int    winSignals   = 0;
int    signalBar[];
double signalSL[];
double signalTP[];
bool   resolved[];

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
   handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);
   handleEMA       = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(handleCCI_Long == INVALID_HANDLE ||
      handleCCI_Short== INVALID_HANDLE ||
      handleEMA      == INVALID_HANDLE)
      return(INIT_FAILED);

   if(UseATR)
     {
      handleATR = iATR(_Symbol, _Period, ATR_Period);
      if(handleATR == INVALID_HANDLE)
         return(INIT_FAILED);
     }

   if(RiskRewardRatio <= 0 ||
      ATR_Multiplier   <= 0 ||
      (SLBufferPoints <= 0 && !UseATR))
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(handleCCI_Long  != INVALID_HANDLE)
      IndicatorRelease(handleCCI_Long);
   if(handleCCI_Short != INVALID_HANDLE)
      IndicatorRelease(handleCCI_Short);
   if(handleEMA       != INVALID_HANDLE)
      IndicatorRelease(handleEMA);
   if(UseATR && handleATR != INVALID_HANDLE)
      IndicatorRelease(handleATR);

   ObjectDelete(0, "SL_Line");
   ObjectDelete(0, "TP_Line");
  }

//+------------------------------------------------------------------+
//| Strategy Tester summary                                          |
//+------------------------------------------------------------------+
double OnTester()
  {
   double winRate = totalSignals > 0
                    ? 100.0 * winSignals / totalSignals
                    : 0.0;
   PrintFormat("=== Backtest Win-Rate ===\nSignals: %d  Wins: %d  Win-Rate: %.2f%%",
               totalSignals, winSignals, winRate);
   return(winRate);
  }

//+------------------------------------------------------------------+
//| Tick handler                                                     |
//+------------------------------------------------------------------+
void OnTick()
  {
// Tokyo session filter for USDJPY
   if(UseTokyoSessionFilter && _Symbol == "USDJPY")
     {
      datetime now = TimeTradeServer();
      MqlDateTime tm;
      TimeToStruct(now, tm);
      int hr = tm.hour;
      if(hr < TokyoStartHour || hr >= TokyoEndHour)
         return;
     }

// Only act on new bar
   static datetime lastTime = 0;
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == lastTime)
      return;
   lastTime = t;

// Copy indicator buffers
   double cciL[2], cciS[1], emaVal[1];
   if(CopyBuffer(handleCCI_Long,  0, 0, 2, cciL)  < 2)
      return;
   if(CopyBuffer(handleCCI_Short, 0, 0, 1, cciS)  < 1)
      return;
   if(CopyBuffer(handleEMA,       0, 0, 1, emaVal) < 1)
      return;

// ATR if needed
   double atrValue = 0.0;
   if(UseATR)
     {
      double atrBuf[1];
      if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) < 1)
         return;
      atrValue = atrBuf[0];
     }

   double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

// Detect zero-line cross
   bool crossUp   = (cciL[1] < 0 && cciL[0] > 0);
   bool crossDown = (cciL[1] > 0 && cciL[0] < 0);

// Confirm with short CCI & EMA trend filter
   if(crossUp   && cciS[0] >  0 && price > emaVal[0])
      RegisterSignal(true,  price, atrValue);
   if(crossDown && cciS[0] <  0 && price < emaVal[0])
      RegisterSignal(false, price, atrValue);

// Resolve pending signals for SL/TP hits
   ResolveSignals();
  }

//+------------------------------------------------------------------+
//| Register new buy/sell signal                                     |
//+------------------------------------------------------------------+
void RegisterSignal(bool isBuy, double price, double atrVal)
  {
   double dist    = UseATR
                    ? atrVal * ATR_Multiplier
                    : SLBufferPoints * _Point;
   double slPrice = isBuy ? price - dist : price + dist;
   double tpPrice = isBuy
                    ? price + dist * RiskRewardRatio
                    : price - dist * RiskRewardRatio;

// Arrow placement just outside the candle
   datetime barTime = iTime(_Symbol, _Period, 0);
   double   barHigh = iHigh(_Symbol, _Period, 0);
   double   barLow  = iLow(_Symbol, _Period, 0);
   double   offset  = 5 * _Point;  // e.g. 5-pip offset

   double arrowPrice = isBuy
                       ? barLow  - offset
                       : barHigh + offset;

// Use numeric arrow codes directly
   int arrowCode = isBuy ? 233 : 234;

   string name = (isBuy ? "BUY_" : "SELL_")
                 + TimeToString(TimeTradeServer(), TIME_SECONDS);
   ObjectCreate(0, name, OBJ_ARROW, 0, barTime, arrowPrice);
   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
   ObjectSetInteger(0, name, OBJPROP_COLOR,     isBuy ? BuyArrowColor : SellArrowColor);
   ObjectSetInteger(0, name, OBJPROP_WIDTH,     2);

// Draw SL/TP lines
   DrawLine("SL_Line", slPrice, clrRed);
   DrawLine("TP_Line", tpPrice, clrLime);

// Alert
   Alert("CCI ZeroLine EMA + R:R 1:1.5 "
         + (isBuy ? "BUY" : "SELL")
         + StringFormat(" @%.5f | SL: %.5f | TP: %.5f",
                        price, slPrice, tpPrice));

// Track for backtest metrics
   totalSignals++;
   ArrayResize(signalBar,  totalSignals);
   ArrayResize(signalSL,   totalSignals);
   ArrayResize(signalTP,   totalSignals);
   ArrayResize(resolved,   totalSignals);

   signalBar[totalSignals-1] = 0;
   signalSL[totalSignals-1]  = slPrice;
   signalTP[totalSignals-1]  = tpPrice;
   resolved[totalSignals-1]  = false;
  }

//+------------------------------------------------------------------+
//| Resolve pending signals (SL/TP checks)                           |
//+------------------------------------------------------------------+
void ResolveSignals()
  {
   int bars = Bars(_Symbol, _Period);
   for(int i = 0; i < totalSignals; i++)
     {
      if(resolved[i])
         continue;
      signalBar[i]++;
      int idx = signalBar[i];
      if(idx >= bars)
         continue;

      double high = iHigh(_Symbol, _Period, idx);
      double low  = iLow(_Symbol, _Period, idx);

      if(high >= signalTP[i])
        {
         winSignals++;
         resolved[i] = true;
        }
      else
         if(low <= signalSL[i])
           {
            resolved[i] = true;
           }
     }
  }

//+------------------------------------------------------------------+
//| Draw a horizontal SL/TP line                                     |
//+------------------------------------------------------------------+
void DrawLine(string name, double price, color clr)
  {
   if(ObjectFind(0, name) >= 0)
      ObjectDelete(0, name);
   ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
   ObjectSetString(0, name, OBJPROP_TEXT, name);
  }
//+------------------------------------------------------------------+


测试

以下是我们EA的回测结果。

我在美元兑日元货币对上对EA进行回测,以下是交易日志中的记录。

  • 输入
2025.06.30 09:34:54.140   UseTokyoSessionFilter=false
2025.06.30 09:34:54.140   TokyoStartHour=0
2025.06.30 09:34:54.140   TokyoEndHour=9
2025.06.30 09:34:54.140   UseATR=true
2025.06.30 09:34:54.140   ATR_Period=14
2025.06.30 09:34:54.140   ATR_Multiplier=1.5
2025.06.30 09:34:54.140   SLBufferPoints=10.0
2025.06.30 09:34:54.140   RiskRewardRatio=1.0
2025.06.30 09:34:54.140   CCI_LongPeriod=50
2025.06.30 09:34:54.140   CCI_ShortPeriod=25
2025.06.30 09:34:54.140   EMAPeriod=34
2025.06.30 09:34:54.140   BuyArrowColor=65280
2025.06.30 09:34:54.140   SellArrowColor=255

配置表明,东京时段过滤器处于禁用状态,允许在东京特定时段之外进行交易;然而,如果启用该过滤器,交易将被限制在东京时间00:00至09:00之间。该EA采用14周期的ATR指标,并应用1.5倍乘数,根据当前市场波动性调整止损和止盈价位。为防止过早止损,在止损价位上额外增加10个点的缓冲空间,同时将风险回报比设定为1:1,以平衡潜在收益与损失。

CCI采用50周期生成多头信号,25周期生成空头信号,策略中还纳入了34周期的EMA。可视化信号通过箭头表示,绿色箭头代表买入信号,蓝色箭头代表卖出信号,分别对应其颜色代码。

输入参数可根据个人偏好进行自定义。此处提供的设置是我在测试期间使用的参数,这些参数带来了我认为比较理想的结果。

  • 信号

2025.06.30 09:34:54.447 2025.01.02 15:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @157.01800 | SL: 157.45075 | TP: 156.58525
2025.06.30 09:34:56.655 2025.01.09 06:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @158.04800 | SL: 158.30343 | TP: 157.79257
2025.06.30 09:40:35.572 2025.01.29 07:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @155.21100 | SL: 155.47704 | TP: 154.94496
2025.06.30 09:40:36.759 2025.01.31 07:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 BUY @154.68200 | SL: 154.25439 | TP: 155.10961
2025.06.30 09:40:45.870 2025.02.26 06:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 BUY @149.42600 | SL: 149.05400 | TP: 149.79800
2025.06.30 09:40:46.737 2025.02.28 03:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @149.51900 | SL: 150.03650 | TP: 149.00150
2025.06.30 09:40:49.726 2025.03.06 01:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @149.14200 | SL: 149.68200 | TP: 148.60200
2025.06.30 09:40:58.522 2025.03.21 16:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @148.97200 | SL: 149.35418 | TP: 148.58982
2025.06.30 09:41:01.363 2025.04.01 00:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 BUY @149.93500 | SL: 149.57425 | TP: 150.29575
2025.06.30 09:41:16.557 2025.04.28 13:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @143.33500 | SL: 143.69232 | TP: 142.97768
2025.06.30 09:41:36.021 2025.06.18 14:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @144.85500 | SL: 145.08911 | TP: 144.62089
2025.06.30 09:41:37.454 2025.06.23 19:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @146.15000 | SL: 146.65711 | TP: 145.64289
2025.06.30 09:41:38.360 2025.06.25 23:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @145.21700 | SL: 145.47286 | TP: 144.96114

  • 胜率

2025.06.30 09:41:39.206 2025.06.29 23:59:59   Signals: 13  Wins: 10  Win-Rate: 76.92%

以下是一张展示测试结果的动态图像(GIF):红色向下箭头表示卖出信号,绿色向上箭头代表买入信号,红色线条表示止损价位,绿色线条表示止盈目标。


结论

总而言之,这款基于CCI零线的EA将所有清晰、规则驱动的入场信号直接呈现在图表上:

  • 我们使用50周期的CCI()来感知大趋势,同时用25周期的CCI()来确认动能是否就绪,因此仅在两者均发出信号时才采取行动。
  • 34周期的EMA可防止我们盲目追涨杀跌。如果价格收盘位于该均线右侧,则表明趋势真实可靠。
  • 基于ATR的止损设置使止损距离随市场波动性灵活调整 —— 市场盘整时止损收紧,剧烈波动时止损放宽。止盈目标则保持相应比例,锁定稳定的盈亏比。

这种组合意味着箭头仅在价格、势头和波动性均指向同一方向时才会出现。您将获得更少的虚假信号、响应迅速的风险管理,以及每个入场和出场价位选择依据的完全透明。您可根据任何交易品种或时间框架微调CCI周期、EMA长度或ATR乘数,凭借基于坚实、多层分析的工具带来的信心进行交易。本EA仅供教学用途。未经历史数据全面回测或模拟账户测试,切勿将其投入到实盘市场。在考虑任何实盘交易前,请确保对测试结果感到满意。





   
图表展示器
分析评论
分析大师
分析预测 
波动率导航仪
均值回归信号收割器
信号脉冲 
指标看板 
外部数据流
VWAP
Heikin Ashi   FibVWAP  
RSI背离
抛物线止损与反转指标 (PSAR) 
四分位绘制脚本
侵入探测器
TrendLoom工具  四分位看板 
ZigZag分析仪  相关性探索  市场结构反转检测工具
关联仪表盘  货币强度计 
PAQ分析工具 
双EMA分形突破器
针形柱、吞噬形态与相对强弱指数(RSI)背离
流动性扫单 开盘区间突破工具 暴涨与暴跌拦截 CCI零线的EA

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18551

附加的文件 |
CCI.mq5 (18.28 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
市场模拟(第 14 部分):套接字(八) 市场模拟(第 14 部分):套接字(八)
许多程序员可能会认为,我们应该放弃使用 Excel,直接使用 Python,使用一些允许 Python 生成 Excel 文件以供以后分析结果的包。不过,正如前一篇文章提到的,虽然这个解决方案对于很多程序员来说是最简单的,但它不会被一些用户接受。在这种特殊情况下,用户总是正确的。作为程序员,我们必须找到一种让一切都能正常工作的方法。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格行为分析工具包开发(第二十九部分):暴涨与暴跌拦截EA 价格行为分析工具包开发(第二十九部分):暴涨与暴跌拦截EA
了解暴涨与暴跌拦截EA如何将您的图表转变为一个主动预警系统 —— 通过超高速扫描价格变动速度、检查波动率激增情况、确认趋势走向以及运用关键枢轴区域过滤条件,精准识别市场的爆发性行情。该工具以清晰的绿色“暴涨”和红色“暴跌”箭头为您的每一次决策提供指引,助您排除市场杂音,以前所未有的方式把握市场价格飙升的机遇。深入探究其工作原理,了解它为何能成为您下一个不可或缺的交易优势。