基于预定义的风险和风险/回报比建立互动式半自动拖放EA 交易(基础篇)
📘

基于预定义的风险和风险/回报比建立互动式半自动拖放EA 交易(基础篇)

第 1/3 篇

◍ 用拖放方式在MT5里搭半自动风控EA

在 MT5 里做半自动交易,不一定非要写完整策略逻辑。一种务实思路是:先定好单笔风险金额和风报比,再用交互式拖放把止损、止盈挂到图表上,由 EA 负责按预设比例下单。 这类 EA 的典型交互是——交易者在图表拖出一条线段代表止损距离,EA 读取长度并结合账户净值算出手数;同时按风险/回报比自动推算止盈位。2013 年 12 月发布的一版示例,历史查看量约 2416 次、评论 7 条,说明手动拖放+自动算仓的需求一直有人用。 外汇与贵金属杠杆品种波动剧烈,拖放设定的止损若过窄,遇数据行情可能被瞬时扫损;建议先在策略测试器用历史数据跑一遍,确认手数计算和风报比映射符合预期再上模拟盘。

「为什么要在图表上动态算风险回报」

做混合交易的不少人卡在一个点上:指标给信号,但仓位和止损还得切到计算器里手敲。外汇和贵金属杠杆高,手动算一旦慢半拍,滑点就可能把原本计划好的风险/回报比直接打穿。 我自己属于「指标辅助+人工确认」那一类,核心诉求很直接——在 MT5 图表上拉一条止损线,EA 立刻告诉我这一笔最多亏多少净值、对应手数该开多大,而不是盘后复盘才发现风险超标。 这套思路落地的关键,是把「最大净值风险」和「风险/回报比」做成 EA 面板上的运行时参数。改一下数字,图表上的止盈止损参考线就重算,省掉切窗口的摩擦。 需要提醒的是,这种半自动方式只是把计算和执行链缩短,并不消除外汇/贵金属固有的高波动风险,实际成交价可能因流动性在极端时段偏离预设水平。

EA 该有的交互与风控底线

一个能用于实盘验证的仓位管理 EA,首先要在启动阶段就允许预设风险水平,并且能在图表运行时直接拖动改值,实时看到仓位大小怎么变。外汇和贵金属杠杆高,这种即时反馈能避免凭感觉下重手。 风险回报比也得能在运行时改,而不是写死在参数里。同一套止损距离,1:1 和 1:3 对应的手数和潜在回撤完全不同,交易者需要当场对比。 给定风险和止损后,EA 要能算出当前可开的最大手数,而不是等报错才知道超了。同时手数本身也能拖动,资产净值的风险暴露和回报会随之一并刷新。 最后,EA 必须能直接发买卖市价单,且支持在图上拖放止损线,同步显示预设风险回报对应的价格位置。这样从测算到下单形成闭环,省去切窗口的误差。

◍ 用图表对象类搭可交互面板

这个 EA 需要在行情跑着的时候改参数、看状态,所以直接走 CChartObject 体系,把 GUI 画在图表窗口上接管鼠标输入,而不是塞进 EA 属性框。界面里要有标签、按钮和编辑框三类基本件,交易者开图就能点。 最早想用 CChartObjectPanel 把控件归组,后来换思路:自己写一个类,把标签、编辑字段、按钮都垫在一张背景图上面。背景图是 GIMP 画的,MQL5 这边只负责生成红色实时标签、可输入编辑框和按钮对象。 具体落法是先把标签摆上图表、记死坐标,再写 CRRDialog 类统一算输出显示、读 CChartObjectEdit 的入参、记按钮状态。风险回报那两块彩色矩形是 CChartObjectRectangle,能拖的止损箭头是 CChartObjectBitmap 位图对象——你在 MT5 里点开对象列表,能看到这几类对象实打实建在图上。

「EA 面板类怎么搭起来」

