学习为什么、以及如何设计算法交易系统·综合运用
(3/3)· 手敲完开平改三件套,多数人卡在订单生命周期的最后一米
把参数暴露给面板而非改代码
写 MT5 脚本时,最笨的做法是每次调止损止盈都进源码改数字。MQL5 用 #property 和 input 修饰符把外部参数交给客户端,运行前弹窗让你填,省去重编译。 #property script_show_inputs 这一行很关键:加上它,脚本点运行会先弹输入窗口;不加则跳过确认直接跑。input 前面的类型声明(如 int)决定面板里填的是整数还是小数。 下面这段代码把 TP、SL 做成输入变量,默认都是 10。注意它按 5 位报价券商处理:用 0.00001 乘参数,所以默认 10 代表 1 个 pip(10 点)。外汇和贵金属杠杆高,参数设错可能瞬间放大回撤风险,先在策略测试器跑一遍再上实盘。 代码逐行拆解: #property copyright / link / version 只是给客户端看的元信息,不影响逻辑;#property script_show_inputs 触发运行前弹窗。 input int TakeProfit=10 与 input int StopLoss=10 声明两个外部整数,用户可在面板改。 OnStart 里先取当前买价并 NormalizeDouble 到品种精度;TakeProfitLevel = Bid + TakeProfit*0.00001 算出止盈价,StopLossLevel 同理向下减;最后三个 Alert 把数值弹出来,方便你核对面板输入是否生效。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Trader Inputs.mq5 | class=class="str">"cmt">//| Copyright class="num">2022, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int TakeProfit = class="num">10; class="kw">input class="type">int StopLoss = class="num">10; class="type">void OnStart() { class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class="type">class="kw">double TakeProfitLevel = Bid + TakeProfit * class="num">0.00001; class=class="str">"cmt">// class="num">0.00001 (class="num">5 digits broker, so multiplied by class="num">10) class="type">class="kw">double StopLossLevel = Bid - StopLoss * class="num">0.00001; Alert("Price now = " + class="type">class="kw">string(Bid)); Alert("TakeProfitLevel = ", TakeProfitLevel); Alert("StopLossLevel = ", StopLossLevel); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 脚本一键市价买入的写法
用 MQL5 写开单脚本,核心是把报价、账户状态、止损止盈价位先算清楚,再调用 CTrade 的 Buy 方法。下面这段脚本在 MT5 里以脚本方式运行后,会在净值不小于余额时直接下 0.01 手市价多单。 代码中 TakeProfit 和 StopLoss 以 input 暴露,默认分别是 150 点和 100 点;按 0.00001 每点折算,EURUSD 这类五位数报价品种上对应约 15 点和 10 点距离。开单后脚本还会倒序遍历当前持仓,把订单号、TP、SL 用 Alert 弹窗打出来,方便你当场核对。 外汇与贵金属杠杆高,0.01 手虽小,若 StopLoss 被快速扫掉仍可能吃掉账户可观比例,跑脚本前先在策略测试器用历史数据验证一遍。 代码逐行拆解: //+--- 开头注释块仅说明文件信息,无执行意义 #property script_show_inputs 让外部参数在运行弹窗可见 #include <Trade\Trade.mqh> 引入交易类,CTrade trade 实例化后才有 Buy/Sell 接口 input int TakeProfit=150 / StopLoss=100 为可调点数 OnStart 是脚本入口:先 NormalizeDouble 取精确保价 Ask/Bid,_Digits 跟随品种小数位 AccountInfoDouble 取 BALANCE 与 EQUITY,作为开单风控判断 TakeProfitLevel = Ask+150*0.00001 算出挂 TP 价,StopLossLevel = Ask-100*0.00001 算出挂 SL 价 if(Equity>=Balance) 才 trade.Buy(0.01,NULL,Ask,StopLossLevel,TakeProfitLevel,NULL) 下市价多单 随后 for 循环从 PositionsTotal()-1 到 0,用 PositionGetTicket 拿单数,Alert 输出票据与价位
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TEST.mq5 | class=class="str">"cmt">//| Copyright class="num">2022, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="macro">#include <Trade\Trade.mqh> CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int TakeProfit = class="num">150; class="kw">input class="type">int StopLoss = class="num">100; class="type">void OnStart() { class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class="type">class="kw">double Balance = AccountInfoDouble(ACCOUNT_BALANCE); class="type">class="kw">double Equity = AccountInfoDouble(ACCOUNT_EQUITY); class="type">class="kw">double TakeProfitLevel = (Ask+TakeProfit*class="num">0.00001); class="type">class="kw">double StopLossLevel = (Ask-StopLoss*class="num">0.00001); if(Equity >= Balance) trade.Buy(class="num">0.01,NULL,Ask,StopLossLevel,TakeProfitLevel,NULL); for (class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); class="type">ENUM_POSITION_TYPE position = class="type">ENUM_POSITION_TYPE(PositionGetInteger(POSITION_TYPE)); Alert(" Order Ticket # ", ticket); Alert("TakeProfit = ", TakeProfitLevel); Alert("StopLoss = ", StopLossLevel); } } class=class="str">"cmt">//+------------------------------------------------------------------+
「在 MT5 脚本里埋好出错警报」
EA 或脚本跑起来之后,交易者往往只能看到界面卡死或没下单,却不知道哪一步出了问题。把错误检查写进每一处关键调用,能在异常发生的瞬间弹出提示,避免资金在无人察觉时被错误信号摆布。外汇与贵金属杠杆高,这类防护直接关系账户安全。 上面这段脚本演示了一种基础做法:先取 Ask/Bid 并归一化到当前品种小数位,再用账户权益是否不低于余额作为开仓条件。下单价用 0.01 手,止盈、止损按输入的 150 点、100 点换算(乘 0.00001),点值假设适用于多数 5 位报价品种。 开仓后遍历持仓,用 PositionGetTicket 拿ticket;若返回值 ≤0 说明取持仓失败,弹“Error!”报警。否则把 ticket 号和计算的 TP/SL 水平逐一 Alert 出来,方便你当场核对挂单参数有没有被平台拒绝或滑点吃掉。 别把警报当摆设 把这段脚本拖进 MT5 回测,故意把 StopLoss 设成超过经纪商限制的值,你会立刻在日志看到 Error! 而非默默不下单。实盘前先用这种破坏性测试确认报警链路通着。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TEST.mq5 | class=class="str">"cmt">//| Copyright class="num">2022, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="macro">#include <Trade\Trade.mqh> CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int TakeProfit = class="num">150; class="kw">input class="type">int StopLoss = class="num">100; class="type">void OnStart() { class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class="type">class="kw">double Balance = AccountInfoDouble(ACCOUNT_BALANCE); class="type">class="kw">double Equity = AccountInfoDouble(ACCOUNT_EQUITY); class="type">class="kw">double TakeProfitLevel = (Ask+TakeProfit*class="num">0.00001); class="type">class="kw">double StopLossLevel = (Ask-StopLoss*class="num">0.00001); if(Equity >= Balance) trade.Buy(class="num">0.01,NULL,Ask,StopLossLevel,TakeProfitLevel,NULL); for (class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); class="type">ENUM_POSITION_TYPE position = class="type">ENUM_POSITION_TYPE(PositionGetInteger(POSITION_TYPE)); if (ticket <= class="num">0) { Alert("Error!"); class=class="str">"cmt">//in Case of error and the order did not open, appears "Error!" } else { Alert("Your ticket # is: " + class="type">class="kw">string(ticket)); Alert("TakeProfit = ", TakeProfitLevel); Alert("StopLoss = ", StopLossLevel); } } } class=class="str">"cmt">//+------------------------------------------------------------------+
用脚本把多单开出来再平掉
平单在 MQL5 里不需要自己去拼 OrderSend 的关闭指令,CTrade 类的 PositionClose 直接吃持仓 ticket 就能处理。下面这段脚本先在当前品种以市价开 0.01 手多单,随后遍历所有持仓逐一平仓,适合在策略测试器里跑一遍看成交与关闭的调用顺序。 外汇与贵金属杠杆高,脚本里写死 0.01 手和固定点数止损止盈,实盘前务必在模拟盘验证,滑点和点差可能让平仓价格偏离预期。 代码逐行拆解: #include <Trade\Trade.mqh> 引入交易类,CTrade trade 声明一个交易对象。 input int TakeProfit = 150; input int StopLoss = 100; 外部输入止盈 150 点、止损 100 点(按 0.00001 每点折算)。 OnStart 内先取 NormalizeDouble 后的 Ask/Bid,以及账户 Balance、Equity。 TakeProfitLevel = Ask + 150*0.00001,StopLossLevel = Ask - 100*0.00001,算出挂单价位。 trade.Buy(0.01, NULL, Ask, StopLossLevel, TakeProfitLevel, NULL) 市价开 0.01 手多单并附带止损止盈。 for 循环从 PositionsTotal()-1 到 0,PositionGetTicket(i) 取 ticket,PositionGetInteger(POSITION_TYPE) 判类型。 循环里 Alert 弹出 ticket 与盈亏价位,Sleep(2000) 停 2 秒,trade.PositionClose(ticket, -1) 按市价平该仓,-1 表示不指定偏离。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TEST.mq5 | class=class="str">"cmt">//| Copyright class="num">2022, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="macro">#include <Trade\Trade.mqh> CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int TakeProfit = class="num">150; class="kw">input class="type">int StopLoss = class="num">100; class="type">void OnStart() { class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class="type">class="kw">double Balance = AccountInfoDouble(ACCOUNT_BALANCE); class="type">class="kw">double Equity = AccountInfoDouble(ACCOUNT_EQUITY); class="type">class="kw">double TakeProfitLevel = (Ask+TakeProfit*class="num">0.00001); class="type">class="kw">double StopLossLevel = (Ask-StopLoss*class="num">0.00001); trade.Buy(class="num">0.01,NULL,Ask,StopLossLevel,TakeProfitLevel,NULL); for (class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); class="type">ENUM_POSITION_TYPE position = class="type">ENUM_POSITION_TYPE(PositionGetInteger(POSITION_TYPE)); Alert(" Order Ticket # ", ticket); Alert("TakeProfit = ", TakeProfitLevel); Alert("StopLoss = ", StopLossLevel); Sleep(class="num">2000); trade.PositionClose(ticket,-class="num">1); Alert("Order Closed..."); } } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用 PositionModify 把止损止盈挪到新价位
下完单不代表仓位就定型了。MT5 里通过 CTrade 的 PositionModify 方法,可以在持仓过程中把已有头寸的 SL/TP 改写成新的价位,用来应对行情跳变或手动风控重算。 上面这段脚本先以 0.01 手吃进一个市价多单,TP 设在 Ask+150 点、SL 设在 Ask-100 点(按 0.00001 每点折算)。随后用 PositionsTotal 倒序遍历所有持仓,把每张单的 ticket 抓出来。 关键动作在 Sleep(5000) 之后:把 TP 再外推 0.00100、SL 再内收 0.00050,然后调用 trade.PositionModify(ticket, StopLossLevel2, TakeProfitLevel2)。改完用 Alert 把新价位打印出来,你在 MT5 终端的「专家」标签里能直接看到两次弹窗数值差。 外汇和贵金属杠杆高、点差跳动快,修改挂单或持仓止损时可能因为价格已穿透而返回失败,实盘前务必在策略测试器用历史数据跑一遍确认返回码。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TEST.mq5 | class=class="str">"cmt">//| Copyright class="num">2022, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class="macro">#include <Trade\Trade.mqh> CTrade trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">input class="type">int TakeProfit = class="num">150; class="kw">input class="type">int StopLoss = class="num">100; class="type">void OnStart() { class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class="type">class="kw">double Balance = AccountInfoDouble(ACCOUNT_BALANCE); class="type">class="kw">double Equity = AccountInfoDouble(ACCOUNT_EQUITY); class="type">class="kw">double TakeProfitLevel = (Ask+TakeProfit*class="num">0.00001); class="type">class="kw">double StopLossLevel = (Ask-StopLoss*class="num">0.00001); class="type">class="kw">double TakeProfitLevel2 = (TakeProfitLevel+class="num">0.00100); class="type">class="kw">double StopLossLevel2 = (StopLossLevel-class="num">0.00050); trade.Buy(class="num">0.01,NULL,Ask,StopLossLevel,TakeProfitLevel,NULL); for (class="type">int i=PositionsTotal()-class="num">1; i>=class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); class="type">ENUM_POSITION_TYPE position = class="type">ENUM_POSITION_TYPE(PositionGetInteger(POSITION_TYPE)); Alert(" Order Ticket # ", ticket); Alert("TakeProfit = ", TakeProfitLevel); Alert("StopLoss = ", StopLossLevel); Sleep(class="num">5000); trade.PositionModify(ticket,StopLossLevel2,TakeProfitLevel2); Alert("Order Modified..."); Alert("Modified TakeProfit = ", TakeProfitLevel2); Alert("Modified StopLoss = ", StopLossLevel2); } } class=class="str">"cmt">//+------------------------------------------------------------------+
「把双均线交叉写成EA并在图表上喊单」
在 MetaEditor 里新建一个「智能交易系统」项目,就能把短周期上穿/下穿长周期均线的逻辑直接自动化。这里用的蓝图很直白:20 周期 SMA 上穿 50 周期 SMA 视为买入信号,下穿则视为卖出信号,EA 只在图表左上角用 Comment 打出 BUY / SELL,不下单。 代码里先用 iMA 拿到两个均线句柄,周期分别填 20 和 50,平滑方式都是 MODE_SMA、价格取 PRICE_CLOSE。随后用 ArraySetAsSeries 把数组按当前 K 线倒序排列,CopyBuffer 各取最近 3 根 K 线的数值——留 3 根是为了用 [0] 和 [1] 比前后状态。 信号判定只看两根 K 线的关系:若本根 20 均线上方、前一根在下方,判 BUY;反过来本根在下、前一根在上,判 SELL。这种写法在欧元/美元 15 分钟图上平均每天可能触发 1~3 次交叉提示,但外汇和贵金属波动剧烈,纯双均线在震荡市容易连续假信号,实盘前务必自行加过滤。 编译完把 EA 拖进图表,记得勾选「允许算法交易」,日志里出现加载成功才算挂上。它只是 MQL5 入门载体,真要跑实盘你得自己补滑点、点差和仓位控制——原文也明确说这例子本身不承担策略盈利责任。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SMA crossover.mq5 | class=class="str">"cmt">//| Copyright class="num">2022, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2022, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//create an array for several prices class="type">class="kw">double myMovingAverageArray1[], myMovingAverageArray2[]; class=class="str">"cmt">//define the properties of MAs - simple MA, 1st class="num">20 / 2nd class="num">50 class="type">int movingAverage1 = iMA(_Symbol, _Period, class="num">20, class="num">0, MODE_SMA, PRICE_CLOSE); class="type">int movingAverage2 = iMA(_Symbol,_Period,class="num">50,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//sort the price arrays class="num">1, class="num">2 from current candle ArraySetAsSeries(myMovingAverageArray1,true); ArraySetAsSeries(myMovingAverageArray2,true); class=class="str">"cmt">//Defined MA1, MA2 - one line - currentcandle, class="num">3 candles - store result CopyBuffer(movingAverage1,class="num">0,class="num">0,class="num">3,myMovingAverageArray1); CopyBuffer(movingAverage2,class="num">0,class="num">0,class="num">3,myMovingAverageArray2); class=class="str">"cmt">//Check if we have a buy entry signal if ( (myMovingAverageArray1[class="num">0]>myMovingAverageArray2[class="num">0]) && (myMovingAverageArray1[class="num">1]<myMovingAverageArray2[class="num">1]) ) { Comment("BUY"); } class=class="str">"cmt">//check if we have a sell entry signal if ( (myMovingAverageArray1[class="num">0]<myMovingAverageArray2[class="num">0]) && (myMovingAverageArray1[class="num">1]>myMovingAverageArray2[class="num">1]) ) { Comment("SELL");
未命名代码段的收口结构
这段 MQL5 片段只露出函数体的右大括号闭合部分,外层还有一个被缩进的 } 以及 //+------------------------------------------------------------------+ 注释行,说明当前正处于某个自定义函数或脚本模块的收尾位置。
从缩进看,内部 } 对应前一节条件判断或循环的结束,外部 } 结束整个函数作用域;注释行是 MT5 代码里常见的模块分隔符,不参与编译但方便在 MetaEditor 里快速定位边界。
如果你在 MT5 里粘贴不完整代码导致编译报 'unexpected end of file',优先检查这类闭合括号是否配对,以及注释行前后是否混入了不可见字符。
} } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 别急着下结论
这套 MQL5 入门材料里附带的 13 个源码文件,体积从 0.76 KB 的 Hello_Worldb.mq5 到 2.04 KB 的 Modifying_Orders.mq5 不等,全部可在 MT5 里直接编译跑通,先逐个打开看结构比急着写策略更实际。 外汇和贵金属杠杆高、滑点突发的特性,决定了任何算法系统都只是概率工具,回测顺滑不代表实盘能活过流动性真空。 把 Boolean_Operations、While_Loop 这些基础例程改两个参数,用策略测试器跑 2023 年 XAUUSD 的 M15,观察胜率漂移,你会对「盈利系统」这个词少点幻觉。