外汇套利交易:一款轻松上手的简单合成做市商机器人·进阶篇
按货币对波动幅度调网格步长
EURUSD 的波动节奏偏慢,像深层洋流;EURJPY 这类日元交叉盘常出现突发行情,幅度明显更凶。用同一套网格参数硬套所有品种,回测和实盘都容易踩坑。 TrisWeb_Optimized 支持给每个交易品种单独设步长。笔者五年实操里,EURUSD 一类平稳货币对用 40 点首步、60 点续步;波动剧烈的日元交叉盘会把步长拉到 50 点和 80 点,避免过早密集加仓。 下面这段输入参数定义了三个符号各自的初始步长和订单间距,默认都是 40 / 60,实盘前应按品种波动改写:
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)
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 回测漂亮、实盘崩掉的根因。
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 观察一周真实点差曲线再填参数才是正路。
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 被拖垮的单子占比——外汇与贵金属高杠杆下,这类隐性成本可能让胜率虚高。
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 点容差在亚盘清淡时偏紧,在非农秒级波动时可能偏松,需按品种波动率手动调。
<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) { <span class="predefines">class="type">MqlTradeRequest</span> request = {}; <span class="predefines">class="type">MqlTradeResult</span> result = {}; request.action = <span class="macro">TRADE_ACTION_PENDING</span>; request.symbol = symbol; request.volume = volume; request.type = <span class="macro">ORDER_TYPE_BUY_STOP</span>; request.price = openPrice; request.deviation = Deviation; request.magic = EXPERT_MAGIC; <span class="keyword">if</span>(UseCommentsInOrders) request.comment = OrderComment; <span class="keyword">if</span>(!<span class="functions">OrderSend</span>(request, result)) { <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); <span class="keyword">class="kw">return</span> <span class="macro">false</span>; } <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>; <span class="comment">class=class="str">"cmt">// Acceptable price deviation</span>
<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) { <span class="predefines">class="type">MqlTradeRequest</span> request = {}; <span class="predefines">class="type">MqlTradeResult</span> result = {}; request.action = <span class="macro">TRADE_ACTION_PENDING</span>; request.symbol = symbol; request.volume = volume; request.type = <span class="macro">ORDER_TYPE_BUY_STOP</span>; request.price = openPrice; request.deviation = Deviation; request.magic = EXPERT_MAGIC; <span class="keyword">if</span>(UseCommentsInOrders) request.comment = OrderComment; <span class="keyword">if</span>(!<span class="functions">OrderSend</span>(request, result)) { <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); <span class="keyword">class="kw">return</span> <span class="macro">false</span>; } <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>; <span class="comment">class=class="str">"cmt">// Acceptable price deviation</span>