外汇套利交易:一款轻松上手的简单合成做市商机器人·进阶篇
📘

外汇套利交易:一款轻松上手的简单合成做市商机器人·进阶篇

第 2/3 篇

按货币对波动幅度调网格步长

EURUSD 的波动节奏偏慢,像深层洋流;EURJPY 这类日元交叉盘常出现突发行情,幅度明显更凶。用同一套网格参数硬套所有品种,回测和实盘都容易踩坑。 TrisWeb_Optimized 支持给每个交易品种单独设步长。笔者五年实操里,EURUSD 一类平稳货币对用 40 点首步、60 点续步;波动剧烈的日元交叉盘会把步长拉到 50 点和 80 点,避免过早密集加仓。 下面这段输入参数定义了三个符号各自的初始步长和订单间距,默认都是 40 / 60,实盘前应按品种波动改写:

MQL5 / C++
input group "Step Settings"
input class="type">int Step01_Symbol1 = class="num">40;      class=class="str">"cmt">// Initial step for Symbol1(in points)
input class="type">int Step02_Symbol1 = class="num">60;      class=class="str">"cmt">// Step between orders for Symbol1(in points)
input class="type">int Step01_Symbol2 = class="num">40;      class=class="str">"cmt">// Initial step for Symbol2(in points)
input class="type">int Step02_Symbol2 = class="num">60;      class=class="str">"cmt">// Step between orders for Symbol2(in points)
input class="type">int Step01_Symbol3 = class="num">40;      class=class="str">"cmt">// Initial step for Symbol3(in points)
input class="type">int Step02_Symbol3 = class="num">60;      class=class="str">"cmt">// Step between orders for Symbol3(in points)
逐行看:第1行声明参数分组"Step Settings",在 MT5 输入页里归为一类;第2、3行是 Symbol1 的首步与续步,单位点是 price point,默认 40 和 60;第4、5行和第6、7行同理对应 Symbol2、Symbol3。把日元交叉盘那组改成 50 / 80,就能在 MT5 策略测试器里对比滑点密度差异。外汇与贵金属杠杆高,参数放大后浮亏扩张速度可能加快,调步长前先用历史数据验证。

MQL5 / C++
input group "Step Settings"
input class="type">int Step01_Symbol1 = class="num">40;      class=class="str">"cmt">// Initial step for Symbol1(in points)
input class="type">int Step02_Symbol1 = class="num">60;      class=class="str">"cmt">// Step between orders for Symbol1(in points)
input class="type">int Step01_Symbol2 = class="num">40;      class=class="str">"cmt">// Initial step for Symbol2(in points)
input class="type">int Step02_Symbol2 = class="num">60;      class=class="str">"cmt">// Step between orders for Symbol2(in points)
input class="type">int Step01_Symbol3 = class="num">40;      class=class="str">"cmt">// Initial step for Symbol3(in points)
input class="type">int Step02_Symbol3 = class="num">60;      class=class="str">"cmt">// Step between orders for Symbol3(in points)

◍ 让手数跟着余额和品种跑

固定手数只在账户规模不变、品种点值稳定时才勉强能用。一旦余额波动或切到小众货币对,死扛固定手数要么仓位轻到没意义,要么一单就把账户打穿。 TrisWeb_Optimized 里那套 CalculateOptimalLotSize() 的思路值得直接抄:它先读账户余额和风控百分比,再抓品种的最小手数、手数步长,最后按报价货币做点值修正。日元、黄金、白银的报价货币分别是 JPY / XAU / XAG,它们的点值相对主货币对差出两个数量级——忽略这层,EA 算出来的手数会直接失真。 实测里最常见的坑是 USDJPY:同样 20 点止损,EURUSD 每标准手约 2 美元/点,USDJPY 每标准手约 0.2 美元/点(视报价精度)。若代码不把 pointCost 乘 100,机器人会按主货币对逻辑下极小仓,或反向爆仓。外汇与贵金属杠杆高,自适应手数只降低仓位错配概率,不消除本金回撤风险。 下面这段是原函数骨架,重点看开关判断和品种参数抓取,省略号处是具体手数折算: 别把点值修正漏掉 JPY / XAU / XAG 的 quoteCurrency 分支必须把 pointCost 乘 100.0,否则小众品种手数会算飞。这是很多开源 EA 回测漂亮、实盘崩掉的根因。

MQL5 / C++
class="type">class="kw">double CalculateOptimalLotSize(class="type">class="kw">string symbol, class="type">class="kw">double baseSize)
{
  if(!AutoLotOptimization)
    class="kw">return baseSize;
    
  class="type">class="kw">double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
  class="type">class="kw">double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
  class="type">class="kw">double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
  class=class="str">"cmt">// ... the rest of the calculation code
}
class=class="str">"cmt">// Adjustment for pairs with JPY and other exotic currencies
class="type">class="kw">string quoteCurrency = StringSubstr(symbol, class="num">3, class="num">3);
if(quoteCurrency == "JPY" || quoteCurrency == "XAU" || quoteCurrency == "XAG")
  pointCost *= class="num">100.0;

