开发一个跨平台的EA交易来根据风险设置止损和获利(基础篇)
用风险百分比反推止损与止盈距离
很多新手 EA 直接写死 30 点止损、60 点止盈,却忽略了账户净值波动带来的风险错位。更稳妥的做法是:先定每笔交易愿意亏多少百分比的权益,再据此反算手数和止损价位。 以 1 万美金账户、单笔风险 1% 为例,可承受亏损为 100 美金;若当前 EURUSD 点值约 10 美金/标准手/点,则最大止损距离约为 10 点(100 ÷ 10)。这套逻辑在 MT5 里用 OrderCalcProfit / SymbolInfoDouble 就能实时算出来,不依赖写死参数。 外汇与贵金属杠杆高、滑点频发,用风险百分比控仓只能降低爆仓概率,无法消除本金回撤风险,实盘前务必在策略测试器用历史数据跑一遍。
◍ 手算仓位是在给价格跑赢你留时间
资金管理的铁律是单笔亏损不超过存款的 N%,N 由交易者自定。要守这条线,就必须按止损距离反推手数。 不少课程会甩给你一张 Excel,让你填止损值、自动出手数。听着省事,其实坑不少:手算加翻表起码耗一分钟以上,等数算完,报价早滑离原计划进场位了。 更麻烦的是人工环节多、易填错。我们换个做法——写个 EA,在可视化模式里直接点出建仓价和止损价,EA 依据你设的风险百分比反推手数并双向开仓,把中间步骤全吞掉。外汇与贵金属杠杆高,错一手数可能放大亏损,自动化前先在小账户验逻辑。
「用比率锁死盈亏比,别靠肉眼估」
EA 要做的第二件事,是按选定的 TP 与 SL 比率自动挂获利价。圈内老手(如 Gerchik)一贯建议:获利至少要是止损的 3 倍——40 点止损就得配 120 点以上获利;价格够不到这空间,就别进。 为了统计省事,最好固定用同一 SL/TP 比率,比如 3:1、4:1,具体选几看你的交易目标。问题是手工算太费时,Excel 不是唯一解,图表上瞎猜更不靠谱。 EA 里直接给一个「获利/止损比」输入参数就能解决:填 4 就代表 4 比 1,开仓时自动把 TP 设为止损值的 4 倍。 EA 形态上有个坑:初版用了弹窗对话框改参数,好处是配一次自动存,坏处是窗口占满图表、挡住价格变动,首次设定后纯属干扰。后续改出隐藏窗口版(市集仍有)和纯输入参数版;纯参数版每次启动要重配或读文件,最省事的是改代码默认项——本文就做无对话框版的简化克隆。外汇与贵金属波动剧烈,杠杆交易高风险,参数比率只是概率优势,不保证每笔达标。
EA 输入参数里真正影响风控的几个开关
EA 的入场动作由止损逻辑兜底,所以先盯住前两个参数:Stop Loss type 与 Stop Loss value in $ or %。前者决定止损按美元还是按存款百分比计,后者填具体数值;若选百分比,上限被锁死在存款的 5%,这是为了防止手滑把仓位参数设爆。 Stop Loss value in $ or % 表达的是你这笔单子止损时愿意亏掉的金额,而另一个 Stop Loss value in cents (keys 7 and 8) 不是金额,是开盘价到止损触发价之间的点数距离。EA 预留了快捷键,比如你习惯 7 分止损,按一下键盘 7,止损就自动挂在离现价 7 分的位置,后面会细拆这套映射。 手数是按 Stop Loss value in $ or % 反推出来的。当最小允许手数算出来仍让单笔风险高于设定值时,参数 No deal entry if with min. lot the risk is greater than specified 决定是硬上还是弃单——默认是拦住不开。 限价单有寿命:Cancel limit order after, hours 以小时计,设 2 就代表挂单 2 小时不触发就撤。Take Profit multiplier 默认 4,即自动止盈价是止损可能亏损的 4 倍距离;想改盈亏比直接动这个数。 剩下能改的还有幻数、订单注释、界面语言(英/俄,默认俄),都属于不影响风控的皮外伤。
◍ 跨平台开仓函数怎么写才不踩坑
写一套能同时跑在 MT4 和 MT5 的 EA,开仓逻辑必须拆两条路。两个平台的订单接口不在一套命名体系下,硬塞同一段代码只会在编译期就报错,所以这里用条件编译 #ifdef __MQL5__ 把两边分开,其余共用逻辑放外面。
MT5 多了一个绕不开的参数:订单执行类型。不同券商支持的模式不一样,最常见的是 ORDER_FILLING_FOK(全部成交或全不成交),所以代码里把它设成默认枚举值 FOK,真遇到不支持的经纪商再手动切 RETURN 或 IOC。
下面这段是开仓/挂单函数的核心骨架,用 pdxSendOrder() 统一收口。它先按品种小数位归一化 sl/tp/price,MT4 分支里若价格没传就实时取 tick 的 bid/ask 补位;MT5 分支则靠 switch 把自定义枚举 TypeOfPos 映射到平台原生的 ENUM_ORDER_TYPE 和请求动作。
限价单寿命这种细节也塞进了函数参数(expiration),比在外部散着写更不容易漏。你直接把这段代码贴进 MT5 编辑器,把经纪商执行模式从 FOK 改成你账户实际支持的,就能跑一套基础下单验证。外汇和贵金属波动剧烈,杠杆交易高风险,参数没验之前别上实盘。
class="macro">#ifdef __MQL5__ class=class="str">"cmt">//MQL5 代码 class="macro">#else class=class="str">"cmt">//MQL4 代码 class="macro">#endif class=class="str">"cmt">// 仓位开启函数中可能的订单类型 enum TypeOfPos { MY_BUY, MY_SELL, MY_BUYSTOP, MY_BUYLIMIT, MY_SELLSTOP, MY_SELLLIMIT, MY_BUYSLTP, MY_SELLSLTP, }; class=class="str">"cmt">// 为 MT5 选择交易执行类型 class="macro">#ifdef __MQL5__ enum TypeOfFilling class=class="str">"cmt">//交易执行类型 { FOK,class=class="str">"cmt">//ORDER_FILLING_FOK RETURN,class=class="str">"cmt">// ORDER_FILLING_RETURN IOC,class=class="str">"cmt">//ORDER_FILLING_IOC }; class="kw">input TypeOfFilling useORDER_FILLING_RETURN=FOK; class=class="str">"cmt">//订单执行模式 class="macro">#endif class=class="str">"cmt">/* 开仓或限价下单函数 */ class="type">bool pdxSendOrder(TypeOfPos mytype, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp, class="type">class="kw">double volume, class="type">class="kw">ulong position=class="num">0, class="type">class="kw">string comment="", class="type">class="kw">string sym="", class="type">class="kw">datetime expiration=class="num">0){ if( !StringLen(sym) ){ sym=_Symbol; } class="type">int curDigits=(class="type">int) SymbolInfoInteger(sym, SYMBOL_DIGITS); if(sl>class="num">0){ sl=NormalizeDouble(sl,curDigits); } if(tp>class="num">0){ tp=NormalizeDouble(tp,curDigits); } if(price>class="num">0){ price=NormalizeDouble(price,curDigits); }else{ class="macro">#ifdef __MQL5__ class="macro">#else class="type">MqlTick latest_price; SymbolInfoTick(sym,latest_price); if( mytype == MY_SELL ){ price=latest_price.ask; }else if( mytype == MY_BUY ){ price=latest_price.bid; } class="macro">#endif } class="macro">#ifdef __MQL5__ ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL; ENUM_ORDER_TYPE type=ORDER_TYPE_BUY; class="kw">switch(mytype){ case MY_BUY: action=TRADE_ACTION_DEAL; type=ORDER_TYPE_BUY; class="kw">break; case MY_BUYSLTP: action=TRADE_ACTION_SLTP; type=ORDER_TYPE_BUY; class="kw">break; } class="macro">#endif }
「把指令枚举翻译成交易请求结构体」
这段 switch 只干一件事:把自定义枚举 MY_BUY / MY_BUYSTOP / MY_SELL 等映射成 MT5 的 action 与 type 常量。比如 MY_BUYSTOP 对应 TRADE_ACTION_PENDING 加 ORDER_TYPE_BUY_STOP,MY_SELLSLTP 则只改挂单的止损止盈,action 用 TRADE_ACTION_SLTP。 映射完之后,代码用 ZeroMemory 清空 MqlTradeRequest,再逐项赋值 symbol、sl、tp、price、volume。若 expiration 大于 0 就设 ORDER_TIME_SPECIFIED_DAY 并填到期时间;position 大于 0 才填持仓 ticket,避免对无关仓位误操作。 当 action 不是 SLTP 时,price 与 volume 才参与赋值,同时写死 magic 为 EA_Magic,并按 useORDER_FILLING_RETURN 在 FOK 与 RETURN 两种成交模式间二选一。外汇与贵金属杠杆高,用 RETURN 模式可能在流动性薄时吃到非预期滑点,上 MT5 跑之前先确认经纪商支持哪种 filling。
class="kw">break; case MY_BUYSTOP: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_BUY_STOP; class="kw">break; case MY_BUYLIMIT: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_BUY_LIMIT; class="kw">break; case MY_SELL: action=TRADE_ACTION_DEAL; type=ORDER_TYPE_SELL; class="kw">break; case MY_SELLSLTP: action=TRADE_ACTION_SLTP; type=ORDER_TYPE_SELL; class="kw">break; case MY_SELLSTOP: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_SELL_STOP; class="kw">break; case MY_SELLLIMIT: action=TRADE_ACTION_PENDING; type=ORDER_TYPE_SELL_LIMIT; class="kw">break; } class="type">MqlTradeRequest mrequest; class="type">MqlTradeResult mresult; ZeroMemory(mrequest); mrequest.action = action; mrequest.sl = sl; mrequest.tp = tp; mrequest.symbol = sym; if(expiration>class="num">0){ mrequest.type_time = ORDER_TIME_SPECIFIED_DAY; mrequest.expiration = expiration; } if(position>class="num">0){ mrequest.position = position; } if(StringLen(comment)){ mrequest.comment=comment; } if(action!=TRADE_ACTION_SLTP){ if(price>class="num">0){ mrequest.price = price; } if(volume>class="num">0){ mrequest.volume = volume; } mrequest.type = type; mrequest.magic = EA_Magic; class="kw">switch(useORDER_FILLING_RETURN){ case FOK: mrequest.type_filling = ORDER_FILLING_FOK; class="kw">break; case RETURN: mrequest.type_filling = ORDER_FILLING_RETURN; class="kw">break;