MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略·进阶篇
🔁

MQL5交易策略自动化(第二十部分):基于CCI和AO指标的多品种策略·进阶篇

(2/3)·单品种脚本在多货币对上频繁漏信号?这篇教你用类结构一次管全市场

实战向 第 2/3 篇
很多人把第十九部分的单品种包络线逻辑直接复制去跑多货币对,结果同周期信号互相打架、订单堆叠失控。多品种不是多开几个图表,而是要在架构层把信号生成和风控统一调度。

「策略类的骨架与调试开关」

多品种 EA 的核心往往落在一个交易策略类上。下面这段声明把品种数据、参数、交易日标记和 CCI 阈值都收进私有成员,相当于给后续信号计算留好了插槽。 CTradingStrategy 里 m_symbols[] 存品种数组,m_array_size 跟踪实际个数,m_last_day 与 m_is_new_day 配合用来识别跨日。CCI_TREND_BUY_VALUE 和 CCI_TREND_SELL_VALUE 是两个 const int 阈值常量,后面判断趋势方向时直接比对。 调试输出做了两层拦截:PrintDebug 只在 debug_mode 为真且不在优化器中才打印;PrintMessage 则屏蔽掉优化阶段的冗余日志。这样回测跑几千代时不会把终端刷爆。 PrepareSymbolsList 用 StringSplit 按逗号切分参数字符串,sep 取 ',' 的字符码。m_array_size 就是切出来的有效品种数,后面循环初始化 m_symbols 就靠它。外汇与贵金属波动剧烈,多品种同时跑务必先在策略测试器里用少量品种验证数组越界风险。

MQL5 / C++
class="type">class="kw">double ao_signal_data[];                 class=class="str">"cmt">//--- Store AO signal data
class="type">class="kw">double ao_trend_data[];                   class=class="str">"cmt">//--- Store AO trend data
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Trading Strategy Class                                            | //--- Implement a class for trading strategy logic
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTradingStrategy {
class="kw">private:
  CTrade          m_trade;                class=class="str">"cmt">//--- Initialize trade object for trading operations
  TradingParameters m_params;             class=class="str">"cmt">//--- Store trading parameters
  SymbolData      m_symbols[];            class=class="str">"cmt">//--- Store array of symbol data
  class="type">int             m_array_size;           class=class="str">"cmt">//--- Track number of symbols
  class="type">class="kw">datetime        m_last_day;             class=class="str">"cmt">//--- Store last day timestamp
  class="type">bool            m_is_new_day;           class=class="str">"cmt">//--- Indicate new day detection
  class="type">int             m_candle_shift;         class=class="str">"cmt">//--- Set candle shift for signal calculation
  class="kw">const class="type">int       CCI_TREND_BUY_VALUE;    class=class="str">"cmt">//--- Define constant for CCI trend buy threshold
  class="kw">const class="type">int       CCI_TREND_SELL_VALUE;   class=class="str">"cmt">//--- Define constant for CCI trend sell threshold
}
class="type">void PrintDebug(class="type">class="kw">string text) {            class=class="str">"cmt">//--- Define method to print debug messages
  if(m_params.debug_mode && !MQLInfoInteger(MQL_OPTIMIZATION)) { class=class="str">"cmt">//--- Check debug mode and optimization status
    Print(text);                          class=class="str">"cmt">//--- Output debug message
  }
}
class="type">void PrintMessage(class="type">class="kw">string text) {          class=class="str">"cmt">//--- Define method to print informational messages
  if(!MQLInfoInteger(MQL_OPTIMIZATION)) { class=class="str">"cmt">//--- Check if not in optimization mode
    Print(text);                          class=class="str">"cmt">//--- Output message
  }
}
class="type">void PrepareSymbolsList() {               class=class="str">"cmt">//--- Define method to prepare symbol list
  class="type">class="kw">string symbols_array[];                 class=class="str">"cmt">//--- Initialize temporary array for symbols
  class="type">class="kw">ushort sep = StringGetCharacter(",", class="num">0); class=class="str">"cmt">//--- Get comma separator character
  m_array_size = StringSplit(m_params.symbols, sep, symbols_array); class=class="str">"cmt">//--- Split symbols class="type">class="kw">string into array

多品种下指标句柄的批量初始化

做跨品种扫描时,先把符号数组按 m_array_size 扩容,再逐个把名称写进结构体、把 last_bar_time 清零,并用 SymbolSelect 保证该品种已在市场报价窗内。否则后面取指标数据会直接拿到空序列。 初始化函数 InitializeIndicators 对每个品种分别挂 4 个句柄:信号周期与趋势周期的 CCI(iCCI)、信号周期与趋势周期的 AO(iAO)。任一句柄返回 INVALID_HANDLE 就打印失败品种并回 false,避免半初始化状态跑后续逻辑。 挂完句柄后必须用 ArraySetAsSeries 把四个数据缓冲都设为时间序列(最新柱在索引 0)。外汇与贵金属波动剧烈、点差跳变频繁,若序列方向错乱,CCI/AO 的穿越判断会整体反向,实盘可能触发错误信号。 下面这段是核心初始化的可直接粘进 EA 的结构:先扩数组填符号,再按索引建指标。开 MT5 把 m_params 的周期参数改成自己常用的 H1/H4 组合,能立刻验证多品种加载是否报错。

MQL5 / C++
ArrayResize(m_symbols, m_array_size);      class=class="str">"cmt">//--- Resize symbol data array
for(class="type">int i = class="num">0; i < m_array_size; i++) {     class=class="str">"cmt">//--- Iterate through symbols
   m_symbols[i].name = symbols_array[i];     class=class="str">"cmt">//--- Set symbol name
   m_symbols[i].last_bar_time = class="num">0;           class=class="str">"cmt">//--- Initialize last bar time
   SymbolSelect(m_symbols[i].name, true);    class=class="str">"cmt">//--- Ensure symbol is in market watch
}
}

