走势延续模型 - 搜索图表和执行统计·综合运用
◍ 走势延续模型的底线识别条件
要做走势延续判定,先卡死最小条件集,不满足就直接排除。下跌和上涨两侧逻辑对称,但方向反着来:跌势里修正浪高点(C)必须低于后续极值高点(A),涨势里修正浪低点(C)必须高于后续极值低点(A)。 第二个硬条件是索引顺序。跌势中修正浪低点索引(B)要超过高点索引(C),涨势中修正浪高点索引(B)超过低点索引(C)——这保证了修正段结构在时间上不颠倒。 修正走势持续时间锁死在 2 到 6 根柱线(从 B 点算起)。少于 2 根可能是噪音,多于 6 根则倾向视为另一段行情而非同模型修正,外汇与贵金属市场跳空频繁,这一窗口需结合品种波动重看。 代码把两类条件收进两个 bool 变量,方便后续直接当开关调用。下跌分支用 High_Corr_wave_downtrend_base_tf_double 比 High_base_tf[UpperFractal_downtrend_base_tf] 小来做条件1,上涨分支对称处理。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">4. 描述模型识别条件 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 对于下跌趋势 class=class="str">"cmt">/*class="num">1. 修正浪高点(点 C)低于其后极值的高点(点 А)*/ class=class="str">"cmt">/*class="num">2. 修正浪低点索引(点 В)超过高点索引(点 С)*/ class=class="str">"cmt">/*class="num">3. 修正走势持续时间为 class="num">2 到 class="num">6 根柱线(从点 В 开始的柱线数)*/ class="type">bool Model_downtrend_base_tf=( class=class="str">"cmt">/*class="num">1.*/High_Corr_wave_downtrend_base_tf_double<High_base_tf[UpperFractal_downtrend_base_tf] && class=class="str">"cmt">/*class="num">2.*/Low_Corr_wave_downtrend_base_tf>High_Corr_wave_downtrend_base_tf && class=class="str">"cmt">/*class="num">3.*/Low_Corr_wave_downtrend_base_tf>=class="num">1 && Low_Corr_wave_downtrend_base_tf<=class="num">6 ); class=class="str">"cmt">//--- 对于上涨趋势 class=class="str">"cmt">/*class="num">1. 修正浪低点(点 C)高于其后的极值低点(点 А)*/ class=class="str">"cmt">/*class="num">2. 修正浪高点索引(点 В)超过低点索引(点 С)*/ class=class="str">"cmt">/*class="num">3. 修正走势持续时间为 class="num">2 到 class="num">6 根柱线(从点 В 开始的柱线数)*/ class="type">bool Model_uptrend_base_tf=( class=class="str">"cmt">/*class="num">1.*/Low_Corr_wave_uptrend_base_tf_double>Low_base_tf[LowerFractal_uptrend_base_tf] && class=class="str">"cmt">/*class="num">2.*/High_Corr_wave_uptrend_base_tf>Low_Corr_wave_uptrend_base_tf && class=class="str">"cmt">/*class="num">3.*/High_Corr_wave_uptrend_base_tf>=class="num">1 && High_Corr_wave_uptrend_base_tf<=class="num">6 ); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">4. 模型识别条件 (结束) | class=class="str">"cmt">//+------------------------------------------------------------------+
「EA 开仓前的三道闸门」
做延续模型的开仓逻辑,程序至少得卡三道检查。头两道管时效性:价格突破修正低点(点 B)后若再回开仓区,不能算有效信号;第三道管仓位唯一性,强制一个模型只挂一单,避免重复吃同一波。以 AUDJPY H4 为例,虚线框定的开仓区落在点 B 与点 C 之间,价格刺穿点 B 价位后不建议追,风险会明显抬升。 第一道最简单:卖模型要求 bid 大于等于修正浪低点 Low_base_tf[Low_Corr_wave_downtrend_base_tf],买模型反过来卡 bid 小于等于对应高点。第二道用 ArrayMinimum / ArrayMaximum 从 0 号柱扫到修正浪端点,若极值柱索引和修正端点一致,说明价格没越界,可以谈入场;不一致就是走势提前发动、进场已晚。 第三道防重复开仓,思路是顺当前图表已有持仓找最近极值柱(点 C 侧),把它时间和本模型点 C 时间比对,撞上了就禁开。外汇和贵金属杠杆高,这类模型失效时回撤可能很快,上 MT5 跑前先把点 B/C 的索引打印出来核对。
class=class="str">"cmt">//+------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.1 在开仓区域形成入场点控制 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------------+ class=class="str">"cmt">//--- 对于下跌趋势 class="type">bool First_downtrend_control_bool=(bid>=Low_base_tf[Low_Corr_wave_downtrend_base_tf]); class=class="str">"cmt">//--- 对于上涨趋势 class="type">bool First_uptrend_control_bool=(bid<=High_base_tf[High_Corr_wave_uptrend_base_tf]); class=class="str">"cmt">//+------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.1 在开仓区域形成入场点控制 (结束) | class=class="str">"cmt">//+------------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.2 价格回滚至开仓区域的控制 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------------------+ class=class="str">"cmt">//--- 对于下跌趋势 class=class="str">"cmt">//在 #class="num">0 柱线和修正走势的低点之间寻找价格最低的柱线 class="type">int Second_downtrend_control_int=ArrayMinimum(Low_base_tf,class="num">0,Low_Corr_wave_downtrend_base_tf+class="num">1); class=class="str">"cmt">//如果当前柱线的低位低于修正走势的低位 if(Low_base_tf_0[class="num">0]<Low_base_tf[Second_downtrend_control_int]) { Second_downtrend_control_int=class="num">0; class=class="str">"cmt">//this means the minimum is on bar class="num">0 } class=class="str">"cmt">//如果价格最低的柱线与修正走势的低点匹配,则这是相同的柱线 class=class="str">"cmt">//这意味着价格没有超越开仓区域 class="type">bool Second_downtrend_control_bool=(Second_downtrend_control_int==Low_Corr_wave_downtrend_base_tf); class=class="str">"cmt">//--- class=class="str">"cmt">//--- 对于上涨趋势 class=class="str">"cmt">//在 #class="num">0 柱线和修正走势的高点之间寻找价格最高的柱线 class="type">int Second_uptrend_control_int=ArrayMaximum(High_base_tf,class="num">0,High_Corr_wave_uptrend_base_tf+class="num">1); class=class="str">"cmt">//如果当前柱线的高位超越修正走势的高位 if(High_base_tf_0[class="num">0]>High_base_tf[Second_uptrend_control_int]) { Second_uptrend_control_int=class="num">0;class=class="str">"cmt">//this means maximum on bar class="num">0 } class=class="str">"cmt">//如果价格最高的柱线与修正走势的高点匹配,则这是相同的柱线 class=class="str">"cmt">//这意味着价格没有超越开仓区域 class="type">bool Second_uptrend_control_bool=(Second_uptrend_control_int==High_Corr_wave_uptrend_base_tf); class=class="str">"cmt">//+-----------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.2 价格回滚至开仓区域的控制 (结束) | class=class="str">"cmt">//+-----------------------------------------------------------------------------+ class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.3.class="num">1 对于卖出 (开始) | class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//--- 声明变量 class="type">int Bar_sell_base_tf,High_Corr_wave_downtrend_base_tf_sell; class="type">bool Third_downtrend_control_bool=class="kw">false; class=class="str">"cmt">//--- 迭代全部持仓 if(PositionsTotal()>class="num">0) { for(i=class="num">0;i<=PositionsTotal();i++) { if(PositionGetTicket(i)) { class=class="str">"cmt">//--- 定义仓位品种,时间和类型 P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL));
定位空单修正高点的柱线逻辑
在 MT5 的持仓遍历里,先通过 PositionGetInteger 抓出持仓品种与类型,只处理当前图表符号且 P_type==1(卖出持仓)的分支。接着用 iBarShift 把开仓时间映射到基础周期柱线序号 Bar_sell_base_tf,这是后续找修正走势高点的锚点。 若 Bar_sell_base_tf==0,说明空单开在当下这根基础周期 K 线。此时直接比 High_base_tf_0[0] 与后两根的高点:若同时大于 High_base_tf[1] 和 High_base_tf[2],当前柱即判为修正高点;否则进入 for 循环从 n=0 扫到 bars_base_tf,遇到 High_base_tf[n] 大于相邻两根即 break,把序号赋给 High_Corr_wave_downtrend_base_tf_sell。 控制条件 Third_downtrend_control_bool 只做一件事:校验刚才找到的高点柱时间 Time_base_tf[...] 是否等于预设的 High_Corr_wave_downtrend_base_tf_time。外汇与贵金属杠杆高,这种基于柱线极值的判定在跳空或点差扩大时可能失效,建议你在 MT5 策略测试器用 EURUSD 15 分钟数据跑一遍观察命中率。
P_type=class="type">int(PositionGetInteger(POSITION_TYPE)); P_opentime=class="type">int(PositionGetInteger(POSITION_TIME)); class=class="str">"cmt">//--- 如果持仓品种与当前图表匹配,且交易类型为“卖出” if(P_symbol==Symbol() && P_type==class="num">1) { class=class="str">"cmt">//--- 寻找开仓所在的柱线 Bar_sell_base_tf=iBarShift(Symbol(),base_tf,P_opentime); class=class="str">"cmt">//--- 从中寻找修正走势高点 class=class="str">"cmt">//如果在当前柱线上有开仓, if(Bar_sell_base_tf==class="num">0) { class=class="str">"cmt">//且当前柱线是一个极值 if(High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf+class="num">1] && High_base_tf_0[Bar_sell_base_tf]>High_base_tf[Bar_sell_base_tf+class="num">2]) { High_Corr_wave_downtrend_base_tf_sell=Bar_sell_base_tf;class=class="str">"cmt">//修正走势高点等于当前柱线 } else { class=class="str">"cmt">//如果当前柱线不是极值,则启动循环搜索极值 for(n=Bar_sell_base_tf; n<(bars_base_tf);n++) { if(High_base_tf[n]>High_base_tf[n+class="num">1] && High_base_tf[n]>High_base_tf[n+class="num">2])class=class="str">"cmt">//如果发现极值 class="kw">break;class=class="str">"cmt">//循环中断 } High_Corr_wave_downtrend_base_tf_sell=n; } class=class="str">"cmt">//--- 描述控制条件 Third_downtrend_control_bool=( class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势高点的时间 匹配当前修正走势高点的时间*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time ); } class=class="str">"cmt">//--- 如果开仓并非在当前柱线上
◍ 从持仓回推修正波极点
在下跌持仓的管理逻辑里,先判断基准周期开仓柱线编号 Bar_sell_base_tf 不为 0 且不为 1000(1000 通常用作“未初始化”占位),再从该柱向后扫描,找第一个满足 High[n] 同时大于前两根 High[n+1]、High[n+2] 的局部高点,把索引写进 High_Corr_wave_downtrend_base_tf_sell 并 break。这一步只认“右侧两根更低”的裸价格结构,不依赖任何指标。 随后用 Time_base_tf[该索引] 是否等于已记录的修正高点时间 High_Corr_wave_downtrend_base_tf_time,来置位 Third_downtrend_control_bool。若两者时间一致,说明自开仓以来的这段修正反弹高点仍在“未被后续刷新”的状态,持仓的 downtrend 控制条件可能延续。 买入侧对称处理:声明 Bar_buy_base_tf 与 Low_Corr_wave_uptrend_base_tf_buy,Third_uptrend_control_bool 初值 false。遍历 PositionsTotal() 返回的持仓,用 PositionGetTicket(i) 取票,再读 SYMBOL / TYPE / TIME;当 P_symbol==Symbol() 且 P_type==0(买入)时,才进入“寻找开仓所在柱线”的分支。外汇与贵金属杠杆品种跳空频繁,这种靠柱线高低的判定在重大数据窗可能失效,实盘前请在 MT5 策略测试器用真实点差回放验证。
if(Bar_sell_base_tf!=class="num">0 && Bar_sell_base_tf!=class="num">1000) { class=class="str">"cmt">//--- 启动循环检测极值柱线 for(n=Bar_sell_base_tf; n<(bars_base_tf);n++) { class=class="str">"cmt">//--- 如果找到极值 if(High_base_tf[n]>High_base_tf[n+class="num">1] && High_base_tf[n]>High_base_tf[n+class="num">2]) class="kw">break;class=class="str">"cmt">//循环中断 } High_Corr_wave_downtrend_base_tf_sell=n; } Third_downtrend_control_bool=( class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势高点的时间 匹配当前修正走势高点的时间*/Time_base_tf[High_Corr_wave_downtrend_base_tf_sell]==High_Corr_wave_downtrend_base_tf_time ); class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.3.class="num">1 对于卖出 (结束) | class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.3.class="num">2 对于买入 (开始) | class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//--- 声明变量 class="type">int Bar_buy_base_tf,Low_Corr_wave_uptrend_base_tf_buy; class="type">bool Third_uptrend_control_bool=class="kw">false; class=class="str">"cmt">//--- 迭代全部持仓 if(PositionsTotal()>class="num">0) { for(i=class="num">0;i<=PositionsTotal();i++) { if(PositionGetTicket(i)) { class=class="str">"cmt">//定义仓位品种,类型和时间 P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL)); P_type=class="type">int(PositionGetInteger(POSITION_TYPE)); P_opentime=class="type">int(PositionGetInteger(POSITION_TIME)); class=class="str">"cmt">//如果持仓品种与当前图表和买入交易类型一致 if(P_symbol==Symbol() && P_type==class="num">0) { class=class="str">"cmt">//寻找开仓所在的柱线
「定位多头修正低点的柱线索引」
在基础周期上确认买入信号后,需要把开仓时刻映射回基础周期的 K 线序号,才能继续找修正走势的低点。iBarShift 用开仓时间 P_opentime 反查当前品种、基础周期下的柱线偏移,得到 Bar_buy_base_tf。 若 Bar_buy_base_tf 为 0,说明开仓就发生在基础周期最新一根柱线上。此时先判断这根柱线的低点是否同时低于前两根:Low_base_tf_0[0] < Low_base_tf[1] 且 < Low_base_tf[2],满足则直接把 0 记为修正低点;不满足就进入 for 循环从 0 往后扫,直到某根 n 满足 Low_base_tf[n] 低于 n+1 与 n+2 才 break,把该 n 作为低点索引。 如果开仓不在最新柱(Bar_buy_base_tf 非 0 且非 1000 这种异常值),同样从 Bar_buy_base_tf 起循环检索后续柱线极值。控制变量 Third_uptrend_control_bool 只做一件事:比对已记录的修正低点时间 Low_Corr_wave_uptrend_base_tf_time 与 Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy] 是否相等,用来确认低点未被更新覆盖。 外汇与贵金属杠杆交易高风险,这套索引逻辑仅用于回测或多周期验证,实盘信号失效概率随周期切换显著上升。
Bar_buy_base_tf=iBarShift(Symbol(),base_tf,P_opentime); class=class="str">"cmt">//从中寻找修正走势低点 class=class="str">"cmt">//如果在当前柱线上有开仓, if(Bar_buy_base_tf==class="num">0) { class=class="str">"cmt">//且当前柱线是一个极值 if(Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf+class="num">1] && Low_base_tf_0[Bar_buy_base_tf]<Low_base_tf[Bar_buy_base_tf+class="num">2]) { Low_Corr_wave_uptrend_base_tf_buy=Bar_buy_base_tf; } else { class=class="str">"cmt">//如果当前柱线不是极值,则启动循环搜索极值 for(n=Bar_buy_base_tf; n<(bars_base_tf);n++) { if(Low_base_tf[n]<Low_base_tf[n+class="num">1] && Low_base_tf[n]<Low_base_tf[n+class="num">2])class=class="str">"cmt">//如果发现极值 class="kw">break;class=class="str">"cmt">//循环中断 } Low_Corr_wave_uptrend_base_tf_buy=n; } class=class="str">"cmt">//--- 描述控制条件 Third_uptrend_control_bool=( class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势低点的时间 匹配当前修正走势低点的时间*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time ); } class=class="str">"cmt">//--- 如果开仓并非在当前柱线上 if(Bar_buy_base_tf!=class="num">0 && Bar_buy_base_tf!=class="num">1000) { class=class="str">"cmt">//--- 启动循环检测极值柱线 for(n=Bar_buy_base_tf; n<(bars_base_tf);n++) { class=class="str">"cmt">//--- 如果找到极值
上升修正低点定位与第三条件锁定
在上升趋势持仓过程中,需要动态确认自开仓以来修正走势的最低点位置。代码通过逆向遍历基础周期低点数组 Low_base_tf,当发现某根 K 线的低点同时小于其后两根 K 线低点时,即判定该位置为修正波段的局部谷底,并用 break 中断循环。 具体看这段判断:若 Low_base_tf[n] < Low_base_tf[n+1] 且 < Low_base_tf[n+2],则 n 被记录为 Low_Corr_wave_uptrend_base_tf_buy,代表修正低点索引。该索引对应的时间 Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy] 会与外部传入的 Low_Corr_wave_uptrend_base_tf_time 比较,相等时 Third_uptrend_control_bool 才置真。 实盘意义在于:只有修正低点时间与记录时间吻合,第三道上升趋势控制条件才成立,过滤掉价格假破后低点漂移的干扰。外汇与贵金属波动剧烈,该条件仅降低误判概率,不保证持仓一定盈利。
if(Low_base_tf[n]<Low_base_tf[n+class="num">1] && Low_base_tf[n]<Low_base_tf[n+class="num">2]) class="kw">break;class=class="str">"cmt">//循环中断 } Low_Corr_wave_uptrend_base_tf_buy=n; } class=class="str">"cmt">//--- 描述控制条件 Third_uptrend_control_bool=( class=class="str">"cmt">/*class="num">1. 发现自开仓以来修正走势低点的时间 匹配当前修正走势低点的时间*/Time_base_tf[Low_Corr_wave_uptrend_base_tf_buy]==Low_Corr_wave_uptrend_base_tf_time ); } } } } class=class="str">"cmt">//+---------------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">5.3.class="num">2 对于买入 (结束) | class=class="str">"cmt">//+---------------------------------------------------------------------------+
◍ 用SAR翻转卡住操作周期入场点
入场信号只在操作周期 work_tf 上判定,这样做的直接好处是进场更及时,若趋势顺势启动,止损点数有可能压得更小。 核心读数是抛物线 SAR:当前柱的 SAR 值高于该柱高点、且前一根柱的 SAR 低于那根柱低点,就是空头发力的信号;反过来,前一根 SAR 在高点之上、当前 SAR 被低点击穿,才考虑买入。 下面这段布尔逻辑把上述规则直接落进 EA。注意它比较的是 #1 已完成柱与 #0 形成中柱,实盘里 #0 条件成立瞬间就触发,存在未来函数式的重绘风险,需用收盘价过滤或仅允许柱收尾后进场。 [CODE] //+------------------------------------------------------------------+
| // | 6. 描述市场准入条件 (开始) |
|---|
//+------------------------------------------------------------------+ //--- 对于卖出 bool PointSell_work_tf_bool=( /*1. #1 柱线低点超越 iSar[1]*/Low_work_tf[1]>Sar_array_work_tf[1] && /*2. #0 柱线高点低于 iSar[0]*/High_work_tf_0[0]<Sar_array_work_tf_0[0] ); //--- 对于买入 bool PointBuy_work_tf_bool=( /*1. #1 柱线高点低于 iSar*/High_work_tf[1]<Sar_array_work_tf[1] && /*2. #0 柱线低点超越 iSar[0]*/Low_work_tf_0[0]>Sar_array_work_tf_0[0] ); //+------------------------------------------------------------------+
| // | 6. 描述市场准入条件 (结束) |
|---|
//+------------------------------------------------------------------+ [/CODE] 逐行拆解:第 1 个条件 Low_work_tf[1]>Sar_array_work_tf[1] 表示前一根柱低点站上 SAR,说明上一根还是多头防守失败;第 2 个条件 High_work_tf_0[0]<Sar_array_work_tf_0[0] 表示当前柱高点被 SAR 压住,翻转确认。买入组完全镜像,High 与 Low 角色对调。外汇与贵金属波动剧烈,SAR 在震荡市会频繁假翻转,触发概率高但胜率可能偏低,务必先在 MT5 策略测试器跑历史数据验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">6. 描述市场准入条件 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 对于卖出 class="type">bool PointSell_work_tf_bool=( class=class="str">"cmt">/*class="num">1. #class="num">1 柱线低点超越 iSar[class="num">1]*/Low_work_tf[class="num">1]>Sar_array_work_tf[class="num">1] && class=class="str">"cmt">/*class="num">2. #class="num">0 柱线高点低于 iSar[class="num">0]*/High_work_tf_0[class="num">0]<Sar_array_work_tf_0[class="num">0] ); class=class="str">"cmt">//--- 对于买入 class="type">bool PointBuy_work_tf_bool=( class=class="str">"cmt">/*class="num">1. #class="num">1 柱线高点低于 iSar*/High_work_tf[class="num">1]<Sar_array_work_tf[class="num">1] && class=class="str">"cmt">/*class="num">2. #class="num">0 柱线低点超越 iSar[class="num">0]*/Low_work_tf_0[class="num">0]>Sar_array_work_tf_0[class="num">0] ); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">6. 描述市场准入条件 (结束) | class=class="str">"cmt">//+------------------------------------------------------------------+
「把散落条件收进一个开关变量」
前面几步分别算出了趋势模型、三层控制开关和 work_tf 上的入场信号,但它们各自独立。这一步要做的,是把它们用逻辑与拼成一个布尔量,让后续下单判断只看 OpenBuy / OpenSell 一个变量。 代码里卖出条件要求:基础周期 downtrend 模型成立、前两层下跌控制为真、第三层控制为假、且 work_tf 上出现 PointSell 信号。五个子条件全真,OpenSell 才置 true,任一不满足就开不了空。 买入对称:uptrend 模型、前两层上涨控制为真、第三层控制为假、PointBuy 信号为真,OpenBuy 才为真。注意第三层控制两边都是 ==false,意味着该层起的是“封锁”作用,触发即禁开。 在 MT5 里把这段直接贴进 EA,打印 OpenSell 和 OpenBuy 到日志,跑一周 EURUSD 的 M15,你能看到多数时间两者同为假——说明模型+三层过滤的合取很严,信号本身就稀。外汇和贵金属波动大、杠杆高,这种低频率更该配合仓位上限来控风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">7. 描述交易条件 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 对于卖出 class="type">bool OpenSell=( class=class="str">"cmt">/*class="num">1. 模型形成*/Model_downtrend_base_tf==true && class=class="str">"cmt">/*class="num">2. 控制 class="num">1 允许开仓*/First_downtrend_control_bool==true && class=class="str">"cmt">/*class="num">3. 控制 class="num">2 允许开仓*/Second_downtrend_control_bool==true && class=class="str">"cmt">/*class="num">4. 控制 class="num">3 允许开仓*/Third_downtrend_control_bool==class="kw">false && class=class="str">"cmt">/*class="num">5. 入场点 work_tf*/PointSell_work_tf_bool==true ); class=class="str">"cmt">//--- 对于卖出 class="type">bool OpenBuy=( class=class="str">"cmt">/*class="num">1. 模型形成*/Model_uptrend_base_tf==true && class=class="str">"cmt">/*class="num">2. 控制 class="num">1 允许开仓*/First_uptrend_control_bool==true && class=class="str">"cmt">/*class="num">3. 控制 class="num">2 允许开仓*/Second_uptrend_control_bool==true && class=class="str">"cmt">/*class="num">4. 控制 class="num">3 允许开仓*/Third_uptrend_control_bool==class="kw">false && class=class="str">"cmt">/*class="num">5. 入场点 work_tf*/PointBuy_work_tf_bool==true ); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">7. 描述交易条件 (结束) | class=class="str">"cmt">//+------------------------------------------------------------------+
把风控映射成具体手数与挂单
一套交易操作在 MT5 里通常拆成三件事:按信号开仓、给持仓补止盈、价格越过成本后把止损推到盈亏平衡。前两件事必须在同一次 tick 逻辑里算清,否则滑点会把你的风险预算撕开。 下面这段逻辑先根据相关性波浪的高低点加减点差定出止损价,再用固定风险金额反推手数。注意 RiskSize_points 乘了系数 f,若结果为 0 会强制置 1,避免后续除零崩脚本。 开仓时手数用 NormalizeDouble(Lot,2) 限制到两位小数,止损价保留五位小数,这对外汇和贵金属都适用,但贵金属点差跳变时可能触发异常成交,属高风险操作。 止盈部分遍历当前所有持仓,逐个读出 SL、开仓价、TP 与品种名,为后面按波动结构挂 TP 做准备。你可以直接把这段代码塞进 EA 的 OnTick 里,开 MT5 用欧美对 1 小时图跑一遍看手数是否随止损距离反向变动。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">8. 处理交易操作 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 定义止损位 SLPrice_sell=High_Corr_wave_downtrend_base_tf_double+spread; SLPrice_buy=Low_Corr_wave_uptrend_base_tf_double-spread; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">8.1 设置仓位 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- 对于卖出 if(OpenSell==true) { RiskSize_points=(SLPrice_sell-bid)*f;class=class="str">"cmt">//止损定义为整数点数 if(RiskSize_points==class="num">0)class=class="str">"cmt">//检查除零 { RiskSize_points=class="num">1; } CostOfPoint_position=SummRisk/RiskSize_points;class=class="str">"cmt">//参考止损定义仓位价格点数 Lot=CostOfPoint_position/CostOfPoint;class=class="str">"cmt">//计算开仓手数 class=class="str">"cmt">//开仓 trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(Lot,class="num">2),bid,NormalizeDouble(SLPrice_sell,class="num">5),class="num">0,""); } class=class="str">"cmt">//--- 对于买入 if(OpenBuy==true) { RiskSize_points=(bid-SLPrice_buy)*f;class=class="str">"cmt">//止损定义为整数点数 if(RiskSize_points==class="num">0)class=class="str">"cmt">//检查除零 { RiskSize_points=class="num">1; } CostOfPoint_position=SummRisk/RiskSize_points;class=class="str">"cmt">//参考止损定义仓位价格点数 Lot=CostOfPoint_position/CostOfPoint;class=class="str">"cmt">//计算开仓手数 class=class="str">"cmt">//开仓 trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(Lot,class="num">2),ask,NormalizeDouble(SLPrice_buy,class="num">5),class="num">0,""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">8.1 设置仓位 (结束) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">8.2 设置止盈 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ if(TP_mode==true) { if(PositionsTotal()>class="num">0) { for(i=class="num">0;i<=PositionsTotal();i++) { if(PositionGetTicket(i)) { class=class="str">"cmt">//获取持仓值 SL_double=class="type">class="kw">double (PositionGetDouble(POSITION_SL)); OP_double=class="type">class="kw">double (PositionGetDouble(POSITION_PRICE_OPEN)); TP_double=class="type">class="kw">double (PositionGetDouble(POSITION_TP)); P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL));
◍ 按风险倍数自动补止盈与移保本
这段逻辑解决一个常见痛点:开仓时只挂了止损、没设止盈,或者跑了一段想锁成本。EA 在遍历持仓时先抓取品种、类型、利润、ticket 与开仓时间,只处理当前图表品种 Symbol() 的仓位。 对买单(P_type==0)若发现 TP_double==0,就用开仓价 OP_double 减止损价 SL_double 得到止损点数,乘以输入参数 M 作为止盈倍数,推倒出 TP_price_buy 后调用 trade.PositionModify 把止盈补上,价格用 NormalizeDouble 精确到 5 位。卖单(P_type==1)对称处理,止盈价落在开仓价下方。 保本部分另起一段:Size_Summ = breakeven * SummRisk,当 Breakeven_mode 为 true 且 breakeven 不为 0、且有持仓时触发。这里 breakeven 是风险倍数,SummRisk 是单笔风险金额,乘积就是触发平移的盈利阈值。外汇与贵金属杠杆高,这类自动修改若遇点差扩大或报价跳空,modify 可能失败,需在实盘前用 MT5 策略测试器验证返回码。 让小布替你跑这套 把 M 设成 2、breakeven 设成 1,意味着止损 20 点则自动补 40 点止盈,浮盈覆盖一倍风险后移保本;直接拷下面代码到 EA 的持仓循环里就能测。
P_type=class="type">int(PositionGetInteger(POSITION_TYPE)); P_profit=class="type">class="kw">double (PositionGetDouble(POSITION_PROFIT)); P_ticket=class="type">int (PositionGetInteger(POSITION_TICKET)); P_opentime=class="type">int(PositionGetInteger(POSITION_TIME)); if(P_symbol==Symbol()) { if(P_type==class="num">0 && TP_double==class="num">0) { class="type">class="kw">double SL_size_buy=OP_double-SL_double;class=class="str">"cmt">//定义止损点数 class="type">class="kw">double TP_size_buy=SL_size_buy*M;class=class="str">"cmt">//止损乘以输入中设置的比率 class="type">class="kw">double TP_price_buy=OP_double+TP_size_buy;class=class="str">"cmt">//定义止盈位 class=class="str">"cmt">//修改持仓 trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_buy,class="num">5)); } if(P_type==class="num">1 && TP_double==class="num">0) { class="type">class="kw">double SL_size_sell=SL_double-OP_double;class=class="str">"cmt">//定义止损点数 class="type">class="kw">double TP_size_sell=SL_size_sell*M;class=class="str">"cmt">//止损乘以输入中设置的比率 class="type">class="kw">double TP_price_sell=OP_double-TP_size_sell;class=class="str">"cmt">//定义止盈位 class=class="str">"cmt">//修改持仓 trade.PositionModify(PositionGetInteger(POSITION_TICKET),SL_double,NormalizeDouble(TP_price_sell,class="num">5)); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">8.2 设置止盈 (结束) | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class="num">8.3 持仓移至盈亏平衡点 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double Size_Summ=breakeven*SummRisk;class=class="str">"cmt">//定义止盈位,之后应将持仓移至盈亏平衡点 if(Breakeven_mode==true && breakeven!=class="num">0) { if(PositionsTotal()>class="num">0) {
「把盈利单推到开仓价保本」
这段逻辑干的事很直接:遍历当前账户所有持仓,把已经跑出足够利润、且止损还压在不利位置的单子,统一把止损拉到开仓价,实现「浮盈不为负」的保本结构。 循环用 PositionsTotal() 取总持仓数,PositionGetTicket(i) 拿到对应持仓的票据号,再从持仓属性里读出 SL、开仓价、TP、品种、类型、利润、票号与开仓时间。注意 P_symbol==Symbol() 这层过滤,只处理当前图表品种,避免跨品种误改。 核心判定分多空两条:P_type==0 为多单,要求 P_profit>=Size_Summ 且 SL_double<OP_double(止损低于开仓价)才改;P_type==1 为空单,要求 P_profit>=Size_Summ 且 SL_double>OP_double(止损高于开仓价)才改。满足条件就调 trade.PositionModify 把止损设成 OP_double,TP 保持不变。 Size_Summ 是外部定义的「触发保本的最小利润额」,单位与账户净值一致。把它设成 0 理论上入场即保本,但实战中点差和滑点可能让修改失败;外汇与贵金属杠杆高,保本后仍可能被瞬扫止损,需自行在 MT5 里跑这段验证触发频率。
for(i=class="num">0;i<=PositionsTotal();i++) { if(PositionGetTicket(i)) { class=class="str">"cmt">//获取持仓值 SL_double=class="type">class="kw">double (PositionGetDouble(POSITION_SL)); OP_double=class="type">class="kw">double (PositionGetDouble(POSITION_PRICE_OPEN)); TP_double=class="type">class="kw">double (PositionGetDouble(POSITION_TP)); P_symbol=class="type">class="kw">string(PositionGetString(POSITION_SYMBOL)); P_type=class="type">int(PositionGetInteger(POSITION_TYPE)); P_profit=class="type">class="kw">double (PositionGetDouble(POSITION_PROFIT)); P_ticket=class="type">int (PositionGetInteger(POSITION_TICKET)); P_opentime=class="type">int(PositionGetInteger(POSITION_TIME)); if(P_symbol==Symbol()) { if(P_type==class="num">0 && P_profit>=Size_Summ && SL_double<OP_double) { trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double); } if(P_type==class="num">1 && P_profit>=Size_Summ && SL_double>OP_double) { trade.PositionModify(PositionGetInteger(POSITION_TICKET),OP_double,TP_double); } } } }
把回测报告榨成统计原料
做价格行为统计前,先锁定一组固定维度:品种、成交类型、入场时间、开仓价、止损位、止损幅度、最大盈利位、最大盈利幅度、成交区间。这里有个关键假设——最大盈利点取开仓后主周期出现的第一个上/下分形高/低位,不是随口定的峰值。 实盘前得先在策略测试器里跑 EA。拿 AUDJPY 举例,回测区间 2018.01.01–2018.08.29,主时间帧 D1、操作时间帧 H6,每笔风险 $100,持仓移到盈亏平衡用 1/2、止盈用 1/3 的风险回报比。跑完把报告存成 CSV,在终端本地文件夹建 report.csv,只留“订单”部分、删掉平仓行,保留开仓时间/品种/类型/价格/止损这几列。 接下来写个脚本读 report.csv,算出止损大小、最大盈利位、最大盈利大小、成交区间柱线数,再落进 file_stat.csv。下面这段是读分隔符 CSV 进数组的底层函数,借了现成文件读取思路,你直接抄进 OnStart 前面就能用。 openprice 和 sl_price 数组是字符串型,计算前要用 StringReplace 把逗号换成小数点再 StringToDouble,否则小数位会丢。分形定位用 iFractals 句柄,开仓柱索引拿到后向两侧找最近上下分形;若持仓被止损打掉,最大盈利记负值、区间算到止损柱为止。最后把点号换回逗号写文件,基准帧设 D1 跑脚本就能在本地文件夹看到新 file_stat.csv。 外汇与贵金属杠杆高、回测不等于未来,AUDJPY 这类交叉盘滑点可能吞掉统计优势,上 MT5 前先想清楚样本外会怎么漂。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 读取数据至数组的函数 (开始) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool ReadFileToArrayCSV(class="type">class="kw">string FileName,SLine &Lines[]) { ResetLastError(); class="type">int h=FileOpen(FileName,FILE_READ|FILE_ANSI|FILE_CSV,";"); if(h==INVALID_HANDLE) { class="type">int ErrNum=GetLastError(); printf("File open error %s # %i",FileName,ErrNum); class="kw">return(class="kw">false); } class="type">int lcnt=class="num">0; class=class="str">"cmt">// 计算字符串的变量 class="type">int fcnt=class="num">0; class=class="str">"cmt">// 计算字符串字段的变量 class="kw">while(!FileIsEnding(h)) { class="type">class="kw">string str=FileReadString(h); class=class="str">"cmt">// 新字符串(新结构数组元素) if(lcnt>=ArraySize(Lines)) { class=class="str">"cmt">// 结构数组完全填满 ArrayResize(Lines,ArraySize(Lines)+class="num">1024); class=class="str">"cmt">// 将数组大小增加 class="num">1024 个元素 } ArrayResize(Lines[lcnt].field,class="num">64);class=class="str">"cmt">// 更改结构中的数组大小 Lines[lcnt].field[class="num">0]=str; class=class="str">"cmt">// 分配第一个字段的值 class=class="str">"cmt">// 开始读取字符串中的剩余字段 fcnt=class="num">1; class=class="str">"cmt">// 字段数组中的一个元素被占用 class="kw">while(!FileIsLineEnding(h)) { class=class="str">"cmt">// 读取字符串中的其余字段 str=FileReadString(h); if(fcnt>=ArraySize(Lines[lcnt].field)) { class=class="str">"cmt">// 字段数组完全填满 ArrayResize(Lines[lcnt].field,ArraySize(Lines[lcnt].field)+class="num">64); class=class="str">"cmt">// 将数组大小增加 class="num">64 个元素 } Lines[lcnt].field[fcnt]=str; class=class="str">"cmt">// 分配下一个字段的值 fcnt++; class=class="str">"cmt">// 增加字段计数器 } ArrayResize(Lines[lcnt].field,fcnt); class=class="str">"cmt">// 根据实际的字段数改变字段数组大小 lcnt++; class=class="str">"cmt">// 增加字符串计数器 } ArrayResize(Lines,lcnt); class=class="str">"cmt">// 根据实际的字符串数更改结构数组(字符串) FileClose(h);
◍ 把分形与账户成交拉进同一套数组
这段脚本把基准周期的分形指标和账户历史成交塞进同一组数组,方便后面做波浪统计。输入参数里 sar_step 默认 0.1、maximum_step 默认 0.11,基准周期由 base_tf 外部指定,脚本启动时会弹出输入面板。 先拿 iFractals 句柄,再把 High、Low 以及分形上下数组全部设成时间序列,用 CopyHigh / CopyLow / CopyBuffer 各拉 1000 根柱线进内存。Bars() 取当前柱数,SymbolInfoInteger 读品种小数位,按 5/4/3/2/1 位分别把价格容量 f 设成 100000 到 10,后面算点数直接乘 f 就行。 账户侧另开一堆数组:opentime 存开仓时间、openprice 存开仓价、sl_price 存止损价、down_fractal / up_fractal 存对应分形索引、sl_size_points 存止损点数、maxprofit_* 存浮盈极值、duration 存波浪持续柱数。这些容器不填数据,只是把后面要跑的循环变量先声明好,开 MT5 把这段贴进脚本头部就能直接编译过。
class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- 输入 class="kw">input ENUM_TIMEFRAMES base_tf; class=class="str">"cmt">//基准周期时间帧 class="kw">input class="type">class="kw">double sar_step=class="num">0.1; class=class="str">"cmt">//设置抛物线步幅 class="kw">input class="type">class="kw">double maximum_step=class="num">0.11; class=class="str">"cmt">//设置抛物线最大步幅 class=class="str">"cmt">//--- 声明指标句柄的变量 class="type">int Fractal_base_tf; class=class="str">"cmt">//iFractal 指标句柄 class=class="str">"cmt">//--- 声明 base_tf 变量 class="type">class="kw">double High_base_tf[],Low_base_tf[]; class=class="str">"cmt">//用于存储柱线高/低价格的数组 class="type">class="kw">double FractalDown_base_tf[],FractalUp_base_tf[]; class=class="str">"cmt">//用于存储 iFractall 指标价格的数组 class=class="str">"cmt">//--- 数组结构 class="kw">struct SLine { class="type">class="kw">string field[]; }; class=class="str">"cmt">//--- 获取 iFractal 指标句柄 Fractal_base_tf=iFractals(Symbol(),base_tf); class=class="str">"cmt">//--- 设置 base_tf 数组的顺序为时间序列 ArraySetAsSeries(High_base_tf,true); ArraySetAsSeries(Low_base_tf,true); ArraySetAsSeries(FractalDown_base_tf,true); ArraySetAsSeries(FractalUp_base_tf,true); class=class="str">"cmt">//--- 初始填充 base_tf 数组 CopyHigh(Symbol(),base_tf,class="num">0,class="num">1000,High_base_tf); CopyLow(Symbol(),base_tf,class="num">0,class="num">1000,Low_base_tf); CopyBuffer(Fractal_base_tf,class="num">0,TimeCurrent(),class="num">1000,FractalUp_base_tf); CopyBuffer(Fractal_base_tf,class="num">1,TimeCurrent(),class="num">1000,FractalDown_base_tf); class=class="str">"cmt">//--- 用于存储柱线编号上数据的变量 class="type">int bars_base_tf=Bars(Symbol(),base_tf); class=class="str">"cmt">//品种价格中的小数位数 class="type">int Digit=(class="type">int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS); class=class="str">"cmt">//定义当前品种的价格容量 class="type">class="kw">double f=class="num">1; if(Digit==class="num">5) {f=class="num">100000;} if(Digit==class="num">4) {f=class="num">10000;} if(Digit==class="num">3) {f=class="num">1000;} if(Digit==class="num">2) {f=class="num">100;} if(Digit==class="num">1) {f=class="num">10;} class=class="str">"cmt">//--- 声明变量和数组 class="type">int i,j,n; class=class="str">"cmt">//循环变量 class="type">class="kw">datetime opentime[];class=class="str">"cmt">//用于存储仓位设定时间的数组 class="type">class="kw">string symbol[];class=class="str">"cmt">//用于存储品种的数组 class="type">class="kw">string type[];class=class="str">"cmt">//用于存储成交类型的数组 class="type">class="kw">string openprice[];class=class="str">"cmt">//存储开仓价格的数组 class="type">class="kw">string sl_price[];class=class="str">"cmt">//存储止损价位的数组 class="type">int index[];class=class="str">"cmt">//存储开仓所在柱线索引的数组 class="type">int down_fractal[];class=class="str">"cmt">//存储低位分形指数的数组 class="type">int up_fractal[];class=class="str">"cmt">//存储高位分形指数的数组 class="type">class="kw">double sl_size_points[];class=class="str">"cmt">//存储止损大小点数的数组 class="type">class="kw">string maxprofit_price[];class=class="str">"cmt">//存储最大利润价位的数组 class="type">class="kw">double maxprofit_size_points[];class=class="str">"cmt">//存储最大利润值的数组 class="type">int duration[];class=class="str">"cmt">//存储波浪持续柱线数的数组 class="type">bool maxprofit_bool[];class=class="str">"cmt">//确保不可由止损激活仓位的数组
「把回测 CSV 拆进数组的实战写法」
做 EA 复盘时,把 MT5 导出的 report.csv 读进程序是第一步。下面这段逻辑先建了 maxprofit_int[] 与 lines[] 两个容器,前者配合布尔数组标记最大盈利柱,后者承接逐行文本。 若 ReadFileToArrayCSV 返回 false,直接在 Experts 标签页报警,不往下跑,避免空数组越界。读成功后才用 ArraySize(lines) 统一把十几个业务数组(开仓时间、品种、类型、开价、止损等)一次性Resize,size 就是总行数。 内层四个 for 循环用不同的步长抽字段:j=j+5 取开仓时间,j=j+4 取品种,j=j+3 取成交类型,j=j+2 取开仓价,j=j+1 取止损价。这种错位步长对应 CSV 里每行的固定列序,改列结构时必须同步调步长,否则会错位写到别的数组。 最后对 openprice 做 StringReplace 把逗号转点号,是因为部分环境 CSV 用逗号做小数分隔,不转的话 (double) 强转会丢精度。外汇与贵金属波动快、点差跳变频繁,回测数据清洗这一步省不得,否则后续信号判定可能系统性偏移。
class="type">int maxprofit_int[];class=class="str">"cmt">//定义最小/最大柱线的数组。 它与 maxprofit_bool[] 一起使用 SLine lines[]; class="type">int size=class="num">0; if(!ReadFileToArrayCSV("report.csv",lines)) { Alert("Error, see details in the \"Experts\" tab"); } else { size=ArraySize(lines); ArrayResize(opentime,ArraySize(lines)); ArrayResize(symbol,ArraySize(lines)); ArrayResize(type,ArraySize(lines)); ArrayResize(openprice,ArraySize(lines)); ArrayResize(sl_price,ArraySize(lines)); ArrayResize(index,ArraySize(lines)); ArrayResize(down_fractal,ArraySize(lines)); ArrayResize(up_fractal,ArraySize(lines)); ArrayResize(sl_size_points,ArraySize(lines)); ArrayResize(maxprofit_price,ArraySize(lines)); ArrayResize(maxprofit_size_points,ArraySize(lines)); ArrayResize(duration,ArraySize(lines)); ArrayResize(maxprofit_bool,ArraySize(lines)); ArrayResize(maxprofit_int,ArraySize(lines)); for(i=class="num">0;i<size;i++) { for(j=class="num">0;j<ArraySize(lines[i].field);j=j+class="num">5)class=class="str">"cmt">//按开仓时间列选择字段 { opentime[i]=(class="type">class="kw">datetime)(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组 } for(j=class="num">1;j<ArraySize(lines[i].field);j=j+class="num">4)class=class="str">"cmt">//按品种列选择字段 { symbol[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组 } for(j=class="num">2;j<ArraySize(lines[i].field);j=j+class="num">3)class=class="str">"cmt">//按成交类型列选择字段 { type[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组 } for(j=class="num">3;j<ArraySize(lines[i].field);j=j+class="num">2)class=class="str">"cmt">//按开仓价格列选择字段 { openprice[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组 } for(j=class="num">4;j<ArraySize(lines[i].field);j=j+class="num">1)class=class="str">"cmt">//按止损列选择字段 { sl_price[i]=(lines[i].field[j]);class=class="str">"cmt">//将数据写入数组 } } } class=class="str">"cmt">//----------------------------------------------------- for(i=class="num">0;i<size;i++) { StringReplace(openprice[i],",",".");
分形与止损间距的批量标定
这段逻辑干的事很直接:把每笔历史订单的开仓时间映射到 D1 柱索引,再向前回溯最近的卖出分形(FractalDown)和买入分形(FractalUp),把位置写进 down_fractal[] / up_fractal[] 数组。 回溯用 iBarShift(Symbol(), PERIOD_D1, opentime[i]) 拿到开仓柱序号,随后从该柱往 0 号柱方向扫,遇到分形值非 EMPTY_VALUE 就 break。注意循环条件是 n>0,意味着若一直没碰到分形,会停在 1 号柱而非 0 号。 止损点数按品种报价因子 f 换算:sell 单为 (sl_price - openprice)*f,buy 单反过来。这里 sl_price 和 openprice 都是字符串,必须靠 StringToDouble 转双精度,否则直接参与运算会得到 0 或乱值。 为防止止损在摸到最大利润前就被触发,代码又扫了一遍:sell 单从开仓柱向下分形之间逐根比对 High_base_tf[n] >= sl_price,一旦某根高点够到止损价就记下 maxprofit_int[i]=n;若循环跑到 n==down_fractal[i] 才停,maxprofit_bool[i] 置真,说明这段区间价格从未威胁止损。外汇与贵金属杠杆高,这类回溯只是历史标定,实盘触发概率仍随波动变化。 开 MT5 把 FractalDown_base_tf / FractalUp_base_tf 数组接上对应周期缓冲,就能直接复用这套索引映射验证自己的订单历史。
StringReplace(sl_price[i],",","."); class=class="str">"cmt">//--- 定义开仓所在柱线的索引 for(i=class="num">0;i<size;i++) { index[i]=iBarShift(Symbol(),PERIOD_D1,opentime[i]);class=class="str">"cmt">//将数据写入数组 } class=class="str">"cmt">//--- 搜索卖出分形 for(i=class="num">0;i<size;i++) { if(type[i]=="sell") { for(n=index[i];n>class="num">0;n--) { if(FractalDown_base_tf[n]!=EMPTY_VALUE) class="kw">break; } down_fractal[i]=n; } } class=class="str">"cmt">//--- 搜索买入分形 for(i=class="num">0;i<size;i++) { if(type[i]=="buy") { for(n=index[i];n>class="num">0;n--) { if(FractalUp_base_tf[n]!=EMPTY_VALUE) class="kw">break; } up_fractal[i]=n; } } class=class="str">"cmt">//--- 止损点数 for(i=class="num">0;i<size;i++) { if(type[i]=="sell") { sl_size_points[i]=(StringToDouble(sl_price[i])-StringToDouble(openprice[i]))*f; } if(type[i]=="buy") { sl_size_points[i]=(StringToDouble(openprice[i])-StringToDouble(sl_price[i]))*f; } } class=class="str">"cmt">//--- 确保在达到最大利润之前,不会由止损平仓 class=class="str">"cmt">//--- 对于卖出 for(i=class="num">0;i<size;i++) { if(type[i]=="sell") { for(n=index[i];n>down_fractal[i];n--) { if(High_base_tf[n]>=StringToDouble(sl_price[i])) class="kw">break; } maxprofit_int[i]=n; maxprofit_bool[i]=(n==down_fractal[i]); } } class=class="str">"cmt">//--- 对于买入 for(i=class="num">0;i<size;i++) { if(type[i]=="buy") { for(n=index[i];n>up_fractal[i];n--) { if(Low_base_tf[n]<=StringToDouble(sl_price[i])) class="kw">break; } maxprofit_int[i]=n; maxprofit_bool[i]=(n==up_fractal[i]); } } class=class="str">"cmt">//--- 最大盈利价位 for(i=class="num">0;i<size;i++) {
◍ 分方向回填最大盈利与持仓时长
这段逻辑按成交方向(buy/sell)和是否触达基准分形(maxprofit_bool)分别回填价格、点数和持续柱数,是统计模块里最易写错分支的地方。 先看价格与点数:卖单若触达下方分形,maxprofit_price 取 Low_base_tf[down_fractal[i]],点数 = (开仓价 - 该低点) * f;未触达则价格置空、点数取 sl_size_points 的相反数。买单对称处理,用 High_base_tf[up_fractal[i]] 减开仓价。 持仓时长 duration 也分四种情况:触达分形的卖单用 index[i] 减 down_fractal 索引,买单减 up_fractal 索引;未触达的统一用 index[i] 减 maxprofit_int[i],即止损或退出时的柱索引。 最后一轮把 openprice、sl_price、maxprofit_price 里的小数点替换成逗号,适配部分区域的小数格式再写文件。外汇与贵金属波动剧烈,这类统计仅作历史分布参考,实盘仍属高风险。
if(type[i]=="sell" && maxprofit_bool[i]==true) { maxprofit_price[i]=(class="type">class="kw">string)Low_base_tf[down_fractal[i]]; } if(type[i]=="sell" && maxprofit_bool[i]==class="kw">false) { maxprofit_price[i]=""; } if(type[i]=="buy" && maxprofit_bool[i]==true) { maxprofit_price[i]=(class="type">class="kw">string)High_base_tf[up_fractal[i]]; } if(type[i]=="buy" && maxprofit_bool[i]==class="kw">false) { maxprofit_price[i]=""; } } for(i=class="num">0;i<size;i++) { if(type[i]=="sell" && maxprofit_bool[i]==true) { maxprofit_size_points[i]=(StringToDouble(openprice[i])-Low_base_tf[down_fractal[i]])*f; } if(type[i]=="sell" && maxprofit_bool[i]==class="kw">false) { maxprofit_size_points[i]=sl_size_points[i]*-class="num">1; } if(type[i]=="buy" && maxprofit_bool[i]==true) { maxprofit_size_points[i]=(High_base_tf[up_fractal[i]]-StringToDouble(openprice[i]))*f; } if(type[i]=="buy" && maxprofit_bool[i]==class="kw">false) { maxprofit_size_points[i]=sl_size_points[i]*-class="num">1; } } class=class="str">"cmt">//--- 计算成交持续柱线数 for(i=class="num">0;i<size;i++) { if(type[i]=="sell" && maxprofit_bool[i]==true) { duration[i]=index[i]-(class="type">int)down_fractal[i]; } if(type[i]=="sell" && maxprofit_bool[i]==class="kw">false) { duration[i]=index[i]-maxprofit_int[i]; } if(type[i]=="buy" && maxprofit_bool[i]==true) { duration[i]=index[i]-(class="type">int)up_fractal[i]; } if(type[i]=="buy" && maxprofit_bool[i]==class="kw">false) { duration[i]=index[i]-maxprofit_int[i]; } } for(i=class="num">0;i<size;i++) { StringReplace(openprice[i],".",","); StringReplace(sl_price[i],".",","); StringReplace(maxprofit_price[i],".",","); } class=class="str">"cmt">//--- 将数据写入新的统计文件
「把成交统计落盘成 CSV」
想把每笔成交的止损幅度、最大盈利和持仓根数留痕,最省事的办法是直接在 EA 里写一份 ANSI 编码的 CSV。下面这段逻辑先尝试以读写方式打开 file_stat.csv,分隔符用分号,打不开就弹窗报警并退出,避免后续空句柄写崩。 文件句柄有效后,第一行先写表头:品种、成交类型、入场时间、开仓价、止损价、止损点数、最大盈利价、最大盈利点数、持续根数,共 9 列。随后 FileSeek 跳到文件尾,按数组 size 循环把每笔历史成交的对应字段写进去;价格类的小数用 NormalizeDouble 截断到 2 位,时间用 TimeToString 转成可读串。 循环结束关句柄并提示文件已生成。MT5 里这份 csv 会落在 MQL5\Files 目录下,拿 Excel 打开就能直接算止损点数分布——外汇和贵金属杠杆高,样本量建议先跑满 200 笔以上再看分布倾向,单看几十笔噪声偏大。
class="type">int h=FileOpen("file_stat.csv",FILE_READ|FILE_WRITE|FILE_ANSI|FILE_CSV,";"); class=class="str">"cmt">//--- 开仓检查 if(h==INVALID_HANDLE) { Alert("Error opening file!"); class="kw">return; } else { FileWrite(h, class=class="str">"cmt">/*class="num">1 品种*/"Symbol", class=class="str">"cmt">/*class="num">2 成交类型*/"Deal type", class=class="str">"cmt">/*class="num">3 入场时间*/"Open time", class=class="str">"cmt">/*class="num">4 开仓价格*/"Open price", class=class="str">"cmt">/*class="num">5 止损价位*/"SL", class=class="str">"cmt">/*class="num">6 止损大小*/"SL size", class=class="str">"cmt">/*class="num">7 最大盈利价位*/"Max profit level", class=class="str">"cmt">/*class="num">8 最大盈利值*/"Max profit value", class=class="str">"cmt">/*class="num">9 持续期*/"Duration in bars"); class=class="str">"cmt">//--- 转至结束 FileSeek(h,class="num">0,SEEK_END); for(i=class="num">0;i<size;i++) { FileWrite(h, class=class="str">"cmt">/*class="num">1 品种*/symbol[i], class=class="str">"cmt">/*class="num">2 成交类型*/type[i], class=class="str">"cmt">/*class="num">3 入场时间*/TimeToString(opentime[i]), class=class="str">"cmt">/*class="num">4 开仓价格*/openprice[i], class=class="str">"cmt">/*class="num">5 止损价位*/sl_price[i], class=class="str">"cmt">/*class="num">6 止损大小*/NormalizeDouble(sl_size_points[i],class="num">2), class=class="str">"cmt">/*class="num">7 最大盈利价位*/maxprofit_price[i], class=class="str">"cmt">/*class="num">8 最大盈利大小*/NormalizeDouble(maxprofit_size_points[i],class="num">2), class=class="str">"cmt">/*class="num">9 持续期*/duration[i]); } } FileClose(h); Alert("file_stat.csv file created");
模型识别只是起点
前面几节把不依赖指标的修正极值抓取和连续点判定讲清楚了,但落到 EA 里这只是最低限度的骨架。策略测试器跑出来的数组统计能证明模型可被程序稳定识别,实盘之前必须把开仓过滤、止损逻辑和品种波动适配补进控制集合。 外汇和贵金属杠杆高、滑点跳空频繁,直接用最小控制集上实盘,回测里看着顺的延续模型在 live 环境可能频繁假突破。建议先拿 EURUSD 的 M15 把极值容差参数从 3 跳调到 8 跳逐档验证。 图例 8 到 11 展示了四个不同品种的延续模型识别样例,你可以照着把代码里的 ExtremumDepth 改小,看识别密度怎么变——这比读结论直观。
◍ 别急着下结论
这套延续形态识别方案落地时,真正要用的就三个件:Trade.mqh 管下单、MQL5_POST_final 跑走势延续模型、Report_read 负责把统计结果拽出来。ZIP 包 128.44 KB,丢进 MT5 的 MQL5 目录就能编译,不用自己拼逻辑。 有读者在评论里挑明,作者原本就没把它包装成完整交易系统,只是给出“定义延续形态的一种可能方法”。所以拿去跑 EURUSD 或 XAUUSD 之前,先想清楚:外汇和贵金属杠杆高、滑点跳空随时来,模型在假突破里的表现没人兜底。 真要验证,先开策略测试器用 Report_read 导一轮数据,看 ABC 段识别命中率再谈改参数。模型本身不承诺胜率,只是把“大浪—修正浪”的肉眼活儿交给了代码。