「EA的昼夜节律:别让网格在垃圾时段自己跑」

外汇虽是24小时连续市场,但流动性分布极不均匀。以欧美经纪商报价为例,东京深夜至伦敦开盘前(大致本地时间0点—6点)点差常扩张到日常均值的1.5到3倍,假突破和异常毛刺显著增多,这时候让网格EA硬跑,磨损大概率高于收益。 TrisWeb_Optimized 用 IsWorkTime() 把‘能交易时段’写成可配置边界,配合 CheckNewDayAndWorkTime() 做每日清零:新的一天且处在工作时间内,就删光所有挂单与存续订单,从干净状态按当前市况重铺网格。这样系统不会背着昨天的过期单过夜,长期看能压住积压风险。 代码里时间比较都用‘时分转秒数’做标量,避开了跨日结构体比大小的坑。StartTimeHour/Min 与 EndTimeHour/Min 是外部参数,你在 MT5 里改这两个区间,就能让 EA 避开自己经纪商点差最烂的那段。 别把正态当圣经 上面区间是作者按一类经纪商经验设的,你的平台夜盘点差峰值可能偏移1到2小时,直接用默认值可能正好卡在毛刺里。开 MT5 观察一周真实点差曲线再填参数才是正路。

MQL5 / C++
class="type">bool IsWorkTime()
{
   class="type">MqlDateTime currentTime;
   TimeToStruct(TimeLocal(), currentTime);
   
   class="type">class="kw">datetime currentTimeSeconds = HoursMinutesToSeconds(currentTime.hour, currentTime.min);
   class="type">class="kw">datetime startTimeSeconds = HoursMinutesToSeconds(StartTimeHour, StartTimeMin);
   class="type">class="kw">datetime endTimeSeconds = HoursMinutesToSeconds(EndTimeHour, EndTimeMin);
   
   class="kw">return (startTimeSeconds <= currentTimeSeconds && currentTimeSeconds <= endTimeSeconds);
}
class="type">void CheckNewDayAndWorkTime()
{
   if(IsNewDay && IsNewDayReset && IsWorkTime())
   {
      DeleteAllOrders();
      IsNewDay = false;
   }
   
   if(!IsWorkTime())
   {
      IsNewDay = true;
   }
}

离场别只看裸利润

TrisWeb_Optimized 的平仓逻辑核心只有一条:账户总盈利摸到预设阈值就清场。但它的总盈利不是裸价格差,而是把隔夜利息和佣金一并算进去了——这对过夜持仓的外汇、贵金属交易者很关键,这类品种隔夜 swap 可能为正也可能为负,忽略它容易把实际亏损的组合误判为盈利。 下面这段 MQL5 片段展示了统计与触发思路:先汇总三组分单的数量与利润,无单时择时建仓,有单且总利大于 Profit 就全平。 // 订单总数 int totalOrders = ordersCount1 + ordersCount2 + ordersCount3; // 三组利润合计 double totalProfit = profit1 + profit2 + profit3; // 无订单时建仓,有订单时查盈利 if(totalOrders == 0) { if(IsWorkTime()) { PlaceInitialOrders(); } } else if(totalProfit > Profit) { DeleteAllOrders(); } // 单仓真实总收益 = 浮动利 + 隔夜息 + 佣金 double swap = PositionGetDouble(POSITION_SWAP); double profit = PositionGetDouble(POSITION_PROFIT); double commission = PositionGetDouble(POSITION_COMMISSION); double totalProfitForPosition = profit + swap + commission; 开盘前把 Profit 参数和你的经纪商 swap 表对一遍,在 MT5 策略测试器里用真实佣金模型跑一轮,看回测中因负 swap 被拖垮的单子占比——外汇与贵金属高杠杆下,这类隐性成本可能让胜率虚高。

MQL5 / C++
class="type">int totalOrders = ordersCount1 + ordersCount2 + ordersCount3;
class="type">class="kw">double totalProfit = profit1 + profit2 + profit3;
class=class="str">"cmt">// If there are no orders, create them; if there are, check for profit.
if(totalOrders == class="num">0)
{
  if(IsWorkTime())
  {
    PlaceInitialOrders();
  }
}
else if(totalProfit > Profit)
{
  DeleteAllOrders();
}
class="type">class="kw">double swap = PositionGetDouble(POSITION_SWAP);
class="type">class="kw">double profit = PositionGetDouble(POSITION_PROFIT);
class="type">class="kw">double commission = PositionGetDouble(POSITION_COMMISSION);
class="type">class="kw">double totalProfitForPosition = profit + swap + commission;

◍ 偏差参数是挂单成交的隐形闸门

