基于预定义的风险和风险/回报比建立互动式半自动拖放EA 交易·综合运用
🖱️

基于预定义的风险和风险/回报比建立互动式半自动拖放EA 交易·综合运用

(3/3)·手算仓位和手绘止损线太低效?这篇把前三篇拆开的逻辑拼成可拖放执行的完整EA

偏理论 第 3/3 篇
很多人在图表上凭感觉拉止损线,再回过头算手数,价格早跑偏了。把风险/回报比写死在脑子里的习惯,会让仓位和净值波动完全脱钩。半自动拖放的意义,是先锁死破产风险再谈执行。

◍ 下单与风控参数的实时联动

这段逻辑把可视面板上的 SL/TP、手数和权益风险绑成了同一条链:每次拖箭头或改 RR,EA 都会按买/卖方向重算 TP,并调用资金管理模块校验允许手数。 当计算出的 TP 不大于 SL 时,代码直接把 SL/TP 清零、删掉风险奖励矩形并返回,避免发出无效订单——这是防止鼠标误拖导致 0 或负盈亏比的硬保险。 EA_placeOrder() 里用 trade.Buy / trade.Sell 下市价单,SL、TP 和 lots 全部取自 visualRRDialog 的当前值;也就是说你面板上看到什么,单子就是什么,没有隐藏覆盖。 EA_onTick() 中若 SL_price 非零且未下单,会按品种实时买价/卖价重新算允许手数,并把手数上限回写面板;若当前设置手数大于上限则自动下调。外汇与贵金属杠杆高,这类自动裁剪能压住单笔回撤概率,但是否成交仍看流动性与滑点。

MQL5 / C++
  SL_price=maxSLAllowed;
  SL_arrow.SetDouble(OBJPROP_PRICE,class="num">0,maxSLAllowed);
  }
  visualRRDialog.SetSL(SL_price);
  visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
  if(visualRRDialog.GetTP()>SL_price)
   {
     visualRRDialog.SetSL(class="num">0.0);
     visualRRDialog.SetTP(class="num">0.0);
     SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
     rectReward.Delete();
     rectRisk.Delete();
     class="kw">return;
   }
  class="type">class="kw">double lotSize=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
  visualRRDialog.SetMaxAllowedLots(lotSize);
  visualRRDialog.SetOrderLots(lotSize);
  visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
  visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Bid(), SL_price, lotSize));
  visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(),
SL_price, lotSize, visualRRDialog.GetRRRRatio()));
  visualRRDialog.Refresh();
  rectUpdate(visualRRDialog.GetOrderType());
}
class="type">bool EA_placeOrder()
  {
  symbolInfo.RefreshRates();
  visualRRDialog.ResetButtons();
  if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
      orderPlaced=trade.Buy(visualRRDialog.GetOrderLots(),symbolInfo.Ask(),
visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));
  else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
      orderPlaced=trade.Sell(visualRRDialog.GetOrderLots(),symbolInfo.Bid(),
visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));
  class="kw">return orderPlaced;
  }
class="type">void EA_editParamsUpdate()
  {
  MM.Percent(visualRRDialog.GetRiskPercent());
  SL_arrow.GetDouble(OBJPROP_PRICE, class="num">0, SL_price);
  SL_arrow.GetInteger(OBJPROP_TIME, class="num">0, startTime);
  symbolInfo.RefreshRates();
  currentTime=TimeCurrent();
  class="type">class="kw">double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
  class="type">class="kw">double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);
  if(SL_price<lowestSLAllowed)
    {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(class="num">0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
    }
  visualRRDialog.SetSL(SL_price);
  visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
  visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
  visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(),
SL_price, visualRRDialog.GetOrderLots()));
  visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price,
visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
  visualRRDialog.Refresh();
 rectUpdate(visualRRDialog.GetOrderType());
  ChartRedraw();
  }