做 MT5 面板型 EA,界面层最好单独封一个类来管。把背景图、编辑框、标签、按钮各自用图表对象类接住,逻辑和画图就不会缠在一起。 这个类里用了 CChartObjectBmpLabel 当背景,CChartObjectEdit 承接风控比例和手数输入,CChartObjectLabel 负责显示买卖价、净值、SL/TP 等只读字段,CChartObjectButton 管切换订单类型和下单退出。CreateCRRDialog() 按基准坐标 m_baseX、m_baseY 把上述对象逐一 Create() 定位,标签和编辑框顺手设 Font()、FontSize()、Description(),按钮则补 BackColor() 定底色。 Refresh() 是面板活起来的关键:它用 CRRDialog 内部变量加上实时 Ask/Bid、账户净值、净值风险值去重写所有标签和按钮描述。实盘里若发现面板数字卡住,先查 Refresh() 是否被主循环按帧调用,而不是怀疑对象创建失败。 下面这段类声明可直接贴进 MT5 编辑器看字段布局,private 区塞了 12 个 double 变量(RRRatio、riskPercent、orderLots、SL、TP 等)加坐标与字体配置,public 区暴露 Create / Refresh / Set / Get 接口,验证时重点看 m_maxAllowedLots 和 m_orderEquityRisk 有没有在 Refresh 里被赋值。

