自动交易的传说: 是少或多?(基础篇)
📘

自动交易的传说: 是少或多?(基础篇)

第 1/3 篇

「用真金白银验证交易假设」

很多关于自动交易的争论,本质是在赌「信号多一点好还是少一点好」。但纸面推演永远缺一环:没人替你的账户负责。 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 切换最直观的反馈;亏损单别扛,平掉比等反弹更省手续费。

MQL5 / C++
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>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4588</span> | \.....\.................................................................... | <span class="number">class="num">1.4588</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4521</span> | X\....X\................................................................... | <span class="number">class="num">1.4521</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4454</span> | XO\.\.XO\.................................................................. | <span class="number">class="num">1.4454</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4388</span> | XOX\X\XO.\................................................................. | <span class="number">class="num">1.4388</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4322</span> | XOXOXOXO..\................................................................ | <span class="number">class="num">1.4322</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4256</span> | XOXOXOXO...\....\.......................................................... | <span class="number">class="num">1.4256</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4191</span> | XOXO/OXO....\...X\......................................................... | <span class="number">class="num">1.4191</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4125</span> | XOX/.O/O.....\..XO\........................................................ | <span class="number">class="num">1.4125</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.4060</span> | XO/../.O......\.XO.\....................................................... | <span class="number">class="num">1.4060</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3996</span> | ./.....O.......\XO..\...................................................... | <span class="number">class="num">1.3996</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3932</span> | .......OX.......XO...\....................................................X | <span class="number">class="num">1.3932</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3868</span> | .......OXO..X.X.XOX...\.................................................X.X | <span class="number">class="num">1.3868</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3804</span> | .......OXO..XOXOXOXOX..\..............................................X.XOX | <span class="number">class="num">1.3804</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3740</span> | .......OXO..XOXOXOXOXO..\..............................\...........XOXOX | <span class="number">class="num">1.3740</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3677</span> | .......OXOX.XO.O.OXOXO...\................................X\..........XOXOX | <span class="number">class="num">1.3677</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3614</span> | .......OXOXOX....O.OXO....\...............................XO\.........XOXOX | <span class="number">class="num">1.3614</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3552</span> | .......O.OXOX...../OXO.....\..............................XO.\........XOXOX | <span class="number">class="num">1.3552</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3490</span> | .........OXOX..../.O.OX.....\.............................XO..\.......XOXO. | <span class="number">class="num">1.3490</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3428</span> | .........OXOX.../....OXO.....\X.\.........................XO...\\...X.XOX.. | <span class="number">class="num">1.3428</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3366</span> | .........O.OX../.....OXO......XOX\........................XO....X\..XOXOX.. | <span class="number">class="num">1.3366</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3305</span> | ...........OX./......OXO....X.XOXO\.....................X.XO....XO\.XOXO... | <span class="number">class="num">1.3305</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3243</span> | ...........OX/.......O.O....XOXOXOX\....................XOXO....XO.\XOX.../ | <span class="number">class="num">1.3243</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3183</span> | ...........O/..........OX...XOXOXOXO\...................XOXOX.X.XOX.XOX../. | <span class="number">class="num">1.3183</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3122</span> | .........../...........OXO..XOXOXOXO.\..........X...X.X.XOXOXOXOXOXOXO../.. | <span class="number">class="num">1.3122</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3062</span> | .......................OXOX.XOXO.OXO..\.........XOX.XOXOXOXOXOXOXOXOX../... | <span class="number">class="num">1.3062</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.3002</span> | .......................O.OXOXO...O/O...\........XOXOXOXOXO.OXO.OXOXO../.... | <span class="number">class="num">1.3002</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.2942</span> | .........................OXOX..../.O....\.......XOXOXOXOX..OX..OXOX../..... | <span class="number">class="num">1.2942</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.2882</span> | .........................O.OX.../..O.....\......XOXO.OXO...OX..OXOX./...... | <span class="number">class="num">1.2882</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.2823</span> | ...........................OX../...OX.....\.....XO...OX.../OX..O/OX/....... | <span class="number">class="num">1.2823</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="number">class="num">1.2764</span> | ...........................OX./....OXO.....\....X....OX../.O.../.O/........ | <span class="number">class="num">1.2764</span>
&nbsp;&nbsp;&nbsp;&nbsp;<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 分拆发送,避免单笔超上限被拒。

MQL5 / C++
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 各跑一遍,能直观看到入选参数簇的收缩与扩张。

MQL5 / C++
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%,就立刻重优化;只要回撤还在最大允许风险参数以内、系统按原逻辑正常输出,就不动它。外汇与贵金属杠杆高、跳空频繁,这条线必须写死在计划里,不能等亏到肉疼才想起来调。 换个角度说,优化准则在上一阶段怎么选,直接决定了后面什么时候该重来。准则偏保守,触发重优化的门槛就低;准则本身宽松,那你容忍的回撤空间也该同步放大,否则会陷入频繁重拟合的过优化陷阱。

常见问题

先拿极小仓位跑两周,只记录信号触发与盈亏,不中途改规则;若胜率和回撤都超出历史模拟区间,再考虑放大。贵金属和外汇波动大,小仓试错能保命。
栽在价格跳变的处理:手动能脑补的缺口,代码会按收盘价硬吃。务必在策略里显式定义跳空过滤,否则滑点吃掉你大半利润。
可以。小布盯盘的 AIGC 会直接标出密集成交区与稀薄区,你打开对应品种页就能看到,不用自己盯盘口数tick。
用账户净值×单笔承受百分比÷(止损点数×每点价值)得出手数;例如1%风险、30点止损、每点10美元,则手数=0.03。高风险品种建议把百分比压到0.5%内。
只有实盘回撤击穿预设红线(如15%)才启动优化,平时跑歪一点也别碰参数。频繁动优化容易过拟合,样本外大概率崩。