挂单能不能在剧烈行情里活着成交,往往卡在一个被忽视的字段:deviation。它定义的是订单允许在请求价之外多大范围内撮合,相当于你给流动性的容差底线。 设太小,报价一跳就被拒单,错过入口;设太大,可能以远超心理位的烂价成交。TrisWeb_Optimized 默认 Deviation=3 点,意味着 Buy Stop 允许比挂单价差 3 点内成交,再多就不动。 下面这段是挂 Buy Stop 的核心函数,deviation 直接读全局输入变量。外汇与贵金属杠杆高、滑点凶,3 点容差在亚盘清淡时偏紧,在非农秒级波动时可能偏松,需按品种波动率手动调。

MQL5 / C++
<span class="keyword">class="type">bool</span> OpenBuyStop(<span class="keyword">class="type">class="kw">string</span> symbol, <span class="keyword">class="type">class="kw">double</span> volume, <span class="keyword">class="type">class="kw">double</span> openPrice)
{
&nbsp;&nbsp; <span class="predefines">class="type">MqlTradeRequest</span> request = {};
&nbsp;&nbsp; <span class="predefines">class="type">MqlTradeResult</span> result = {};
&nbsp;&nbsp; 
&nbsp;&nbsp; request.action = <span class="macro">TRADE_ACTION_PENDING</span>;
&nbsp;&nbsp; request.symbol = symbol;
&nbsp;&nbsp; request.volume = volume;
&nbsp;&nbsp; request.type = <span class="macro">ORDER_TYPE_BUY_STOP</span>;
&nbsp;&nbsp; request.price = openPrice;
&nbsp;&nbsp; request.deviation = Deviation;
&nbsp;&nbsp; request.magic = EXPERT_MAGIC;
&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">if</span>(UseCommentsInOrders)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.comment = OrderComment;
&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">if</span>(!<span class="functions">OrderSend</span>(request, result))
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"OrderSend error "</span>, <span class="functions">GetLastError</span>(), <span class="class="type">class="kw">string">" retcode: "</span>, result.retcode);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">false</span>;
&nbsp;&nbsp; }
&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="macro">true</span>;
}
<span class="keyword">input</span> <span class="keyword">class="type">int</span> Deviation = <span class="number">class="num">3</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Acceptable price deviation</span>
逐行看:MqlTradeRequest 清零后填 action 为挂单、type 为 BUY_STOP;price 用传入 openPrice,deviation 绑定输入变量 Deviation(默认 3);magic 区分ea订单。OrderSend 失败就打印错误码和 retcode 并返回 false,成功返 true。 开 MT5 把这段塞进 ea,把 Deviation 改成 1 和 10 各跑一次回测,能直接看到拒单率和成交价分布如何被这一个整数拽着走。

MQL5 / C++
<span class="keyword">class="type">bool</span> OpenBuyStop(<span class="keyword">class="type">class="kw">string</span> symbol, <span class="keyword">class="type">class="kw">double</span> volume, <span class="keyword">class="type">class="kw">double</span> openPrice)
{
&nbsp;&nbsp; <span class="predefines">class="type">MqlTradeRequest</span> request = {};
&nbsp;&nbsp; <span class="predefines">class="type">MqlTradeResult</span> result = {};
&nbsp;&nbsp; 
&nbsp;&nbsp; request.action = <span class="macro">TRADE_ACTION_PENDING</span>;
&nbsp;&nbsp; request.symbol = symbol;
&nbsp;&nbsp; request.volume = volume;
&nbsp;&nbsp; request.type = <span class="macro">ORDER_TYPE_BUY_STOP</span>;
&nbsp;&nbsp; request.price = openPrice;
&nbsp;&nbsp; request.deviation = Deviation;
&nbsp;&nbsp; request.magic = EXPERT_MAGIC;
&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">if</span>(UseCommentsInOrders)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.comment = OrderComment;
&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">if</span>(!<span class="functions">OrderSend</span>(request, result))
&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"OrderSend error "</span>, <span class="functions">GetLastError</span>(), <span class="class="type">class="kw">string">" retcode: "</span>, result.retcode);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">false</span>;
&nbsp;&nbsp; }
&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="macro">true</span>;
}
<span class="keyword">input</span> <span class="keyword">class="type">int</span> Deviation = <span class="number">class="num">3</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Acceptable price deviation</span>

常见问题

按近期 ATR 的 0.5~1 倍设步长:欧美 ATR 约 30 点就设 15~30 点,镑美 ATR 约 60 点就设 30~60 点,波动大的品种步长同步放大。
用余额百分比下单:每 1000 余额给 0.01 手,5000 就自动跑 0.05;同时按品种点值限制单笔风险不超过余额 2%。
小布会标注各品种活跃与垃圾时段,并把低流动性窗口标红,你照着关掉 EA 的夜间权限就行。
要算滑点、点差和隔夜利息后的净收益,并在总浮盈覆盖已发生成本 1.2 倍时才离场,避免假盈利。
偏差设 2~3 个点多数时候能成;超过 5 点虽易成交但易买贵卖贱,建议回测找品种最优值。