一步步学习如何利用公允价值缺口(FVG)或市场不平衡性来交易的策略:一种聪明资金的交易方法·综合运用
(3/3)·当你已懂FVG定义与识别,却卡在策略落地与回测验证,这篇把闭环补齐
◍ FVG 对象的登记与存续判定
这段逻辑解决两件事:新出现的公平价值缺口(FVG)如何落进全局数组,以及已有图形对象是否还活在该品种的 K 线结构里。外汇与贵金属市场跳空频繁,FVG 失效速度可能快于预期,属于高风险识别动作,务必在 MT5 实盘前用策略测试器跑一遍。 先说新建分支:当 ObjectFind 在当前图表找不到同名矩形,说明这是一个未被记录的 FVG。代码先调用 CreateRec 画出缺口区域,随后三次 ArrayResize 把 totalFVGs、barTIMEs、signalFVGs 各扩容 1,并把新名字、触发时间、信号标记 false 写进末尾。Print 输出的 Old/New ArraySize 差值恒为 1,可直接在终端日志核对扩容是否生效。 存续检查放在独立 for 循环,从数组末尾倒序扫到 0。对每个对象取出名称、上下边界与颜色,再向内回溯 FVG_Rec_Ext_Bars 根 K 线,只要某根 bar 的高或低碰到 fvgLow 就置 fvgExist=true 并 break。这里只判边界接触,不判实体填充,回测时若发现缺口被毛刺刺穿却仍保留,大概率是这个等号逻辑在起作用。 最后两行取规范化后的 Ask、Bid,为后续信号触发准备实时报价。注意 NormalizeDouble 用了 _Digits,黄金 XAUUSD 的 _Digits 通常是 2 或 3,EURUSD 多为 5,复制代码前先确认品种报价精度,否则边界比对会偏一格。
class="type">color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN; if (ObjectFind(class="num">0,fvgNAME) < class="num">0){ CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr); Print("Old ArraySize = ",ArraySize(totalFVGs)); ArrayResize(totalFVGs,ArraySize(totalFVGs)+class="num">1); ArrayResize(barTIMEs,ArraySize(barTIMEs)+class="num">1); ArrayResize(signalFVGs,ArraySize(signalFVGs)+class="num">1); Print("New ArraySize = ",ArraySize(totalFVGs)); totalFVGs[ArraySize(totalFVGs)-class="num">1] = fvgNAME; barTIMEs[ArraySize(barTIMEs)-class="num">1] = time1; signalFVGs[ArraySize(signalFVGs)-class="num">1] = false; ArrayPrint(totalFVGs); ArrayPrint(barTIMEs); ArrayPrint(signalFVGs); } } } for (class="type">int j=ArraySize(totalFVGs)-class="num">1; j>=class="num">0; j--){ class="type">bool fvgExist = false; class="type">class="kw">string objName = totalFVGs[j]; class="type">class="kw">string fvgNAME = ObjectGetString(class="num">0,objName,OBJPROP_NAME); class="type">class="kw">double fvgLow = ObjectGetDouble(class="num">0,fvgNAME,OBJPROP_PRICE,class="num">0); class="type">class="kw">double fvgHigh = ObjectGetDouble(class="num">0,fvgNAME,OBJPROP_PRICE,class="num">1); class="type">color fvgColor = (class="type">color)ObjectGetInteger(class="num">0,fvgNAME,OBJPROP_COLOR); for (class="type">int k=class="num">1; k<=FVG_Rec_Ext_Bars; k++){ class="type">class="kw">double barLow = iLow(_Symbol,_Period,k); class="type">class="kw">double barHigh = iHigh(_Symbol,_Period,k); if (barHigh == fvgLow || barLow == fvgLow){ class=class="str">"cmt">//Print("Found: ",fvgNAME," @ bar ",k); fvgExist = true; break; } } class=class="str">"cmt">//Print("Existence of ",fvgNAME," = ",fvgExist); class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
FVG 被吃后的双向挂单与数组清理
这段逻辑处理公允值缺口(FVG)失效后的交易触发与内存回收。向下缺口(CLR_DOWN)时,若 Bid 已站上 fvgHigh 且未发过信号,则打印 SELL 信号,并以 Ask 加 10 倍缺口跨度作止损、fvgLow 作止盈尝试卖出;向上缺口反之用 Ask 跌破 fvgLow 触发 BUY。 止损距离用 NormalizeDouble 按 _Digits 对齐:((fvgHigh-fvgLow)/_Point)*10*_Point,即缺口高度的 10 倍点数。外汇与贵金属杠杆高,这类固定倍数止损在跳空时可能直接穿损,实盘前应在 MT5 策略测试器用 0.01 手跑一轮观察最大回撤。 下单前依次过 Check2_Margin(保证金)、Check3_VolumeLimit(限量)、Check4_TradeLevels(层级合规),三者全过才 obj_Trade.Sell/Buy,并把 signalFVGs[j] 置 true 防重复触发。 当 fvgExist 为 false,说明缺口已被完全回补,代码用 ArrayRemove 把 totalFVGs、barTIMEs、signalFVGs 的 0 号元素同步删掉,打印剩余数组大小确认回收成功。注意三个数组必须同下标操作,否则后续 j 循环会错位。
if (fvgColor == CLR_DOWN && Bid > fvgHigh && !signalFVGs[j]){ Print("SELL SIGNAL For(",fvgNAME,") Now @ ",Bid); class="type">class="kw">double SL_sell = Ask + NormalizeDouble((((fvgHigh-fvgLow)/_Point)*class="num">10)*_Point,_Digits); class="type">class="kw">double trade_lots = Check1_ValidateVolume_Lots(class="num">0.01); if (Check2_Margin(ORDER_TYPE_SELL,trade_lots) && Check3_VolumeLimit(trade_lots) && Check4_TradeLevels(POSITION_TYPE_SELL,SL_sell,fvgLow)){ obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_sell,fvgLow); signalFVGs[j] = true; } ArrayPrint(totalFVGs,_Digits," [< >] "); ArrayPrint(signalFVGs,_Digits," [< >] "); } else if (fvgColor == CLR_UP && Ask < fvgLow && !signalFVGs[j]){ Print("BUY SIGNAL For(",fvgNAME,") Now @ ",Ask); class="type">class="kw">double SL_buy = Bid - NormalizeDouble((((fvgHigh-fvgLow)/_Point)*class="num">10)*_Point,_Digits); class="type">class="kw">double trade_lots = Check1_ValidateVolume_Lots(class="num">0.01); if (Check2_Margin(ORDER_TYPE_BUY,trade_lots) && Check3_VolumeLimit(trade_lots) && Check4_TradeLevels(POSITION_TYPE_BUY,SL_buy,fvgHigh)){ obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_buy,fvgHigh); signalFVGs[j] = true; } ArrayPrint(totalFVGs,_Digits," [< >] "); ArrayPrint(signalFVGs,_Digits," [< >] "); } if (fvgExist == false){ class="type">bool removeName = ArrayRemove(totalFVGs,class="num">0,class="num">1); class="type">bool removeTime = ArrayRemove(barTIMEs,class="num">0,class="num">1); class="type">bool removeSignal = ArrayRemove(signalFVGs,class="num">0,class="num">1); if (removeName && removeTime && removeSignal){ Print("Success removing the FVG DATA from the arrays. New Data as Below:"); Print("FVGs: ",ArraySize(totalFVGs)," TIMEs: ",ArraySize(barTIMEs), " SIGNALs: ",ArraySize(signalFVGs)); ArrayPrint(totalFVGs); ArrayPrint(barTIMEs);
「回测里看 FVG 信号数组的实际产出」
把 signalFVGs 数组用 ArrayPrint 打进日志后,在 MT5 策略测试器跑一轮,能直接看到每一根 K 线识别出的 FVG 结构编号与方向。 回测输出的余额/资产净值曲线只是表象,真正有含金量的是诊断数组本身:若某段行情 signalFVGs 持续为空,说明该周期 FVG 条件未被触发,策略在那段大概率是空仓。 建议你在自己的品种上(如 XAUUSD、EURUSD,均属高风险杠杆市场)复跑这段代码,把日志导出来数一下有效 FVG 信号占比,再决定是否调小实体阈值来提升触发频率。
ArrayPrint(signalFVGs);◍ 把 SMC 缺口策略真正跑起来之前
公允价值缺口策略的核心,是识别价格与内在价值之间的偏离并用统计方法量化,而不是凭肉眼猜影线。前面几节给出的 FVG_SMC_EA.mq5 附件约 21.23 KB,已经把数据分析、K线结构与 MQL5 自动化串成了一条可验证链路。 在 MT5 里加载该 EA 后,优先看时间序列与 K 线分析模块是否按预期标记不平衡区;若标记逻辑与你手动画的 FVG 区吻合度低,先调检测窗口参数而非直接上实盘。外汇与贵金属杠杆高,这类统计套利模型在历史样本上有效,不等于未来概率不变。 真要落地,就把它当基础框架:自己补优化与数据提取,别信任何「保证结果」的说法。开 MT5 跑一遍附件里的代码,比读十遍结论更有用。