迷你行情模拟器或手动策略测试器·综合运用
(3/3)· 全职没空盯盘、不愿耗一年模拟?这套有限功能指标让你快速跑通理论验证
回测漂亮实盘翻车,是很多新手栽跟头的地方。与其在真实账户交学费,不如先在接近现实的仿真环境里把逻辑跑熟。本文收尾这一三篇系列,直接给能落地的迷你模拟器用法。
用分笔报价补全合成K线的影线
这段逻辑解决一个实盘常见痛点:用 M1 柱合成大周期柱时,若只取首尾 close,高低点会被平滑掉,价格行为里的刺透、影线拒绝就看不见了。代码先通过 Bars() 算出起止柱索引差,再循环把区间内每根 M1 的 high/low 塞进输出数组,顺手按收闭关系给 output_col 标 0(阴)或 1(阳)。 start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling)-1; current_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator)-1; if(start_of_z<current_of_z-1) { for(int z=start_of_z; z<current_of_z-1; z++) { output_c[input_end_bar_indicator]=input_rates[z].close; if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high; if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low; if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0; else output_col[input_end_bar_indicator]=1; } input_end_time_indicator=input_rates[current_of_z].time; } // 得到数组中的分笔报价 static int x=0; // 数组计数器和开始标志 static double tick_current[]; static int tick_current_size=0; if(x==0) { func_tick_generation(input_rates[current_of_z-1],tick_current); tick_current_size=ArraySize(tick_current); if(output_h[input_end_bar_indicator]==0) {output_h[input_end_bar_indicator]=tick_current[x];} if(output_l[input_end_bar_indicator]==0) {output_l[input_end_bar_indicator]=tick_current[x];} output_c[input_end_bar_indicator]=tick_current[x]; } if(x<tick_current_size) { output_c[input_end_bar_indicator]=tick_current[x]; if(tick_current[x]>output_h[input_end_bar_indicator]) {output_h[input_end_bar_indicator]=tick_current[x];} if(tick_current[x]<output_l[input_end_bar_indicator]) {output_l[input_end_bar_indicator]=tick_current[x];} if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0; else output_col[input_end_bar_indicator]=1; x++; } else { input_end_time_indicator=input_rates[current_of_z+1].time; x=0; tick_current_size=0; ArrayFree(tick_current); } if(input_end_time_indicator>input_time_next_filling) {work_status=1;} } } //+------------------------------------------------------------------+
| // | Merger 函数 |
|---|
//+------------------------------------------------------------------+ void func_merger() { switch(button_speed) { case 1: { func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); func_of_form_jeweler_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); price_bid_now=TST_C_C[end_bar_indicator]; price_ask_now=price_bid_now+(spread*_Point); } break; case 2: { func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); func_of_form_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); price_bid_now=TST_C_C[end_bar_indicator]; price_ask_now=price_bid_now+(spread*_Point); end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,13); } break; case 3: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,11); } break; case 4: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,9); } break; case 5: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,7); } break; case 6: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,5); } break; case 7: { 后面那段 func_merger 用 button_speed 切档:档位 2 在合成后把 end_time_indicator 往后推 func_calc_time(...,13),档位 3~6 分别推 11/9/7/5。也就是说,速度档越高,单根合成柱覆盖的 M1 根数越少,图表刷新越密,但 CPU 占用会随 tick_current 数组膨胀而上升。外汇与贵金属波动大、滑点频繁,这类重绘逻辑在 live 账户上可能触发意外重算,建议先在 MT5 策略测试器用历史数据跑一遍确认。 别把 M1 高低点当唯一真相 分笔报价由 func_tick_generation 生成,若经纪商 Tick 流在极端行情有丢包,合成影线会偏短,此时价格行为信号的可信度要打折。开 MT5 把 spread 和 tick_current_size 打进 Print,看一眼实际数组长度再决定信不信这根针。
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling)-class="num">1; current_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator)-class="num">1; if(start_of_z<current_of_z-class="num">1) { for(class="type">int z=start_of_z; z<current_of_z-class="num">1; z++) { output_c[input_end_bar_indicator]=input_rates[z].close; if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high; if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low; if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=class="num">0; else output_col[input_end_bar_indicator]=class="num">1; } input_end_time_indicator=input_rates[current_of_z].time; } class=class="str">"cmt">// 得到数组中的分笔报价 class="kw">static class="type">int x=class="num">0; class=class="str">"cmt">// 数组计数器和开始标志 class="kw">static class="type">class="kw">double tick_current[]; class="kw">static class="type">int tick_current_size=class="num">0; if(x==class="num">0) { func_tick_generation(input_rates[current_of_z-class="num">1],tick_current); tick_current_size=ArraySize(tick_current); if(output_h[input_end_bar_indicator]==class="num">0) {output_h[input_end_bar_indicator]=tick_current[x];} if(output_l[input_end_bar_indicator]==class="num">0) {output_l[input_end_bar_indicator]=tick_current[x];} output_c[input_end_bar_indicator]=tick_current[x]; } if(x<tick_current_size) { output_c[input_end_bar_indicator]=tick_current[x]; if(tick_current[x]>output_h[input_end_bar_indicator]) {output_h[input_end_bar_indicator]=tick_current[x];} if(tick_current[x]<output_l[input_end_bar_indicator]) {output_l[input_end_bar_indicator]=tick_current[x];} if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=class="num">0; else output_col[input_end_bar_indicator]=class="num">1; x++; } else { input_end_time_indicator=input_rates[current_of_z+class="num">1].time; x=class="num">0; tick_current_size=class="num">0; ArrayFree(tick_current); } if(input_end_time_indicator>input_time_next_filling) {work_status=class="num">1;} } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Merger 函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void func_merger() { class="kw">switch(button_speed) { case class="num">1: { func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); func_of_form_jeweler_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); price_bid_now=TST_C_C[end_bar_indicator]; price_ask_now=price_bid_now+(spread*_Point); } break; case class="num">2: { func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); func_of_form_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); price_bid_now=TST_C_C[end_bar_indicator]; price_ask_now=price_bid_now+(spread*_Point); end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,class="num">13); } break; case class="num">3: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,class="num">11); } break; case class="num">4: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,class="num">9); } break; case class="num">5: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,class="num">7); } break; case class="num">6: { ... end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,class="num">5); } break; case class="num">7: {
◍ 实时报价与蜡烛合成的最后一步
在周期数组遍历到目标末端后,func_filling 负责把已统计的 open/high/low/close 与颜色标记写入 TST_C_* 系列数组,func_candle_per_seconds 则按秒级粒度补齐当前未闭合蜡烛的瞬时值。 随后用 end_bar_indicator 索引直接取 TST_C_C 中的收盘价作为买价:price_bid_now = TST_C_C[end_bar_indicator]。卖价则在买价基础上叠加 spread * _Point,这里的 _Point 是品种最小报价单位,外汇与贵金属点差随流动性跳变,属于高风险变量,实盘需先 Print(spread) 确认。 break 跳出外层循环即意味着本根自定义蜡烛已定型。打开 MT5 按 F4 把这段贴进 EA 的 OnCalculate 末尾,改 end_bar_indicator 的偏移量就能观察不同收盘锚点下的报价刷新延迟。
func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); func_candle_per_seconds(period_array,end_time_indicator,bars_now_rates,number_now_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status); price_bid_now=TST_C_C[end_bar_indicator]; price_ask_now=price_bid_now+(spread*_Point); } break; } }
「拿它模拟新手盘内动作」
这个指标最实用的落点,是替你跑一遍‘假设我是刚进场的新手’这种思维实验。把入场、离场、被支撑阻力扫损的全过程用机器重演,比在脑里空想更接近真实盘感。 涉及的技术工具不复杂:可以用它画埃洛特波浪结构,也可以拉通道线,或者专门测试某条支撑/阻力在被触碰时的反应。外汇与贵金属波动大、杠杆高,这类模拟只反映历史情境概率,不代表未来必现。 开 MT5 把指标挂上,选一段已知行情回放,观察它在你预设的关卡附近如何触发买卖,就能验证你的策略假设是否成立。
画得少,看得清
把这类分笔报价模拟称作“珠宝”,原因很直白:开发时我就发现,对大多数策略回测来说,那种极度平滑又精确的建模根本不是刚需,它更像是件奢侈的瑰宝。模拟出来的微小波动,量级几乎和点差持平,对策略资金曲线只有微弱扰动,更别提还拖慢了测试速度。 真遇到需要回退到某个顺手价位的情形,没人愿意耗上好几天去等一个入场信号。基础状态全存在全局变量里,即便仿真中途报错,只要不关指标窗口,重切时间帧或重启终端就能接着跑。 附带工程里 STSv1.1.ex5 占 120.96 KB、源码 STSv1.1.mq5 为 127.38 KB,辅助压缩包 for_STS.zip 仅 13.39 KB——装的时候把 mq5 丢进 MetaEditor 对应目录,解压 for_STS 里的图像文件夹,编译后当指标挂上 MT5 即可。外汇与贵金属仿真终归是高风险的沙盘,实盘前请自验边界。