MQL5:在 MetaTrader 5 中分析和处理商品期货交易委员会 (CFTC) 报告·进阶篇
📊

MQL5:在 MetaTrader 5 中分析和处理商品期货交易委员会 (CFTC) 报告·进阶篇

(2/3)· 跳过中间转换,用单一指标吃透三种持仓报告,老 MQL4 手也能平滑迁移

含代码示例 第 2/3 篇
很多人还在手动下载 CFTC 文件再导表,其实 MetaTrader 5 里能写指标直接读原始 zip。旧式报告、CIT、DCOT 混着看时,最容易漏掉指数型交易员的套期保值信号。

◍ 指标入口与净头寸数据流的写法

这段 OnCalculate 的形参列表把 MT5 每根 K 线的 OHLC、Tick 量、真实成交量和点差以引用数组形式传进来,供后续 COT 类指标直接读取,不需要再调用 CopySeries。 pos=rates_total-prev_calculated-1 算出未处理的最旧一根索引;当首次加载 pos>=rates_total-1 时减一,避免越界。随后把 Time、两个缓冲区设为序列数组(最新在 [0]),for 循环从 pos 倒序填值。 Report.At(Time[i], type_trader, type_data) 是按时间从 COT 报告取原始字段,写入 BufferCalculations;BufferData 则由 CalcData 做二次加工。若请求的是 netlongsrate 或 willams_index,代码先把 passed 归一成 netlongs,保证取数口径一致。 CalcData 里净买入率 = 净买入 / 未平仓合约(openinterest),威廉指数走 CalcIndex 分支,其余情况原样返回。开 MT5 把这段贴进自定义指标,改 type_trader 参数就能切换交易商持仓视角,外汇与贵金属品种均适用,但 COT 数据滞后,单边行情中信号可能失效,属高风险参考。

