MQL5:在 MetaTrader 5 中分析和处理商品期货交易委员会 (CFTC) 报告·进阶篇
(2/3)· 跳过中间转换,用单一指标吃透三种持仓报告,老 MQL4 手也能平滑迁移
◍ 指标入口与净头寸数据流的写法
这段 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 数据滞后,单边行情中信号可能失效,属高风险参考。
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 }; // 四种数据口径:净买入、净买入占比、威廉姆斯指数、未平仓合约
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 就得照这个格式补一行,把经纪商符号翻成对应金属代码。
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 只是概率参考,别当方向保证。
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 周线跑通下面这段,再换其他品种验证。
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); }