class="type">bool InitializeIndicators(class="type">int index) {       class=class="str">"cmt">//--- Define method to initialize indicators
   m_symbols[index].cci_signal_handle = iCCI(m_symbols[index].name, m_params.tf_signal, m_params.per_signal_cci, m_params.price_cci); class=class="str">"cmt">//--- Create CCI signal indicator
   if(m_symbols[index].cci_signal_handle == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
      Print("INITIALIZATION OF CCI SIGNAL FAILED: ", m_symbols[index].name); class=class="str">"cmt">//--- Log error
      class="kw">return class="kw">false;                          class=class="str">"cmt">//--- Return failure
   }
   m_symbols[index].cci_trend_handle = iCCI(m_symbols[index].name, m_params.tf_trend, m_params.per_trend_cci, m_params.price_cci); class=class="str">"cmt">//--- Create CCI trend indicator
   if(m_symbols[index].cci_trend_handle == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
      Print("INITIALIZATION OF CCI TREND FAILED: ", m_symbols[index].name); class=class="str">"cmt">//--- Log error
      class="kw">return class="kw">false;                          class=class="str">"cmt">//--- Return failure
   }
   m_symbols[index].ao_signal_handle = iAO(m_symbols[index].name, m_params.tf_signal); class=class="str">"cmt">//--- Create AO signal indicator
   if(m_symbols[index].ao_signal_handle == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
      Print("INITIALIZATION OF AO SIGNAL FAILED: ", m_symbols[index].name); class=class="str">"cmt">//--- Log error
      class="kw">return class="kw">false;                          class=class="str">"cmt">//--- Return failure
   }
   m_symbols[index].ao_trend_handle = iAO(m_symbols[index].name, m_params.tf_trend); class=class="str">"cmt">//--- Create AO trend indicator
   if(m_symbols[index].ao_trend_handle == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
      Print("INITIALIZATION OF AO TREND FAILED: ", m_symbols[index].name); class=class="str">"cmt">//--- Log error
      class="kw">return class="kw">false;                          class=class="str">"cmt">//--- Return failure
   }
   ArraySetAsSeries(m_symbols[index].cci_signal_data, true); class=class="str">"cmt">//--- Set CCI signal data as series
   ArraySetAsSeries(m_symbols[index].cci_trend_data, true);  class=class="str">"cmt">//--- Set CCI trend data as series
   ArraySetAsSeries(m_symbols[index].ao_signal_data, true);  class=class="str">"cmt">//--- Set AO signal data as series
   ArraySetAsSeries(m_symbols[index].ao_trend_data, true);   class=class="str">"cmt">//--- Set AO trend data as series
   class="kw">return true;                               class=class="str">"cmt">//--- Return success
}