MQL5 / C++
const class="type">class="kw">double &High[],
const class="type">class="kw">double &Low[],
const class="type">class="kw">double &Close[],
const class="type">long &TickVolume[],
const class="type">long &Volume[],
const class="type">int &Spread[]){
class="type">int pos=rates_total-prev_calculated-class="num">1;
if( pos >= rates_total-class="num">1 )pos--;
ArraySetAsSeries(Time, true);
ArraySetAsSeries(BufferData, true);
ArraySetAsSeries(BufferCalculations, true);
for(class="type">int i=pos;i>=class="num">0;i--)
{
   cot_type_data passed = type_data;
   if(type_data==netlongs || type_data==netlongsrate || type_data==willams_index)
{
   class=class="str">"cmt">// 数据类型:净买入,净买入率,威廉指数) 返回净买入
class=class="str">"cmt">// 进一步的计算都基于这些数据。
     passed = netlongs;

}
   class="type">class="kw">double res = Report.At(Time[i], type_trader, type_data ); class=class="str">"cmt">//从报告中获取数据
   BufferCalculations[i] = res;
   BufferData[i]         = CalcData( type_data, i, Time );

}
BufferData[i]         = CalcData( type_data, i, Time );
class="type">class="kw">double CalcData( cot_type_data type_data, class="type">int i, const class="type">class="kw">datetime& Time[] )
{
  class=class="str">"cmt">// 未平仓合约中的净买入比率
  if( type_data == netlongsrate ) class="kw">return( BufferCalculations[ i ] / Report.At( Time[i], type_trader, openinterest ));
  class=class="str">"cmt">// 威廉指数
  if( type_data == willams_index )class="kw">return( CalcIndex( i, Time, BufferCalculations ) );
  class="kw">return(BufferCalculations[i]);
}

「把 CFTC 原始报告摆进 MT5 文件树」

CFTC 每年单独发布一份历史持仓报告压缩包,文件名里直接带年号。比如 deacot2010.zip 就是 2010 年全量数据,deacot2009.zip 对应 2009 年,链接规律固定可推。 手动做法要把每一年解压到客户端安装目录下的 \MQL5\files\ 里,且每年建一个 deacotXXXX 文件夹(XXXX 是年号),最终形成按年隔离的目录结构,EA 后续才能按路径读取。 这套下载+解压+建目录的活可以交给 Cotdownloader 脚本自动跑。它内核基于 WININET_TEST 做下载,用 CProgressBar 类画进度,外部调用走 Windows API;挂到任意图表就开工,进度条和 EA 选项卡文本双通道报状态,不用人盯。 外汇与贵金属属高杠杆品种,COT 数据只反映机构持仓倾向,不能单独作为开仓依据,需结合价格行为验证。

给 COT 指标挂上图表的实操要点

把 COT 指标拖到交易品种图表后,先确认输入参数填对、COT 原始文件已下载并解压,指标才会在独立子窗口里画出来。外汇和贵金属属于高杠杆品种,COT 持仓只是情绪侧参考,信号出现不代表方向必然兑现。 MT5 现在允许用中文注释替掉生硬的变量名和枚举值,读参数不再靠猜。声明 input 或 enum 时顺手写一句 // 说明,下次调参一眼就能看懂。 如果加载失败,别反复拖图表——直接看底部「工具箱」的「EA 交易程序」标签页,报错会列在那里,多半是文件路径或枚举值写错。 下面这段是指标头部的参数声明,逐行拆一下方便你直接抄: input cot_type_traders type_trader = noncommercial; // 交易者类型,默认取非商业持仓(基金/机构) input cot_type_data type_data = netlongs; // 指标类型,默认画净买入量 input cot_type_report type_report = COT; // 报告类型,默认标准 COT 周报 enum cot_type_data { netlongs, netlongsrate, willams_index, openinterest }; // 四种数据口径:净买入、净买入占比、威廉姆斯指数、未平仓合约

MQL5 / C++
class="kw">input cot_type_traders type_trader = noncommercial;  class=class="str">"cmt">//交易者类型
class="kw">input cot_type_data    type_data    = netlongs;      class=class="str">"cmt">//指标类型
class="kw">input cot_type_report  type_report = COT;            class=class="str">"cmt">//报告的类型
enum cot_type_data    class=class="str">"cmt">// 数据类型
{  
  netlongs,            class=class="str">"cmt">// 净买入
  netlongsrate,        class=class="str">"cmt">// 未平仓合约中的净买入比率
  willams_index,       class=class="str">"cmt">// Williams指数
  openinterest         class=class="str">"cmt">// 未平仓合约
};

◍ 把 COT 品种映射接到你自己的经纪商

这个指标不是拿来即用的完整品,它只是演示怎么拿到结果的样本,真正的扩展得自己写几行。比如经纪商自定义的 COT 报告品种映射,原文没做可视化模块,而是直接塞进 DefineIdSymbol 函数里,改代码才能适配。 原文示例跑在 Metaquotes-Demo 与 Alpari-Demo 上,DCOT 报告里只挂了两个贵金属:XAUUSD(黄金)和 XAGUSD(白银)。外汇与贵金属杠杆高、波动猛,映射错了可能读到完全不相干的持仓数据。 CIT 报告目前不支持,因为那两个 demo 服务器上没有对应金融工具;等你有品种了,取消注释加改几行就能开。下面这段是函数开头,看它怎么把终端符号转成 CFTC 代码: bool CCFTCReport::DefineIdSymbol() { idsymbol="";

if(terminalsymbol=="USDLFX"terminalsymbol=="DX_CONT"StringFind(terminalsymbol,"DX")==0)idsymbol="098662";

//U.S. 美元指数 - 美国CE期货

if( terminalsymbol == "FC_CONT"StringFind( terminalsymbol, "FC")== 0)idsymbol = "061641"; //肉牛
if( terminalsymbol == "LC_CONT"StringFind( terminalsymbol, "LC")== 0)idsymbol = "057642"; //活牛
if( terminalsymbol == "QM_CONT"StringFind( terminalsymbol, "QM")== 0)idsymbol = "067651";

//原油,轻质原油 - 纽约商品交易所 逐行拆:第1行声明函数返回布尔,名为 DefineIdSymbol;第3行先把 idsymbol 清空;第4行判断终端符号是美元指数相关时,把 CFTC 代码 098662 赋进去;第6行匹配肉牛 FC 前缀给 061641;第7行活牛 LC 给 057642;第8行轻质原油 QM 给 067651。你要接 XAUUSD 就得照这个格式补一行,把经纪商符号翻成对应金属代码。

MQL5 / C++
class="type">bool CCFTCReport::DefineIdSymbol()
  {
  idsymbol="";
  if(terminalsymbol=="USDLFX" || terminalsymbol=="DX_CONT" || StringFind(terminalsymbol,"DX")==class="num">0)idsymbol="class="num">098662";
  class=class="str">"cmt">//U.S. 美元指数 - 美国CE期货
  if( terminalsymbol == "FC_CONT" || StringFind( terminalsymbol, "FC")== class="num">0)idsymbol = "class="num">061641";  class=class="str">"cmt">//肉牛 
  if( terminalsymbol == "LC_CONT" || StringFind( terminalsymbol, "LC")== class="num">0)idsymbol = "class="num">057642";  class=class="str">"cmt">//活牛 
  if( terminalsymbol == "QM_CONT" || StringFind( terminalsymbol, "QM")== class="num">0)idsymbol = "class="num">067651";
  class=class="str">"cmt">//原油,轻质原油 - 纽约商品交易所

「用 iCustom 把 COT 持仓搬进 MT5 直方图」

MQL5 的 iCustom 函数能直接复用自定义指标,不必把 COT 报告画成图 3 那种静态图。想做直观分析,直方图是顺手的一种:用不同颜色区分商业与非商业群组的持仓方向。 我们实际要做的,是建一个 COTnet 指标,把 COT 报告里的商业、非商业持仓画成双色直方图,并且让用户能切数据类型:净长仓、净长仓比率或威廉指标。用 MQL5 向导生成绘图骨架,能省掉大量样板代码。 以非商业持仓为例,首次调 iCustom 时传这些参数:NULL 代表当前品种、0 代表当前周期、"cot" 是指标文件名(不带扩展名)、td 设 noncommercial、type_data 由用户输入、tr 设 COT。函数返回一个句柄,后面取数全靠它。 下面这段是向导产出的指标核心代码,含输入变量、缓冲区和 OnInit 里的双句柄创建: 别把向导当黑盒 代码里 handlecomm 和 handlenoncomm 是两个独立 iCustom 句柄,分别绑商业和非商业。若只建一个句柄又想画两组,缓冲区会互相覆盖。 OnCalculate 里先用 BarsCalculated 确认两个句柄都算完,再算 to_copy 并分别 copycotbuf 进 CommercialBuffer / NoncommercialBuffer。任一组取数失败就 Print 报错并 return(0),避免脏数据上图。 同样套路也能写进脚本。我们做过一个 cotsignals 脚本,在 Alpari-Demo 上跑 150 周柱(约 3 年)历史,测三种信号预测周烛形颜色的命中率:非商业净长仓符号 0.54、净长仓量变化 0.50、威廉超买超卖区 0.48。净长仓符号略优于随机,威廉区域反而低于随机,说明那种用法在这周期上不合理。外汇和贵金属属高风险,COT 只是概率参考,别当方向保证。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots  class="num">2
class=class="str">"cmt">//---- 绘制非商业的
class="macro">#class="kw">property indicator_label1  "Noncommercial"
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  Blue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">5
class=class="str">"cmt">//---- 绘制商业的
class="macro">#class="kw">property indicator_label2  "Commercial"
class="macro">#class="kw">property indicator_type2   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color2  Red
class="macro">#class="kw">property indicator_style2  STYLE_SOLID
class="macro">#class="kw">property indicator_width2  class="num">5
class=class="str">"cmt">//--- 指标缓存
class="macro">#include <cot.mqh>
class="kw">input cot_type_data    type_data   = netlongs;  class=class="str">"cmt">// 指标类型
class="type">class="kw">double        NoncommercialBuffer[];
class="type">class="kw">double        CommercialBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始化函数                                      | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int handlecomm, handlenoncomm;
class="type">int OnInit()
{
   SetIndexBuffer(class="num">0,NoncommercialBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,CommercialBuffer,INDICATOR_DATA);
   cot_type_traders td = commercial;
   cot_type_report   tr = COT;
   handlecomm = iCustom(NULL, class="num">0, "cot", td, type_data, tr );
   td = noncommercial;
   handlenoncomm = iCustom(NULL, class="num">0, "cot", td, type_data, tr );
   class="kw">return(class="num">0);
}
  if(BarsCalculated(handlecomm)<rates_total || BarsCalculated(handlenoncomm)<rates_total) class="kw">return(class="num">0);
  class="type">int to_copy = clctocopy(rates_total, prev_calculated );
  if( !copycotbuf(handlecomm, CommercialBuffer, to_copy ) )
{
    Print("Error in data of commercial traders");
    class="kw">return( class="num">0 );
  
}
  if( !copycotbuf(handlenoncomm, NoncommercialBuffer, to_copy ) )
{
    Print("Error in data of non-commercial traders");

用 COT 净头寸回测方向胜率

把 COT 报告里的非商业交易者净买入和威廉指数拉进 MT5,按周线逐根比对 K 线收开方向,就能算出某类统计信号的历史正确概率。代码里对 USDCHF、USDCAD、USDJPY 做了符号翻转,因为这几个货币对美元在分子,净头寸方向与价格涨跌天然相反。 回测循环里先统计有信号的根数 cntsignals,再数出 COT 方向与蜡烛颜色同向的 cntsucsess,最后概率 = cntsucsess / cntsignals。例如某品种跑完打印出 correct forecast probability=0.62,代表这类信号周线级别顺势的概率约 62%,不是确定性结论。 外汇与贵金属受杠杆和突发事件影响,COT 概率仅反映历史样本倾向,实盘仍可能连续失效。建议先拿 EURUSD 周线跑通下面这段,再换其他品种验证。

MQL5 / C++
if (!LoadDataFromCOTIndicator(symbol, PERIOD_W1, noncommercial, netlongs, COT, netlongsspeculantsdata, bars))
   class="kw">return(false);
class=class="str">"cmt">//获取到相关数据 - 投机者的净买入
if (!LoadDataFromCOTIndicator(symbol, PERIOD_W1, noncommercial, willams_index, COT, willamsindexspeculantsdata, bars))
   class="kw">return(false);
class=class="str">"cmt">//获取到数据 - 投机者的威廉指数
for(class="type">int istat = class="num">0; istat < ArraySize(liststatistic); istat++)
  {
   class="type">int cntsignals = class="num">0;
   class="type">int cntsucsess = class="num">0;
   for(class="type">int i=bars-class="num">1; i>=class="num">0; i--)
     {
      class="type">class="kw">double colorcandle=iClose(symbol,PERIOD_W1,i)-iOpen(symbol,PERIOD_W1,i);
      if(symbol=="USDCHF" || symbol=="USDCAD" || symbol=="USDJPY") colorcandle=-colorcandle;
      class="type">class="kw">double cotdirection=GetCotSignalDirection(i,liststatistic[istat]);
      if(cotdirection==class="num">0)class="kw">continue;                      class=class="str">"cmt">//无信号
      cntsignals++;
      if(cotdirection*colorcandle>class="num">0) cntsucsess++; class=class="str">"cmt">//颜色和方向一致

}
   if(cntsignals!=class="num">0) probably[istat]=class="num">1.*cntsucsess/cntsignals;
   Print("Calculation complete for ",symbol,GetStringTypeStatistic(liststatistic[istat]),
         "        the correct forecast probability=",DoubleToString(probably[istat],class="num">2));
   Print("Total signals:",cntsignals,"success:",cntsucsess);

}
把报告解析交给小布盯盘
这些 COT 指标的字段映射和品种页叠加,小布盯盘的 AIGC 已内置,打开对应品种页即可看到异动分组,你只管判读仓位倾斜。

常见问题

旧式用 deacot2010.zip,CIT 用 dea_cit_txt_2010.zip,DCOT 用 fut_disagg_txt_2010.zip,指标需按类型切换解包路径。
把指标设为数据源模式且仅在 new bar 触发读取,脚本层用全局缓存持有上次句柄,减少重复文件 IO。
仅在 CIT 补充商品指数报告出现,自 2007 年起发布,属于套期保值者分类下的独立组别。
可以,品种页的 AIGC 模块会按周度报告计算分位并提示概率性拥挤,但不构成方向结论,外汇贵金属仍为高风险品类。
MQL5 不兼容 MQL4 且全面面向对象,原脚本需重写成 CI 类指标,文件读取改用 CFile 而非老旧字符串解析。