开发一个跨平台的EA交易来根据风险设置止损和获利(基础篇)
📘

开发一个跨平台的EA交易来根据风险设置止损和获利(基础篇)

第 1/3 篇

用风险百分比反推止损与止盈距离

很多新手 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 改成你账户实际支持的,就能跑一套基础下单验证。外汇和贵金属波动剧烈,杠杆交易高风险,参数没验之前别上实盘。

MQL5 / C++
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。

MQL5 / C++
         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;

常见问题

先用止损距离×每点价值算出单笔最大亏损额,再用账户净值×2%除以它得到手数,止损距离由支撑阻力位决定,别拍脑袋定。
先定风险金额和止损点数,再查当前品种每点价值,两者相除得手数;把公式写进表格或脚本,每次只填三个数就行。
可以,小布能读取你设的风险百分比和实时止损距离,直接给出建议手数与止盈位,省去手算出错。
风险百分比、最大单笔亏损、品种每点价值校准这三个最关键,其余多是执行细节,先调稳前三个。
多是请求结构体里价格字段没按品种报价精度处理,开仓前用归一化函数对齐小数点位,再发请求即可避坑。