◍ 多品种指标缓冲与持仓统计的实现骨架

做跨品种监控时,先把每个品种的 CCI 与 AO 双线信号缓冲拉进本地数组,是后续比价的地基。下面这段更新函数一次性拷贝 4 个句柄的最近 3 根数据,任意一处不足 3 根就直接返回失败并打印品种名,避免脏数据进入判断逻辑。

MQL5 / C++
class="type">bool UpdateIndicatorData(class="type">int index) {
  if(CopyBuffer(m_symbols[index].cci_signal_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].cci_signal_data) < class="num">3) {
    Print("UNABLE TO COPY CCI SIGNAL DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  if(CopyBuffer(m_symbols[index].cci_trend_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].cci_trend_data) < class="num">3) {
    Print("UNABLE TO COPY CCI TREND DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  if(CopyBuffer(m_symbols[index].ao_signal_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].ao_signal_data) < class="num">3) {
    Print("UNABLE TO COPY AO SIGNAL DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  if(CopyBuffer(m_symbols[index].ao_trend_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].ao_trend_data) < class="num">3) {
    Print("UNABLE TO COPY AO TREND DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  class="kw">return true;
}
逐行拆解:函数入口定义更新第 index 个品种的数据;前四个 if 分别用 CopyBuffer 从 CCI 信号线、CCI 趋势线、AO 信号线、AO 趋势线句柄拷贝 0~2 共 3 根柱到对应数组,返回值小于 3 说明拷贝不完整,Print 报错并 return false;全部通过才 return true。实盘里若某品种休市或句柄失效,这里会精准拦住,不会让后续信号误触发。 持仓统计则是另一块常用拼图。CountOrders 按 magic、symbol、持仓方向三维过滤,从 PositionsTotal()-1 反向遍历,用 PositionSelectByTicket 选中后再比对其整数与字符串属性。外汇与贵金属波动剧烈,这类统计务必在每次下单前跑一遍,防止同品种同魔术码重复开仓导致风险敞口失控。
MQL5 / C++
class="type">int CountOrders(class="type">class="kw">string symbol, class="type">int magic, class="type">ENUM_POSITION_TYPE type) {
  class="type">int count = class="num">0;
  for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {
    class="type">class="kw">ulong ticket = PositionGetTicket(i);
    if(PositionSelectByTicket(ticket)) {
      if(PositionGetInteger(POSITION_MAGIC) == magic &&
         PositionGetString(POSITION_SYMBOL) == symbol &&
         PositionGetInteger(POSITION_TYPE) == type) {
这段只贴到条件判断开口,实际函数内再 count++ 并闭合括号即可。反向遍历是因为 PositionsTotal 在循环中可能因平仓变动,从尾向前扫能避免漏单。把这两个函数直接塞进你的 EA 模板,开 MT5 接两个以上品种测一遍,能省掉很多手动排查的时间。

MQL5 / C++
class="type">bool UpdateIndicatorData(class="type">int index) {
  if(CopyBuffer(m_symbols[index].cci_signal_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].cci_signal_data) < class="num">3) {
    Print("UNABLE TO COPY CCI SIGNAL DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  if(CopyBuffer(m_symbols[index].cci_trend_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].cci_trend_data) < class="num">3) {
    Print("UNABLE TO COPY CCI TREND DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  if(CopyBuffer(m_symbols[index].ao_signal_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].ao_signal_data) < class="num">3) {
    Print("UNABLE TO COPY AO SIGNAL DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  if(CopyBuffer(m_symbols[index].ao_trend_handle, class="num">0, class="num">0, class="num">3, m_symbols[index].ao_trend_data) < class="num">3) {
    Print("UNABLE TO COPY AO TREND DATA: ", m_symbols[index].name);
    class="kw">return class="kw">false;
  }
  class="kw">return true;
}
class="type">int CountOrders(class="type">class="kw">string symbol, class="type">int magic, class="type">ENUM_POSITION_TYPE type) {
  class="type">int count = class="num">0;
  for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {
    class="type">class="kw">ulong ticket = PositionGetTicket(i);
    if(PositionSelectByTicket(ticket)) {
      if(PositionGetInteger(POSITION_MAGIC) == magic &&
         PositionGetString(POSITION_SYMBOL) == symbol &&
         PositionGetInteger(POSITION_TYPE) == type) {

「策略初始化与下单函数的代码骨架」

这段代码片段把多品种 CCI 趋势策略的底层动作拆开了:先数已有订单,再封装一个带日志的下单函数,最后在构造函数和 Init() 里落定参数。直接复制到 MT5 的 EA 头文件里就能跑通编译,但逻辑是否盈利得你自己回测。 count++ 那几行处在某个遍历持仓的循环末尾,每匹配到一个符合条件的订单计数器加一,最终 return count 把当前策略管理的订单数交出去。注意它只返回数量,不区分多空,统计日内持仓密度时得自己加过滤。 OpenOrder() 用 m_trade.PositionOpen 发市价单,失败走 PrintMessage 打错误码,成功打成交明细。价格、SL、TP 全用 %.5f 精度记录,对黄金这类小数位多的品种比对 EURUSDm 更必要。 构造函数里写死了 CCI_TREND_BUY_VALUE(-114) 和 CCI_TREND_SELL_VALUE(134),但 Init() 又把信号阈值设成 -90 和 130,两组常数不统一,实盘前必须确认以哪组为准。Init() 默认盯 EURUSDm、GBPUSDm、AUDUSDm 三个货币对,信号 CCI 周期 20、趋势 CCI 周期 24,信号看 M5、趋势看 H1——外汇和贵金属杠杆高,参数直接上实盘可能触发连续止损。

MQL5 / C++
    count++;
    }
  }
  class="kw">return count;
}
class="type">long OpenOrder(class="type">class="kw">string symbol, ENUM_ORDER_TYPE type, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp, class="type">class="kw">double lots, class="type">int magic, class="type">class="kw">string comment) {
  class="type">long ticket = m_trade.PositionOpen(symbol, type, lots, price, sl, tp, comment);
  if(ticket < class="num">0) {
    PrintMessage(StringFormat("Info - OrderSend %s %d_%s_%.5f error %.5f_%.5f_%.5f_#%d",
      comment, type, symbol, price, price, sl, tp, GetLastError()));
  } else {
    PrintMessage(StringFormat("Info - OrderSend done. Comment:%s, Type:%d, Sym:%s, Price:%.5f, SL:%.5f, TP:%.5f",
      comment, type, symbol, price, sl, tp));
  }
  class="kw">return ticket;
}
class="kw">public:
  CTradingStrategy() : CCI_TREND_BUY_VALUE(-class="num">114), CCI_TREND_SELL_VALUE(class="num">134) {
    m_last_day = class="num">0;
    m_is_new_day = true;
    m_array_size = class="num">0;
  }

  class="type">bool Init() {
    m_params.symbols = "EURUSDm,GBPUSDm,AUDUSDm";
    m_params.per_signal_cci = class="num">20;
    m_params.per_trend_cci = class="num">24;
    m_params.price_cci = PRICE_TYPICAL;
    m_params.cci_signal_buy_value = -class="num">90;
    m_params.cci_signal_sell_value = class="num">130;
    m_params.tf_signal = PERIOD_M5;
    m_params.tf_trend = PERIOD_H1;

EA 初始化时的参数与品种装载

这段初始化逻辑把一套 AO+突破策略的默认参数直接写死在结构体里,先看清几个关键数字:take-profit 设 200 点、stop-loss 设 300 点,仓位固定 0.01 手,滑点容忍 5 点,最大点差限制 20 点。AO 趋势过滤默认关闭(use_ao_for_trend=false),说明基础版本不靠 AO 方向筛单,只靠价格行为突破。 代码里 magic number 写死 123456789,订单注释标 "EA_AO_BP",方便在 MT5 终端里把这套 EA 的成交和其他脚本区分开。trade_anytime=false 时,m_candle_shift 被赋为 1,意味着信号判定会跳过当前未完成 K 线,只用已闭合的上一根——这是避免盘中假突破的硬约束。 初始化末尾会跑 PrepareSymbolsList() 并逐个 InitializeIndicators(i),任一品种指标句柄创建失败就直接 return false,EA 起不来。最后 PrintMessage 把当前品种点差用整数打进日志,你可以在策略测试器或实盘日志里核对:若点差超过 20,后续开单逻辑大概率被拦。外汇与贵金属杠杆高,点差跳变常发生在数据行情,这类写死阈值要先在模拟盘验证。

MQL5 / C++
m_params.use_ao_for_trend = class="kw">false;              class=class="str">"cmt">//--- Disable AO trend by class="kw">default
m_params.take = class="num">200;                            class=class="str">"cmt">//--- Set take-profit
m_params.stop = class="num">300;                            class=class="str">"cmt">//--- Set stop-loss
m_params.lots = class="num">0.01;                           class=class="str">"cmt">//--- Set lot size
m_params.slip = class="num">5;                              class=class="str">"cmt">//--- Set slippage
m_params.max_spread = class="num">20;                       class=class="str">"cmt">//--- Set maximum spread
m_params.magic = class="num">123456789;                     class=class="str">"cmt">//--- Set magic number
m_params.trade_anytime = class="kw">false;                 class=class="str">"cmt">//--- Disable trade anytime
m_params.comment = "EA_AO_BP";                  class=class="str">"cmt">//--- Set trade comment
m_params.tester_max_balance = class="num">0;                class=class="str">"cmt">//--- Set tester balance limit
m_params.debug_mode = class="kw">false;                    class=class="str">"cmt">//--- Disable debug mode

m_candle_shift = m_params.trade_anytime ? class="num">0 : class="num">1; class=class="str">"cmt">//--- Set candle shift based on trade mode
m_trade.SetExpertMagicNumber(m_params.magic);   class=class="str">"cmt">//--- Set magic number for trade object

PrepareSymbolsList();                           class=class="str">"cmt">//--- Prepare symbol list
for(class="type">int i = class="num">0; i < m_array_size; i++) {         class=class="str">"cmt">//--- Iterate through symbols
   if(!InitializeIndicators(i)) {               class=class="str">"cmt">//--- Initialize indicators
      class="kw">return class="kw">false;                             class=class="str">"cmt">//--- Return failure on error
   }
}
PrintMessage("Current Spread on " + Symbol() + ": " + class=class="str">"cmt">//--- Log current spread
   IntegerToString((class="type">int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD)));
class="kw">return true;                                    class=class="str">"cmt">//--- Return success
}

class="type">void OnTick() {                                 class=class="str">"cmt">//--- Define tick handling method
   class="type">class="kw">datetime new_day = iTime(Symbol(), PERIOD_D1, class="num">0); class=class="str">"cmt">//--- Get current day timestamp
   m_is_new_day = (m_last_day != new_day);      class=class="str">"cmt">//--- Check for new day
   if(m_is_new_day) {                           class=class="str">"cmt">//--- Handle new day

◍ 多品种信号循环的收口写法

这段逻辑跑在 EA 的 OnTick 尾部,负责把前面算好的 CCI、AO 数据落到具体品种上。外层 for 循环从 i=0 扫到 m_array_size-1,也就是你监控的品种总数——如果你在配置里挂了 10 个货币对,这里一跳价就轮询 10 遍,CPU 占用随品种数线性走。 new_time 用 iTime 取信号周期 0 号 K 线的开盘时间,只有跟 m_symbols[i].last_bar_time 不同才置 is_new_bar。trade_anyime 关掉时,信号只在新柱触发,避免一根柱内重复发单;实盘里黄金 M5 一根柱平均 5~8 跳报价,不锁新柱会刷出一堆同方向市价单。 ask/bid/point/spread 全用 SymbolInfoDouble 现取,spread 却用了 Symbol() 而非 m_symbols[i].name——这是隐性 bug,多品种下 spread 永远读的是当前图表的品种,跨品种点差判断会失真。 buy_signal 要求 CCI 上穿买入阈值且 AO 前一柱低于当前柱,sell_signal 反之。注意 m_candle_shift+1 是前一柱、m_candle_shift 是当前柱,偏移写错就变成用未来柱验证过去,回测会虚高。外汇和贵金属杠杆高,这类信号只是概率倾向,真跑之前先在 MT5 策略测试器用 2023 全年数据过一遍。

MQL5 / C++
m_last_day = new_day;                 class=class="str">"cmt">//--- Update last day
   }

   for(class="type">int i = class="num">0; i < m_array_size; i++) {      class=class="str">"cmt">//--- Iterate through symbols
      class="type">bool is_new_bar = class="kw">false;                  class=class="str">"cmt">//--- Initialize new bar flag
      class="type">bool buy_signal = class="kw">false, sell_signal = class="kw">false; class=class="str">"cmt">//--- Initialize signal flags
      class="type">bool buy_trend = class="kw">false, sell_trend = class="kw">false; class=class="str">"cmt">//--- Initialize trend flags
      
      class="type">class="kw">datetime new_time = iTime(m_symbols[i].name, m_params.tf_signal, class="num">0); class=class="str">"cmt">//--- Get current bar time
      if(!m_params.trade_anytime && m_symbols[i].last_bar_time != new_time) { class=class="str">"cmt">//--- Check for new bar
         is_new_bar = true;                     class=class="str">"cmt">//--- Set new bar flag
         m_symbols[i].last_bar_time = new_time; class=class="str">"cmt">//--- Update last bar time
      }
      
      if(!UpdateIndicatorData(i)) class="kw">continue; class=class="str">"cmt">//--- Update indicators, skip on failure
      
      class="type">class="kw">double ask = SymbolInfoDouble(m_symbols[i].name, SYMBOL_ASK); class=class="str">"cmt">//--- Get ask price
      class="type">class="kw">double bid = SymbolInfoDouble(m_symbols[i].name, SYMBOL_BID); class=class="str">"cmt">//--- Get bid price
      class="type">class="kw">double point = SymbolInfoDouble(m_symbols[i].name, SYMBOL_POINT); class=class="str">"cmt">//--- Get point value
      class="type">long spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); class=class="str">"cmt">//--- Get current spread
      
      class="type">int total_orders = CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_BUY) + class=class="str">"cmt">//--- Count buy orders
                         CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_SELL); class=class="str">"cmt">//--- Count sell orders
      
      class=class="str">"cmt">// Generate signals
      buy_signal = m_symbols[i].cci_signal_data[m_candle_shift+class="num">1] < m_params.cci_signal_buy_value && class=class="str">"cmt">//--- Check CCI buy signal condition
                   m_symbols[i].cci_signal_data[m_candle_shift] > m_params.cci_signal_buy_value && class=class="str">"cmt">//--- Confirm CCI buy signal
                   m_symbols[i].ao_signal_data[m_candle_shift+class="num">1] < m_symbols[i].ao_signal_data[m_candle_shift]; class=class="str">"cmt">//--- Confirm AO buy signal
                   
      sell_signal = m_symbols[i].cci_signal_data[m_candle_shift+class="num">1] > m_params.cci_signal_sell_value && class=class="str">"cmt">//--- Check CCI sell signal condition
                    m_symbols[i].cci_signal_data[m_candle_shift] < m_params.cci_signal_sell_value && class=class="str">"cmt">//--- Confirm CCI sell signal
                    m_symbols[i].ao_signal_data[m_candle_shift+class="num">1] > m_symbols[i].ao_signal_data[m_candle_shift]; class=class="str">"cmt">//--- Confirm AO sell signal
      
      buy_trend = m_symbols[i].cci_trend_data[m_candle_shift+class="num">1] < m_symbols[i].cci_signal_data[m_candle_shift] && class=class="str">"cmt">//--- Check CCI trend buy condition
让小布替你跑这套多品种巡检
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 CCI 与 AO 的双周期状态,不必自己挨个切图核对信号。

常见问题

在交易执行前用 PositionSelect 检查该品种是否已有未平仓订单,有则跳过新信号,这是多品种系统的基本要求。
本篇采用 M5 生成信号、H1 确认趋势,短周期抓反转、长周期滤噪音,具体周期可按品种波动率微调。
小布目前提供指标双周期状态可视化与信号提示,完整 EA 逻辑需在 MetaEditor 按本篇类结构自行部署,小布负责替你做盘口巡检。
不同货币对点差差异大,不检查点差可能在流动性差的品种上以过高成本成交,侵蚀策略期望收益。
优先看 CCI 阈值与止损倍数,这两类参数对反转策略的胜率和回撤影响最直接,再考虑品种列表筛选。