class="type">void EA_onTick()
  {
  if(SL_price!=class="num">0.0 && orderPlaced==false)
    {
      class="type">class="kw">double lotSize=class="num">0.0;
      SL_price=visualRRDialog.GetSL();
      symbolInfo.RefreshRates();
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        lotSize=MM.CheckOpenShort(symbolInfo.Ask(),SL_price);
      visualRRDialog.SetMaxAllowedLots(lotSize);
      if(visualRRDialog.GetOrderLots()>lotSize) visualRRDialog.SetOrderLots(lotSize);
      visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

买卖双向的 RR 矩形怎么画

这段逻辑处理的是可视化 RR 面板里,根据订单方向实时算 TP、风险收益额,并把报酬区与风险区画成矩形。买单方用 Ask 作为入场基准,空单方用 Bid,SL 价格由面板传入,TP 直接按 RR 倍数从 SL 反推。 以空单为例:TP 价 = Bid - (SL - Bid) * RR,风险额由 CalcOrderEquityRisk(Bid, SL, lots) 得出,报酬额由 CalcOrderEquityReward(Bid, SL, lots, RR) 得出。函数末尾调 Refresh() 和 rectUpdate(),再 ChartRedraw(0) 强制重绘。 rectUpdate() 里先 RefreshRates() 拿最新报价,取 SL 箭头的时间作为矩形起点。买单方报酬矩形上边界是 Ask + (Ask - SL) * RR、下边界 Ask,风险矩形夹在 SL 与 Ask 之间;空单方镜像处理。颜色硬编码 LightGreen / LightPink 且 Background(true),即半透明衬底,不挡 K 线。 开 MT5 把这段塞进你的 RR 脚本,改 LightGreen 为 clrForestGreen 之类,能立刻看出报酬区在深色模板下是否够醒目;外汇与贵金属杠杆高,可视化再直观也只是辅助,实际滑点可能让风险矩形边界失真。

MQL5 / C++
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(),
SL_price, visualRRDialog.GetOrderLots()));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price,
visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
   }
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
   {
   visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(
symbolInfo.Bid(), SL_price, visualRRDialog.GetOrderLots()));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(), SL_price,
visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
   }
   visualRRDialog.Refresh();
   rectUpdate(visualRRDialog.GetOrderType());
   }
 ChartRedraw(class="num">0);
}
class="type">void rectUpdate(ENUM_ORDER_TYPE orderType)
  {
  symbolInfo.RefreshRates();
  currentTime=TimeCurrent();
  SL_arrow.GetInteger(OBJPROP_TIME,class="num">0,startTime);
  if(orderType==ORDER_TYPE_BUY)
   {
   rectReward.Create(class="num">0,rewardRectID,class="num">0,startTime,symbolInfo.Ask(),currentTime,symbolInfo.Ask()+
(symbolInfo.Ask()-visualRRDialog.GetSL())*visualRRDialog.GetRRRRatio());
   rectReward.Color(LightGreen);
   rectReward.Background(true);
   rectRisk.Create(class="num">0,riskRectID,class="num">0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Ask());
   rectRisk.Color(LightPink);
   rectRisk.Background(true);
   }
  else if(orderType==ORDER_TYPE_SELL)
   {
   rectReward.Create(class="num">0,rewardRectID,class="num">0,startTime,symbolInfo.Bid(),currentTime,symbolInfo.Bid()-
(visualRRDialog.GetSL()-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
   rectReward.Color(LightGreen);
   rectReward.Background(true);
   rectRisk.Create(class="num">0,riskRectID,class="num">0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Bid());
   rectRisk.Color(LightPink);
   rectRisk.Background(true);
   }
  }

「实盘回看:周一开盘反弹后的卖单是怎么挂的」

一段 2010 年 1 月 11 日(周一)的行情回放里,能看到大反弹刚起没多久就下了卖单。那天亚盘开盘后不久价格快速拉升,EA 在动量转弱的第一根吞没阴线触发了逆势入场。 视频建议开 480p 全屏,注释直接打在画面里,能看清下单价位和后续止损移动轨迹。外汇与贵金属这类品种跳空和滑点频繁,逆势单风险偏高,只看演示不验证参数等于白看。 想复现的话,把这段行情加载到 MT5 的「日期跳转」里,对照 EA 日志里的 tick 时间戳,就能确认它是在反弹后第几根 K 线发的单。

◍ 一点提醒

前面几节把基于预定风险与回报比搭交互式 EA 的思路讲完了,标准类画图、图表事件拖放输入也都落了地。附带的 visualrrea.mq5(27.41 KB)和 moneyfixedriskext.mqh(14.36 KB)是直接能开 MT5 编译验证的实物,不用自己从头写框架。 这类半自动辅助工具只解决下单流程和风控参数可视化,不替你判断方向。外汇和贵金属杠杆高、滑点跳空频繁,哪怕固定风险比例设得再漂亮,实盘仍可能连亏或瞬间扩损。 想继续扩能力的,作者后来用 CChartObject 做了 Spinner、ProgressBar 等新 GUI 组件,比本文拖图方式更顺手,有空可以顺着那个思路改你自己的面板。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时风险回报矩形与拖放止损的辅助线,把重复劳动交给小布,你专注决策。

常见问题

位图对象仅挂载在图表前景层,接收鼠标事件后改写自身坐标,不参与报价计算,所以拖放只改止损参考位、不触发重绘序列。
目前小布提供风险回报可视化与手数速算,EA面板需自行编译原文代码;但小布的AIGC看盘页已覆盖同款实时水平线逻辑。
本文EA只在市价单执行前重算,已成交单不受影响;若要移动止损需另接追踪模块,原文未含。
矩形仅作视觉参考,超出报价边界时标签变灰,实际下单仍以经纪商可执行价为上限,外汇贵金属属高风险品类需自判流动性。