MQL5 / C++
class CRRDialog
  {
class="kw">private:
   class="type">int                m_baseX;
   class="type">int                m_baseY;
   class="type">int                m_fontSize;

   class="type">class="kw">string             m_font;
   class="type">class="kw">string             m_dialogName;
   class="type">class="kw">string             m_bgFileName;
   class="type">class="kw">double             m_RRRatio;
   class="type">class="kw">double             m_riskPercent;
   class="type">class="kw">double             m_orderLots;
   class="type">class="kw">double             m_SL;
   class="type">class="kw">double             m_TP;
   class="type">class="kw">double             m_maxAllowedLots;
   class="type">class="kw">double             m_maxTicksLoss;
   class="type">class="kw">double             m_orderEquityRisk;
   class="type">class="kw">double             m_orderEquityReward;
   ENUM_ORDER_TYPE    m_orderType;
   CChartObjectBmpLabel m_bgDialog;
   CChartObjectEdit   m_riskRatioEdit;
   CChartObjectEdit   m_riskValueEdit;
   CChartObjectEdit   m_orderLotsEdit;
   CChartObjectLabel m_symbolNameLabel;
   CChartObjectLabel m_tickSizeLabel;
   CChartObjectLabel m_maxEquityLossLabel;
   CChartObjectLabel m_equityLabel;
   CChartObjectLabel m_profitValueLabel;
   CChartObjectLabel m_askLabel;
   CChartObjectLabel m_bidLabel;
   CChartObjectLabel m_tpLabel;
   CChartObjectLabel m_slLabel;
   CChartObjectLabel m_maxAllowedLotsLabel;
   CChartObjectLabel m_maxTicksLossLabel;
   CChartObjectLabel m_orderEquityRiskLabel;
   CChartObjectLabel m_orderEquityRewardLabel;
   CChartObjectLabel m_orderTypeLabel;
   CChartObjectButton m_switchOrderTypeButton;
   CChartObjectButton m_placeOrderButton;
   CChartObjectButton m_quitEAButton;
class="kw">public:
   class="type">void                CRRDialog(); class=class="str">"cmt">// CRRDialog 构造函数
   class="type">void               ~CRRDialog(); class=class="str">"cmt">// CRRDialog 析构函数
   class="type">bool               CreateCRRDialog(class="type">int topX,class="type">int leftY);
   class="type">int                DeleteCRRDialog();
   class="type">void               Refresh();
   class="type">void               SetRRRatio(class="type">class="kw">double RRRatio);
   class="type">void               SetRiskPercent(class="type">class="kw">double riskPercent);
   class="type">class="kw">double             GetRiskPercent();
   class="type">class="kw">double             GetRRRRatio();
   class="type">void               SetSL(class="type">class="kw">double sl);
   class="type">void               SetTP(class="type">class="kw">double tp);
   class="type">class="kw">double             GetSL();
   class="type">class="kw">double             GetTP();

对话框里把风险敞口算给交易者看

上面这组方法暴露了面板类的核心接口:SetMaxAllowedLots 限定单笔最大手数,SetMaxTicksLoss 管住每单允许的 tick 亏损上限,SetOrderEquityRisk / SetOrderEquityReward 则把权益回撤与预期回报直接绑进按钮逻辑。SwitchOrderType 和 GetOrderType 配合,能在多空之间一键翻转,不用退出面板重设。 CreateCRRDialog 里能看到真实的布局坐标:symbolNameLabel 落在 baseX+120、baseY+40,tickSizeLabel 在 +57,riskRatioEdit 在 +72 且宽 35 高 15。也就是说整个风险面板竖向每行约占 15~18 像素,字体统一 Verdana 8 号,适合在 1280×720 副屏上常驻。 m_maxEquityLossLabel 的描述直接用 AccountInfoDouble(ACCOUNT_EQUITY)*m_riskPercent/100.0 算出金额,比如权益 10000、风险百分比 2.0 时显示 200.00。外汇与贵金属杠杆高,这个数值只是当前账户权益下的理论最大回撤,实际滑点可能让亏损超出。 把这段贴进 MT5 的面板类,改 m_baseX / m_baseY 就能挪动面板锚点;想让风险行更紧凑,把 +90、+107、+122 的 Y 偏移各减 5 即可,但别忘了同步调 askLabel 的 +145。

MQL5 / C++
class="type">void                SetMaxAllowedLots(class="type">class="kw">double lots);
class="type">void                SetMaxTicksLoss(class="type">class="kw">double ticks);
class="type">void                SetOrderType(ENUM_ORDER_TYPE);
class="type">void                SwitchOrderType();
class="type">void                ResetButtons();
ENUM_ORDER_TYPE     GetOrderType();
class="type">void                SetOrderLots(class="type">class="kw">double orderLots);
class="type">class="kw">double              GetOrderLots();
class="type">void                SetOrderEquityRisk(class="type">class="kw">double equityRisk);
class="type">void                SetOrderEquityReward(class="type">class="kw">double equityReward);
};
class="type">bool CRRDialog::CreateCRRDialog(class="type">int topX,class="type">int leftY)
  {
  class="type">bool isCreated=false;
  class="type">MqlTick current_tick;
  SymbolInfoTick(Symbol(),current_tick);
  m_baseX = topX;
  m_baseY = leftY;
  m_bgDialog.Create(class="num">0, m_dialogName, class="num">0, topX, leftY);
  m_bgDialog.BmpFileOn(m_bgFileName);
  m_symbolNameLabel.Create(class="num">0, "symbolNameLabel", class="num">0, m_baseX + class="num">120, m_baseY + class="num">40);
  m_symbolNameLabel.Font("Verdana");
  m_symbolNameLabel.FontSize(class="num">8);
  m_symbolNameLabel.Description(Symbol());
  m_tickSizeLabel.Create(class="num">0, "tickSizeLabel", class="num">0, m_baseX + class="num">120, m_baseY + class="num">57);
  m_tickSizeLabel.Font("Verdana");
  m_tickSizeLabel.FontSize(class="num">8);
  m_tickSizeLabel.Description(DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits()));
  m_riskRatioEdit.Create(class="num">0, "riskRatioEdit", class="num">0, m_baseX + class="num">120, m_baseY + class="num">72, class="num">35, class="num">15);
  m_riskRatioEdit.Font("Verdana");
  m_riskRatioEdit.FontSize(class="num">8);
  m_riskRatioEdit.Description(DoubleToString(m_RRRatio, class="num">2));
  m_riskValueEdit.Create(class="num">0, "riskValueEdit", class="num">0, m_baseX + class="num">120, m_baseY + class="num">90, class="num">35, class="num">15);
  m_riskValueEdit.Font("Verdana");
  m_riskValueEdit.FontSize(class="num">8);
  m_riskValueEdit.Description(DoubleToString(m_riskPercent, class="num">2));
  m_equityLabel.Create(class="num">0, "equityLabel", class="num">0, m_baseX + class="num">120, m_baseY + class="num">107);
  m_equityLabel.Font("Verdana");
  m_equityLabel.FontSize(class="num">8);
  m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),class="num">2));
  m_maxEquityLossLabel.Create(class="num">0, "maxEquityLossLabel", class="num">0, m_baseX + class="num">120, m_baseY + class="num">122);
  m_maxEquityLossLabel.Font("Verdana");
  m_maxEquityLossLabel.FontSize(class="num">8);
  m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*m_riskPercent/class="num">100.0,class="num">2));
  m_askLabel.Create(class="num">0, "askLabel", class="num">0, m_baseX + class="num">120, m_baseY + class="num">145);

常见问题

用图表对象类画可拖放控件,下单前按账户余额和止损距离反推手数,并在面板实时显示风险/回报比,低于阈值禁止发单。
开仓前动态算能拦掉赔率差的单;拖放止损和止盈时回报比实时变,交易者能当场改主意,不被已成持仓绑架。
小布可读取品种页的敞口与回报比分布,提示你面板阈值是否过松,并把异常拖放单标红供复盘。
任何交互发单前必须校验风险百分比和最低回报比,且一键撤单/锁面板要在,防止误拖出大敞口。
列账户净值、单笔风险金额、止损点数、推算手数、回报比和最大可接受亏损,缺一不可。