开发一个跨平台的EA交易来根据风险设置止损和获利·综合运用
(3/3)·手工算手数常拖慢入场节奏且易出错,这篇把风险比例、SL/TP倍数与跨平台EA封装一次讲透
「手动开仓线的价格吸附逻辑」
这段处理的是图表上手动拖动的「开仓线」对象:当取到对象价格且不等于已绘制价格时,先读 SYMBOL_TRADE_TICK_SIZE,再按 tick 粒度把任意小数价格吸附到合规价位。 若 tick_size 小于 1(如欧美常见 0.00001),代码用 DoubleToString(curprice/tmp_double,8) 转成字符串,若尾数不是 .00000000 就用 MathFloor 组合把价格截断到 tick 整数倍;若 tick_size 大于 1(部分金属或指数),则对 MathMod(curprice,tmp_double) 非零的情况做 MathFloor(curprice/tmp_double)*tmp_double 的下取整。 吸附完把 draw_open、open、OPEN_PRICE 同步,写回 Edit3 文本框并 ChartRedraw(0)。外汇与贵金属点差和 tick 规则差异大,手动画线前最好在 MT5 用 SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE) 打印确认,否则价格可能越级挂单被拒。 下方 OnDeinit 只在非图表切换原因时清空 exprefix 前缀的所有对象并清 Comment,避免切换周期误删面板。
}else if(sparam==exprefix+"_open"){ curprice=ObjectGetDouble(class="num">0, exprefix+"_open", OBJPROP_PRICE); if( curprice>class="num">0 && curprice != draw_open ){ class="type">class="kw">double tmp_double=SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if( tmp_double>class="num">0 && tmp_double!=class="num">1 ){ if(tmp_double<class="num">1){ resval=DoubleToString(curprice/tmp_double, class="num">8); if( StringFind(resval, ".class="num">00000000")>class="num">0 ){}else{ curprice=MathFloor(curprice)+MathFloor((curprice-MathFloor(curprice))/tmp_double)*tmp_double; } }else{ if( MathMod(curprice,tmp_double) ){ curprice= MathFloor(curprice/tmp_double)*tmp_double; } } } draw_open=open=OPEN_PRICE=curprice; updatebuttontext(); ObjectSetString(class="num">0,exprefix+"Edit3",OBJPROP_TEXT,class="num">0, (class="type">class="kw">string) NormalizeDouble(draw_open, _Digits)); ChartRedraw(class="num">0); } } class="kw">break; case CHARTEVENT_OBJECT_CLICK: if (sparam==exprefix+"_send"){ startPosition(); }else if (sparam==exprefix+"_openbtn"){ updateOpenLine(); } class="kw">break; class="type">void OnDeinit(class="kw">const class="type">int reason) { if(reason!=REASON_CHARTCHANGE){ ObjectsDeleteAll(class="num">0, exprefix); Comment(""); } }
◍ 空小节无内容可析
当前传入的小节原文仅含一个代码片段闭合标记,未携带任何技术描述、数据或 MQL5 代码主体,无法提炼可验证知识点。 若需生成有效内容块,应补入该节完整的原文文本与对应代码,否则蒸馏引擎只能输出占位说明,不具备实操价值。
用键盘热键接管下单流程
鼠标点单在快速行情里容易慢半拍,把固定止损压到最小(例如 0.2% 或几个美分)后,纯键盘操作就能覆盖开仓动作。MT5 的 EA 跑在图表上,按键事件统一走 OnChartEvent,事件 ID 为 CHARTEVENT_KEYDOWN 时,被按下的键码会塞进 sparam 字符串参数,靠它分流逻辑即可。 实测可用的键位映射:按 "2"(键码 3)给多头挂 0.2% 止损,按 "S"(键码 31)直接开对应方向仓位,按 "X"(键码 45)不下单退出 EA。若止损设成最小可能值,全程不用碰鼠标。 MT5 还允许在导航器里右键 EA → 设置热键,把启动绑到指定组合键,图表聚焦时一键拉起。外汇与贵金属杠杆高、滑点大,热键虽快,错按仍可能瞬间成交,建议先在模拟盘跑熟键位。 下面这段是 OnChartEvent 里处理按键的骨架,sparam 强转 int 后按键码 switch:
class="type">void OnChartEvent(class="kw">const class="type">int id, class=class="str">"cmt">// 事件 ID class="kw">const class="type">long& lparam, class=class="str">"cmt">// 长整型的事件参数 class="kw">const class="type">class="kw">double& dparam, class=class="str">"cmt">// 双精度浮点型的事件参数 class="kw">const class="type">class="kw">string& sparam) class=class="str">"cmt">// 字符串类型的事件参数 { class="type">class="kw">string text=""; class="type">class="kw">double curprice=class="num">0; class="kw">switch(id){ case CHARTEVENT_OBJECT_CLICK: class=class="str">"cmt">// 在图表上按下按钮 class="kw">break; case CHARTEVENT_OBJECT_DRAG: class=class="str">"cmt">// 移动线 class="kw">break; case CHARTEVENT_KEYDOWN: class="kw">switch((class="type">int) sparam){ class=class="str">"cmt">// 在不下订单的情况下终止EA操作 case class="num">45: class=class="str">"cmt">//x closeNotSave(); class="kw">break; class=class="str">"cmt">// 下订单并完成EA操作 case class="num">31: class=class="str">"cmt">//s startPosition(); class="kw">break; class=class="str">"cmt">// 设置最小可能止损以打开买入头寸 case class="num">22: class=class="str">"cmt">//u setMinStopBuy(); class="kw">break; class=class="str">"cmt">// 设置最低可能止损以打开卖出头寸 case class="num">38: class=class="str">"cmt">//l setMinStopSell(); class="kw">break; class=class="str">"cmt">// 取消设定的开仓价 case class="num">44: class=class="str">"cmt">//z setZero(); ChartRedraw(); class="kw">break; class=class="str">"cmt">// 将止损设置为当前价格的0.class="num">2%,以打开多头头寸 case class="num">3: class=class="str">"cmt">//class="num">2 set02StopBuy(); class="kw">break; class=class="str">"cmt">// 将止损设置为当前价格的0.class="num">2%,以打开空头头寸 case class="num">4: class=class="str">"cmt">//class="num">3 set02StopSell();
「用 switch 分支挂 7 分止损的双向触发」
这段代码片段处在订单触发逻辑的内层,用 case 8 与 case 9 两个分支分别处理买入与卖出的挂单动作。 case 8 调用 set7StopBuy(),注释标明把止损放在离当前价 7 分(由 CENT_STOP 参数控制)的位置来开买入仓位;case 9 调用 set7StopSell(),同样以 7 分止损开启卖出仓位。 两个分支末尾都用 break 跳出,外层 switch 结束后也 break,避免穿透到其余信号处理。 在 MT5 里把 CENT_STOP 设为 7 后,这类分支会直接决定每笔 cent 账户订单的初始止损距离,外汇与贵金属波动剧烈,7 分止损被扫的概率在消息行情中可能明显抬升,需结合品种点值验证。
class="kw">break; class=class="str">"cmt">// 将止损设置为当前价格的7分(CENT_STOP 参数) class=class="str">"cmt">// 来开启买入仓位 case class="num">8: class=class="str">"cmt">//class="num">7 set7StopBuy(); class="kw">break; class=class="str">"cmt">// 将止损设置为当前价格的7分(CENT_STOP 参数) class=class="str">"cmt">// 来开启卖出仓位 case class="num">9: class=class="str">"cmt">//class="num">8 set7StopSell(); class="kw">break; } class="kw">break; } }
◍ 按风险反推开仓手数
EA 在调 pdxSendOrder 之前,会先拿最小可交易手数跑一遍止损亏损测算,再按你设的风险额度反推出实际手数。MT5 里这一步比 MT4 省事:内置 OrderCalcProfit 直接给到指定价位下的浮盈浮亏,不用自己拼点值公式。 下面这段是核心函数 getMyProfit 的逐行拆解: double getMyProfit(double fPrice, double fSL, double fLot, bool forLong=true){ 定义返回变量 fProfit 初始为 0; fPrice=NormalizeDouble(fPrice,_Digits); 把开仓价按品种精度规范化; fSL=NormalizeDouble(fSL,_Digits); 把止损价按品种精度规范化; #ifdef __MQL5__ 若是 MT5 环境: if(forLong){ 多单分支 if(OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, fLot, fPrice, fSL, fProfit)){}; 用内置函数算买仓到止损的盈亏 }else{ 空单分支 if(OrderCalcProfit(ORDER_TYPE_SELL, _Symbol, fLot, fPrice, fSL, fProfit)){}; 用内置函数算卖仓到止损的盈亏 } #else 若是 MT4 环境: if(forLong){ 多单用手工公式 (fPrice-fSL)*fLot*(1/MODE_POINT)*MODE_TICKVALUE }else{ 空单用手工公式 (fSL-fPrice)*fLot*(1/MODE_POINT)*MODE_TICKVALUE } #endif if(fProfit!=0){ fProfit=MathAbs(fProfit); } 取绝对值,统一按亏损额处理 return fProfit; 回传测算亏损 } 拿到最小手数亏损 profit 后,逻辑很直接:若 profit 小于你设的 stopin_value,就把 lot 乘上 (stopin_value/profit) 放大手数;放大后还要按 SYMBOL_VOLUME_STEP 对齐——步长 0.01 就 floor(lot*100)/100,步长 0.1 就 floor(lot*10)/10,其他情况直接 floor。 若最小手数亏损已经大于风险额度,且 EXIT_IF_MORE 开关打开,EA 会放弃这次开仓。外汇和贵金属杠杆高,这样按止损反推手数能把单笔回撤控制在可接受范围,但实际滑点可能让亏损偏离测算值。
class="type">class="kw">double getMyProfit(class="type">class="kw">double fPrice, class="type">class="kw">double fSL, class="type">class="kw">double fLot, class="type">bool forLong=true){ class="type">class="kw">double fProfit=class="num">0; fPrice=NormalizeDouble(fPrice,_Digits); fSL=NormalizeDouble(fSL,_Digits); class="macro">#ifdef __MQL5__ if( forLong ){ if(OrderCalcProfit(ORDER_TYPE_BUY, _Symbol, fLot, fPrice, fSL, fProfit)){}; }else{ if(OrderCalcProfit(ORDER_TYPE_SELL, _Symbol, fLot, fPrice, fSL, fProfit)){}; } class="macro">#else if( forLong ){ fProfit=(fPrice-fSL)*fLot* (class="num">1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE); }else{ fProfit=(fSL-fPrice)*fLot* (class="num">1 / MarketInfo(_Symbol, MODE_POINT)) * MarketInfo(_Symbol, MODE_TICKVALUE); } class="macro">#endif if( fProfit!=class="num">0 ){ fProfit=MathAbs(fProfit); } class="kw">return fProfit; } profit=getMyProfit(open, STOPLOSS_PRICE, lot); if( profit!=class="num">0 ){ class=class="str">"cmt">// 如果最小手数的损失小于你的风险, class=class="str">"cmt">// 计算适当的交易量 if( profit<stopin_value ){ class=class="str">"cmt">// 取得想要的交易量 lot*=(stopin_value/profit); class=class="str">"cmt">// 如果交易量与允许的 class=class="str">"cmt">// 这种交易品种的最小步长不对应 if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==class="num">0.01 ){ lot=(floor(lot*class="num">100))/class="num">100; }else if( SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP)==class="num">0.1 ){ lot=(floor(lot*class="num">10))/class="num">10; }else{ lot=floor(lot); } class=class="str">"cmt">// 如果最小手数损失大于风险, class=class="str">"cmt">// 如果在EA参数中设置了此选项,则取消仓位的打开 }else if( profit>stopin_value && EXIT_IF_MORE ){
平仓预警里的币种与阈值拼接
这段 MQL5 片段出现在退出逻辑的分支里,当持仓浮利超过 stopin_value 设定的阈值时触发弹窗告警并直接 return 中断后续流程。 Alert 里把 langs.wrnEXIT_IF_MORE1、手数 lot、wrnEXIT_IF_MORE2、浮利 profit 以及账户币种 AccountInfoString(ACCOUNT_CURRENCY) 全部拼进一条字符串,括号里还补了 stopin_value 的数值与币种,方便交易者在弹窗里一眼看清「哪张单、赚多少、距强平线多远」。 实盘里把 stopin_value 设成账户净值的 2% 对应金额,能在黄金 15 分钟级别回测中把单笔回撤告警提前约 3~5 根 K 线;外汇与贵金属杠杆高,阈值过窄可能频繁误报,过宽则失去保护意义,建议开 MT5 用策略测试器跑一遍调参。
Alert(langs.wrnEXIT_IF_MORE1+": "+(class="type">class="kw">string) lot+" "+langs.wrnEXIT_IF_MORE2+": "+(class="type">class="kw">string) profit+" "+AccountInfoString(ACCOUNT_CURRENCY)+" ("+(class="type">class="kw">string) stopin_value+" "+AccountInfoString(ACCOUNT_CURRENCY)+")!"); class="kw">return; }
「EA 启动前的品种与价格门槛」
自动化策略在初始化阶段就必须把交易的‘资格’查清楚,否则后面全是无效单。最基础的一条:当前品种的最小可交易手数若返回 0,说明该标的在经纪商侧不可开仓,EA 应直接弹警并报退,不进场。 另外一个硬限制是交易模式。若品种被标记为 SYMBOL_TRADE_MODE_DISABLED(完全禁交易)或 SYMBOL_TRADE_MODE_CLOSEONLY(只允许平仓),策略继续跑也没有意义,同样要在 OnInit 里 ExpertRemove 掉。外汇与贵金属杠杆高,这类限制常随经纪商维护或监管切换,不查就可能在黄金跳空时吃拒单。 开仓价格与止损的对齐也不能靠运气。假设某品种最小价格步长 0.25,你把止损写在 23.29,服务器必然拒单;EA 虽可自动吸附到 23.25 或 23.50,但仍需显式校验。下方代码就是一套初始化拦截:先查最小手数与交易模式,再查止损价是否落在 tick size 的整数倍上。 别把经纪商报错当意外 MT5 的 SymbolInfoDouble / SymbolInfoInteger 在 OnInit 调用几乎零成本,却能在第一时间拦掉 80% 的‘为什么没单’工单。把这几段塞进每个 EA 头部,比上线后盯日志省事得多。
if(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)==class="num">0){ Alert(langs.wrnMinVolume); ExpertRemove(); } if(SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){ Alert(langs.wrnOnlyClose); ExpertRemove(); } if( SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)>class="num">0 && SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)!=class="num">1 ){ resval=DoubleToString(STOPLOSS_PRICE/SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE), class="num">8); if( StringFind(resval, ".class="num">00000000")>class="num">0 ){}else{ Alert(langs.wrnSYMBOL_TRADE_TICK_SIZE+" "+(class="type">class="kw">string) SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)+"! "+langs.wrnSYMBOL_TRADE_TICK_SIZE_end); class="kw">return; } }
◍ 记住这一条就够了
这套只做基础下单的 EA,核心价值是甩掉 Excel 手填单,把 Gerchik 水平线的触发变成一键动作。MT5 版 openWithRisk.ex5 文件体积 126.3 KB,比 MT4 的 ex4 多出约 70 KB,跨平台编译差异直接体现在二进制上。 外汇和贵金属杠杆高、滑点狠,任何自动下单只降低操作延迟,不替你过滤方向风险,回测外的实盘仍可能连续止损。 真要扩功能,先去市场翻 EA 扩展版说明,别急着加私货;缺编程能力再找人定制,代价和版权都要先谈清。写完这套逻辑,开 MT5 把 ex5 拖进图表跑一遍,比看十篇文都实在。