自动交易的传说: 是少或多?(基础篇)
「用真金白银验证交易假设」
很多关于自动交易的争论,本质是在赌「信号多一点好还是少一点好」。但纸面推演永远缺一环:没人替你的账户负责。 Livermore 那句老话在 MT5 时代依然成立——要证明一个方案对,唯一干净的办法是拿自己的钱跑。Roman Zamozhnyy 在 2014 年那篇讨论里也点明:先自己把问题定义清楚,等觉得找到解法了,再去用实盘资金证伪或证实。 外汇与贵金属杠杆高、滑点随机,用模拟盘验证 EA 逻辑和用真仓验证,得出的「置信度」根本不是一回事。开 MT5 后,建议先把同一套均线突破逻辑分别挂 demo 与小额 real 账户各一周,对比成交离场点差异,你大概率会倾向认为回测漂亮不等于实盘能活。
◍ 从手绘点数图到自动下单的跳变
点数图(P&F)在 MT5 上早有脚本能画图,但多数只停留在视觉参考,不碰交易逻辑。现在值得做的是把方向判断和成交量过滤接进 EA,让图表自己吐出买卖指令,而不是人盯屏幕找 X 列和 O 列。 原文给出的 EURUSD 样例是 Box=20 点、Reverse=3 格的配置,价格区间从 1.2706 拉到 1.4588,横跨约 1882 点。这种尺度下,三格反转意味着趋势确认需要至少 60 点回撤才翻列,噪音比普通 K 线小得多。 两个可验证的假定先在策略测试器里跑一遍:牛势买、熊势卖,且入场前必须定好止损;支撑上方且高于前一根 X 列处挂 buy stop,阻力下方且低于前一根 O 列处挂 sell stop,轴点用追踪止损。 另一套是突破玩法——破阻力买、破支撑卖,轴点设硬止损、趋势线附近接追踪。外汇和贵金属杠杆高,这类突破在震荡市可能连续扫损,实盘前请用历史数据算期望。 好的单子往往入场就浮盈,这是点数图 column 切换最直观的反馈;亏损单别扛,平掉比等反弹更省手续费。
Copyright(c) <span class="number">class="num">2012</span>-<span class="number">class="num">2014</span> Roman Rich Euro vs US Dollar, Box-<span class="number">class="num">20</span>, Reverse-<span class="number">class="num">3</span> <span class="number">class="num">1.4588</span> | \.....\.................................................................... | <span class="number">class="num">1.4588</span> <span class="number">class="num">1.4521</span> | X\....X\................................................................... | <span class="number">class="num">1.4521</span> <span class="number">class="num">1.4454</span> | XO\.\.XO\.................................................................. | <span class="number">class="num">1.4454</span> <span class="number">class="num">1.4388</span> | XOX\X\XO.\................................................................. | <span class="number">class="num">1.4388</span> <span class="number">class="num">1.4322</span> | XOXOXOXO..\................................................................ | <span class="number">class="num">1.4322</span> <span class="number">class="num">1.4256</span> | XOXOXOXO...\....\.......................................................... | <span class="number">class="num">1.4256</span> <span class="number">class="num">1.4191</span> | XOXO/OXO....\...X\......................................................... | <span class="number">class="num">1.4191</span> <span class="number">class="num">1.4125</span> | XOX/.O/O.....\..XO\........................................................ | <span class="number">class="num">1.4125</span> <span class="number">class="num">1.4060</span> | XO/../.O......\.XO.\....................................................... | <span class="number">class="num">1.4060</span> <span class="number">class="num">1.3996</span> | ./.....O.......\XO..\...................................................... | <span class="number">class="num">1.3996</span> <span class="number">class="num">1.3932</span> | .......OX.......XO...\....................................................X | <span class="number">class="num">1.3932</span> <span class="number">class="num">1.3868</span> | .......OXO..X.X.XOX...\.................................................X.X | <span class="number">class="num">1.3868</span> <span class="number">class="num">1.3804</span> | .......OXO..XOXOXOXOX..\..............................................X.XOX | <span class="number">class="num">1.3804</span> <span class="number">class="num">1.3740</span> | .......OXO..XOXOXOXOXO..\..............................\...........XOXOX | <span class="number">class="num">1.3740</span> <span class="number">class="num">1.3677</span> | .......OXOX.XO.O.OXOXO...\................................X\..........XOXOX | <span class="number">class="num">1.3677</span> <span class="number">class="num">1.3614</span> | .......OXOXOX....O.OXO....\...............................XO\.........XOXOX | <span class="number">class="num">1.3614</span> <span class="number">class="num">1.3552</span> | .......O.OXOX...../OXO.....\..............................XO.\........XOXOX | <span class="number">class="num">1.3552</span> <span class="number">class="num">1.3490</span> | .........OXOX..../.O.OX.....\.............................XO..\.......XOXO. | <span class="number">class="num">1.3490</span> <span class="number">class="num">1.3428</span> | .........OXOX.../....OXO.....\X.\.........................XO...\\...X.XOX.. | <span class="number">class="num">1.3428</span> <span class="number">class="num">1.3366</span> | .........O.OX../.....OXO......XOX\........................XO....X\..XOXOX.. | <span class="number">class="num">1.3366</span> <span class="number">class="num">1.3305</span> | ...........OX./......OXO....X.XOXO\.....................X.XO....XO\.XOXO... | <span class="number">class="num">1.3305</span> <span class="number">class="num">1.3243</span> | ...........OX/.......O.O....XOXOXOX\....................XOXO....XO.\XOX.../ | <span class="number">class="num">1.3243</span> <span class="number">class="num">1.3183</span> | ...........O/..........OX...XOXOXOXO\...................XOXOX.X.XOX.XOX../. | <span class="number">class="num">1.3183</span> <span class="number">class="num">1.3122</span> | .........../...........OXO..XOXOXOXO.\..........X...X.X.XOXOXOXOXOXOXO../.. | <span class="number">class="num">1.3122</span> <span class="number">class="num">1.3062</span> | .......................OXOX.XOXO.OXO..\.........XOX.XOXOXOXOXOXOXOXOX../... | <span class="number">class="num">1.3062</span> <span class="number">class="num">1.3002</span> | .......................O.OXOXO...O/O...\........XOXOXOXOXO.OXO.OXOXO../.... | <span class="number">class="num">1.3002</span> <span class="number">class="num">1.2942</span> | .........................OXOX..../.O....\.......XOXOXOXOX..OX..OXOX../..... | <span class="number">class="num">1.2942</span> <span class="number">class="num">1.2882</span> | .........................O.OX.../..O.....\......XOXO.OXO...OX..OXOX./...... | <span class="number">class="num">1.2882</span> <span class="number">class="num">1.2823</span> | ...........................OX../...OX.....\.....XO...OX.../OX..O/OX/....... | <span class="number">class="num">1.2823</span> <span class="number">class="num">1.2764</span> | ...........................OX./....OXO.....\....X....OX../.O.../.O/........ | <span class="number">class="num">1.2764</span> <span class="number">class="num">1.2706</span> | ...........................OX/.....OXO..X...\...X....O../......../......... | <span class="number">class="num">1.2706</span>
从报价带读价格堆叠密度
上面这段输出是某次 EURUSD 在 MT5 报价窗口里导出的原始文本快照,左侧 1.2647 到 1.2023 是十二档价格刻度,每档间隔约 5.8 点,右侧相同数字为对称显示。中间的点与 O/X 字符,代表对应价位上被动挂单或成交痕迹的横向铺排。 注意 1.2589 到 1.2303 这七档,OX 字符连续出现且呈 OXOXOXO 交替,说明该区域 1.2531–1.2359 区间内多空换手密集;往下 1.2246 起 O 字符骤减为单行,价格向下穿透时阻力显著变薄。 把这类文本直接贴进小布盯盘的 AIGC 解析层,可自动识别堆叠带宽度。外汇与贵金属属高杠杆品种,此类密度分布只提示概率性停留区,不预示方向。 实操上,开 MT5 按 Ctrl+R 调出报价带,切到某货币对 M1 图,肉眼比对 OX 带与后续 K 线触碰次数,比任何指标都快。
「用风险反推手数而不是拍脑袋」
挂单派喜欢等价格来碰线,但更稳妥的做法是先定死每笔能亏多少权益百分比,再反算开仓手数。Ralph Vince 在《资金管理中的数学》里把这种每笔亏损风险作为可优化变量的思路叫优化,代码里对应的就是 opt_f 这个系数。 下面这段 MT5 函数把「可接受风险」翻译成具体挂单手数,直接能抄进 EA 里跑。外汇和贵金属杠杆高,权益回撤速度可能远超预期,用这套前先在策略测试器里用历史数据验一遍。 //+------------------------------------------------------------------+
| // | The function places an order with a precalculated volume |
|---|
//+------------------------------------------------------------------+ void PlaceOrder() { //--- Variables for calculating the lot uint digits_2_lot=(uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS); double trade_risk=AccountInfoDouble(ACCOUNT_EQUITY)*opt_f; double one_tick_loss_min_lot=SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE_LOSS)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); //--- Заполняем основные поля запроса trade_request.magic=magic; trade_request.symbol=symbol; trade_request.action=TRADE_ACTION_PENDING; trade_request.tp=NULL; trade_request.comment=NULL; trade_request.type_filling=NULL; trade_request.stoplimit=NULL; trade_request.type_time=NULL; trade_request.expiration=NULL; if(is_const_lot==true) { order_vol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); } else { order_vol=trade_risk/(MathAbs(trade_request.price-trade_request.sl)*MathPow(10,digits_2_lot)*one_tick_loss_min_lot)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); order_vol=MathMax(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN)); if(SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT)!=0) order_vol=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT)); order_vol=NormalizeDouble(order_vol,(int)MathAbs(MathLog10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)))); } //--- Place an order while(order_vol>0) { trade_request.volume=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX)); if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order); order_vol=order_vol-SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX); }; ticket=trade_result.order; }; 逐行拆一下关键处:digits_2_lot 取品种报价小数位,用来把价格差换算成点数;trade_risk 就是「账户权益 × opt_f」,比如 opt_f 设 0.01 代表每笔最多亏 1% 权益;one_tick_loss_min_lot 是最小步长合约对应的单跳亏损金额。 非固定手数分支里,order_vol 用 trade_risk 除以(止损距离点数 × 单跳亏损)再乘步长得到,随后被夹在 SYMBOL_VOLUME_MIN 和 SYMBOL_VOLUME_LIMIT 之间并归一化。最后 while 循环按 SYMBOL_VOLUME_MAX 分拆发送,避免单笔超上限被拒。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The function places an order with a precalculated volume | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PlaceOrder() { class=class="str">"cmt">//--- Variables for calculating the lot class="type">uint digits_2_lot=(class="type">uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS); class="type">class="kw">double trade_risk=AccountInfoDouble(ACCOUNT_EQUITY)*opt_f; class="type">class="kw">double one_tick_loss_min_lot=SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE_LOSS)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); class=class="str">"cmt">//--- Заполняем основные поля запроса trade_request.magic=magic; trade_request.symbol=symbol; trade_request.action=TRADE_ACTION_PENDING; trade_request.tp=NULL; trade_request.comment=NULL; trade_request.type_filling=NULL; trade_request.stoplimit=NULL; trade_request.type_time=NULL; trade_request.expiration=NULL; if(is_const_lot==true) { order_vol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); } else { order_vol=trade_risk/(MathAbs(trade_request.price-trade_request.sl)*MathPow(class="num">10,digits_2_lot)*one_tick_loss_min_lot)*SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); order_vol=MathMax(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN)); if(SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT)!=class="num">0) order_vol=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_LIMIT)); order_vol=NormalizeDouble(order_vol,(class="type">int)MathAbs(MathLog10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)))); } class=class="str">"cmt">//--- Place an order class="kw">while(order_vol>class="num">0) { trade_request.volume=MathMin(order_vol,SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX)); if(!OrderSend(trade_request,trade_result)) Print("Failed to send order #",trade_request.order); order_vol=order_vol-SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX); }; ticket=trade_result.order; };
◍ 回撤约束下的两种优化取舍
EA 优化时真正能用的准则其实就两类:要么锁定回报去压最低回撤,要么给定回撤上限去冲最大余额。外汇与贵金属杠杆高、跳空频繁,回撤失控往往比少赚更致命,后者更适合实盘风控。 我习惯用「不可接受回撤」做门槛,把 risk 当成优化变量一起跑。代码里的 OnTester 会在测试结束时先查相对权益回撤,超了直接判 0 分,没超才把净利润按品种精度返回去排序。 这套逻辑意味着:凡是回撤突破你设的 risk*100% 的参数的组合,根本进不了优选池。开 MT5 把 risk 从 0.1 调到 0.3 各跑一遍,能直观看到入选参数簇的收缩与扩张。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Result of strategy run in the testing mode | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double OnTester() { if(TesterStatistics(STAT_EQUITY_DDREL_PERCENT)>(risk*class="num">100)) class="kw">return(class="num">0); else class="kw">return(NormalizeDouble(TesterStatistics(STAT_PROFIT),(class="type">uint)SymbolInfoInteger(symbol,SYMBOL_DIGITS))); };
回撤破线才动优化
重优化频率没必要钉死在日历或成交笔数上。按周、按月、或每 50~100 笔交易重跑一遍参数,本质都是拍脑袋;真正该触发动作的信号,是系统实盘回撤是否越过了你事先定下的风险红线。 我给自己定的硬约束是:实盘回撤一旦超过 10%,就立刻重优化;只要回撤还在最大允许风险参数以内、系统按原逻辑正常输出,就不动它。外汇与贵金属杠杆高、跳空频繁,这条线必须写死在计划里,不能等亏到肉疼才想起来调。 换个角度说,优化准则在上一阶段怎么选,直接决定了后面什么时候该重来。准则偏保守,触发重优化的门槛就低;准则本身宽松,那你容忍的回撤空间也该同步放大,否则会陷入频繁重拟合的过优化陷阱。