外汇EA编写教程:基于预定义的风险和风险/回报比建立互动式半自动拖放“EA 交易”

简介

部分交易人员选择自动执行所有交易,而另外一些交易人员基于多个指标的输出混合使用自动和手动交易。作为后者中的一员,我需要一个互动式工具以直接从图表动态地评估风险和回报价格水平。

对我的资产净值声明了最大风险后,我希望基于自己在图表上设置的止损水平计算实时参数,并且我需要基于计算得出的止损和获利水平直接从 EA 执行我的交易。

本文将介绍通过预定义的资产净值风险和风险/回报比实施互动式半自动“EA 交易”的方法。“EA 交易”风险、风险/回报和手数参数可于运行时期间在 EA 面板上更改。

1. 要求

对此 EA 的要求如下所示:

  • 能够在启动时预定义风险水平,并在运行时期间更改该水平以查看其影响仓位大小的方式
  • 能够预定义风险/回报比并在运行时期间更改
  • 能够在给定的风险和止损水平下计算实时最大手数
  • 能够在运行时更改手数以查看其影响资产净值风险和回报的方式
  • 能够直接从 EA 执行买入/卖出市价单
  • 能够拖放界面以设置止损和查看预定义的风险/回报水平的价格水平

2. 设计

由于此 EA 要求在运行时期间显示和更改参数,我决定使用 CChartObject 类及其后代,以在图表窗口上显示 GUI 和处理用户交互的输入图表事件。因此,此 EA 需要含标签、按钮和编辑字段的用户界面。

起初,我希望使用 CChartObjectPanel 对象对面板上的其他对象进行分组,但我决定尝试一种不同的方法,于是我设计了一个带有标签、编辑字段和按钮的类,并使其在图像背景上显示。界面的背景图像采用 GIMP 软件制作。MQL5 生成的对象是编辑字段、实时更新的红色标签以及按钮。

我只是简单地将标签对象放置在图表上并记录下它们的位置,然后构建 CRRDialog 类以处理显示计算得出的输出、接收 CChartObjectEdit 字段的参数以及记录按钮状态等所有功能。彩色的风险和回报矩形是 CChartObjectRectangle 类的对象,可拖放止损指针是 CChartObjectBitmap 类的位图对象。

 

图 1. 可视化 EA 屏幕截图

图 1. 可视化 EA 屏幕截图

 

3. EA 对话框类的实施

CRRDialog 类处理 EA 的所有用户界面。它包含多个显示的变量、用于显示变量的对象,以及获取/设置变量值和刷新对话框的方法。

我将 CChartObjectBmpLabel 对象用于背景,将 CChartObjectEdit 对象用于编辑字段,并将 CChartObjectLabel 对象用于显示标签、 CChartObjectButton 对象用于显示按钮:

class CRRDialog
  {
private:

   int               m_baseX;
   int               m_baseY;
   int               m_fontSize;
   
   string            m_font;
   string            m_dialogName;
   string            m_bgFileName;

   double            m_RRRatio;
   double            m_riskPercent;
   double            m_orderLots;
   double            m_SL;
   double            m_TP;
   double            m_maxAllowedLots;
   double            m_maxTicksLoss;
   double            m_orderEquityRisk;
   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;

public:

   void              CRRDialog(); // CRRDialog 构造函数
   void             ~CRRDialog(); // CRRDialog 析构函数

   bool              CreateCRRDialog(int topX,int leftY);
   int               DeleteCRRDialog();
   void              Refresh();
   void              SetRRRatio(double RRRatio);
   void              SetRiskPercent(double riskPercent);
   double            GetRiskPercent();
   double            GetRRRRatio();
   void              SetSL(double sl);
   void              SetTP(double tp);
   double            GetSL();
   double            GetTP();
   void              SetMaxAllowedLots(double lots);
   void              SetMaxTicksLoss(double ticks);
   void              SetOrderType(ENUM_ORDER_TYPE);
   void              SwitchOrderType();
   void              ResetButtons();
   ENUM_ORDER_TYPE   GetOrderType();
   void              SetOrderLots(double orderLots);
   double            GetOrderLots();
   void              SetOrderEquityRisk(double equityRisk);
   void              SetOrderEquityReward(double equityReward);
  };

由于获取/设置变量方法一目了然,我将重点讲述 CreateCRRDialog() 和 Refresh() 方法。CreateCRRDialog() 方法用于初始化背景图像、标签、按钮和编辑字段。

对于标签和编辑字段的初始化:我使用 Create() 方法和坐标参数以在图表上定位对象,使用 Font() 和 FontSize() 方法以设置字体,并使用 Description() 方法以将文本放置在标签上。

对于按钮:我使用 Create() 方法和其他参数来指定按钮的大小,并使用 BackColor() 方法指定按钮的背景颜色。 

bool CRRDialog::CreateCRRDialog(int topX,int leftY)
  {
   bool isCreated=false;

   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_baseX = topX;
   m_baseY = leftY;

   m_bgDialog.Create(0, m_dialogName, 0, topX, leftY);
   m_bgDialog.BmpFileOn(m_bgFileName);

   m_symbolNameLabel.Create(0, "symbolNameLabel", 0, m_baseX + 120, m_baseY + 40);
   m_symbolNameLabel.Font("Verdana");
   m_symbolNameLabel.FontSize(8);
   m_symbolNameLabel.Description(Symbol());

   m_tickSizeLabel.Create(0, "tickSizeLabel", 0, m_baseX + 120, m_baseY + 57);
   m_tickSizeLabel.Font("Verdana");
   m_tickSizeLabel.FontSize(8);
   m_tickSizeLabel.Description(DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits()));

   m_riskRatioEdit.Create(0, "riskRatioEdit", 0, m_baseX + 120, m_baseY + 72, 35, 15);
   m_riskRatioEdit.Font("Verdana");
   m_riskRatioEdit.FontSize(8);
   m_riskRatioEdit.Description(DoubleToString(m_RRRatio, 2));

   m_riskValueEdit.Create(0, "riskValueEdit", 0, m_baseX + 120, m_baseY + 90, 35, 15);
   m_riskValueEdit.Font("Verdana");
   m_riskValueEdit.FontSize(8);
   m_riskValueEdit.Description(DoubleToString(m_riskPercent, 2));

   m_equityLabel.Create(0, "equityLabel", 0, m_baseX + 120, m_baseY + 107);
   m_equityLabel.Font("Verdana");
   m_equityLabel.FontSize(8);
   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));

   m_maxEquityLossLabel.Create(0, "maxEquityLossLabel", 0, m_baseX + 120, m_baseY + 122);
   m_maxEquityLossLabel.Font("Verdana");
   m_maxEquityLossLabel.FontSize(8);
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*m_riskPercent/100.0,2));

   m_askLabel.Create(0, "askLabel", 0, m_baseX + 120, m_baseY + 145);
   m_askLabel.Font("Verdana");
   m_askLabel.FontSize(8);
   m_askLabel.Description("");

   m_bidLabel.Create(0, "bidLabel", 0, m_baseX + 120, m_baseY + 160);
   m_bidLabel.Font("Verdana");
   m_bidLabel.FontSize(8);
   m_bidLabel.Description("");

   m_slLabel.Create(0, "slLabel", 0, m_baseX + 120, m_baseY + 176);
   m_slLabel.Font("Verdana");
   m_slLabel.FontSize(8);
   m_slLabel.Description("");

   m_tpLabel.Create(0, "tpLabel", 0, m_baseX + 120, m_baseY + 191);
   m_tpLabel.Font("Verdana");
   m_tpLabel.FontSize(8);
   m_tpLabel.Description("");

   m_maxAllowedLotsLabel.Create(0, "maxAllowedLotsLabel", 0, m_baseX + 120, m_baseY + 208);
   m_maxAllowedLotsLabel.Font("Verdana");
   m_maxAllowedLotsLabel.FontSize(8);
   m_maxAllowedLotsLabel.Description("");

   m_maxTicksLossLabel.Create(0, "maxTicksLossLabel", 0, m_baseX + 120, m_baseY + 223);
   m_maxTicksLossLabel.Font("Verdana");
   m_maxTicksLossLabel.FontSize(8);
   m_maxTicksLossLabel.Description("");

   m_orderLotsEdit.Create(0, "orderLotsEdit", 0, m_baseX + 120, m_baseY + 238, 35, 15);
   m_orderLotsEdit.Font("Verdana");
   m_orderLotsEdit.FontSize(8);
   m_orderLotsEdit.Description("");

   m_orderEquityRiskLabel.Create(0, "orderEquityRiskLabel", 0, m_baseX + 120, m_baseY + 255);
   m_orderEquityRiskLabel.Font("Verdana");
   m_orderEquityRiskLabel.FontSize(8);
   m_orderEquityRiskLabel.Description("");

   m_orderEquityRewardLabel.Create(0, "orderEquityRewardLabel", 0, m_baseX + 120, m_baseY + 270);
   m_orderEquityRewardLabel.Font("Verdana");
   m_orderEquityRewardLabel.FontSize(8);
   m_orderEquityRewardLabel.Description("");

   m_switchOrderTypeButton.Create(0, "switchOrderTypeButton", 0, m_baseX + 20, m_baseY + 314, 160, 20);
   m_switchOrderTypeButton.Font("宋体");
   m_switchOrderTypeButton.FontSize(8);
   m_switchOrderTypeButton.BackColor(LightBlue);

   m_placeOrderButton.Create(0, "placeOrderButton", 0, m_baseX + 20, m_baseY + 334, 160, 20);
   m_placeOrderButton.Font("宋体");
   m_placeOrderButton.FontSize(8);
   m_placeOrderButton.BackColor(LightBlue);
   m_placeOrderButton.Description("下达市价单");

   m_quitEAButton.Create(0, "quitEAButton", 0, m_baseX + 20, m_baseY + 354, 160, 20);
   m_quitEAButton.Font("宋体");
   m_quitEAButton.FontSize(8);
   m_quitEAButton.BackColor(LightBlue);
   m_quitEAButton.Description("退出");

   return isCreated;
  }

Refresh() 方法通过 CRRDialog 变量和当前买价/卖价水平、帐户资产净值和资产净值风险值来刷新所有标签和按钮描述:

void CRRDialog::Refresh()
  {
   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*
                                         StringToDouble(m_riskValueEdit.Description())/100.0,2));
   m_askLabel.Description(DoubleToString(current_tick.ask, Digits()));
   m_bidLabel.Description(DoubleToString(current_tick.bid, Digits()));
   m_slLabel.Description(DoubleToString(m_SL, Digits()));
   m_tpLabel.Description(DoubleToString(m_TP, Digits()));
   m_maxAllowedLotsLabel.Description(DoubleToString(m_maxAllowedLots,2));
   m_maxTicksLossLabel.Description(DoubleToString(m_maxTicksLoss,0));
   m_orderEquityRiskLabel.Description(DoubleToString(m_orderEquityRisk,2));
   m_orderEquityRewardLabel.Description(DoubleToString(m_orderEquityReward,2));

   if(m_orderType==ORDER_TYPE_BUY) m_switchOrderTypeButton.Description("订单类型: 买入");
   else if(m_orderType==ORDER_TYPE_SELL) m_switchOrderTypeButton.Description("订单类型: 卖出");
  }

4. 图表事件

由于 EA 为互动设计,它应能够处理图表事件。

要处理的图表事件包括:

  • 在图表上拖放 S/L 指针(CChartObjectBitmap 类的 SL_arrow 对象)- 这将允许基于 R/R 比收集 S/L 水平和计算 T/P 水平
  • 切换订单类型(买入/卖出)按钮
  • 按“下达市价单”按钮
  • 编辑风险、R/R 和订单手数字段
  • 按“退出”按钮后关闭 EA

在要处理的事件中,CHARTEVENT_OBJECT_CLICK 用于指针选择和按钮,CHARTEVENT_OBJECT_DRAG 用于拖放 S/L 指针,而编辑字段后的 CHARTEVENT_OBJECT_ENDEDIT 由交易人员更新。

起初,OnChartEvent() 函数的实施要用到好几页代码,但我决定将其划分为多个事件处理程序,这就将 OnChartEvent() 函数转换为人们可读的形式:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- 检查点击鼠标按钮事件
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      string clickedChartObject=sparam;

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

      if(clickedChartObject==switchOrderTypeButtonID)
        {
         EA_switchOrderType();
        };

      if(clickedChartObject==placeOrderButtonID)
        {
         EA_placeOrder();
        }

      if(clickedChartObject==quitEAButtonID) ExpertRemove();

      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      // 买进 
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         EA_dragBuyHandle();
        };

      // 卖出
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         EA_dragSellHandle();
        };
      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      if((sparam==riskRatioEditID || sparam==riskValueEditID || sparam==orderLotsEditID) && orderPlaced==false)
        {
         EA_editParamsUpdate();
        }
     }
  }

事件处理程序的实施将会在后续章节中更详细地介绍。值得注意的是我用于选择 SL_arrow 对象的技巧。通常,要在图表上选择对象需要双击该对象。但可通过单击和在 CHARTEVENT_OBJECT_CLICK 事件处理程序内部的 OnChartEvent() 函数中调用 CChartObject 对象或其后代的 Selected() 方法来选择对象。

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

单击后,是选择还是取消选择对象取决于其之前的状态。

5. 基于 CMoneyFixedRisk 扩展资金管理类

在介绍 ChartEvent 处理程序之前,我们先讨论资金管理类。

对于资金管理,我重新使用 MetaQuotes 提供的 CMoneyFixedRisk 类并实施 CMoneyFixedRiskExt 类。

原始 CMoneyFixedRisk 类方法为指定的价格、止损水平和资产净值风险返回经纪人允许的最小和最大手数之间的允许的订单手数。我更改了 CheckOpenLong() 和 CheckOpenShort() 方法,以在不符合风险要求的情况下返回 0.0 手数,并使用下列四个方法对其进行扩展:GetMaxSLPossible()、CalcMaxTicksLoss()、CalcOrderEquityRisk() 和 CalcOrderEquityReward():

class CMoneyFixedRiskExt : public CExpertMoney
  {
public:
   //---
   virtual double    CheckOpenLong(double price,double sl);
   virtual double    CheckOpenShort(double price,double sl);
   
   double GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType);
   double CalcMaxTicksLoss();
   double CalcOrderEquityRisk(double price, double sl, double lots);
   double CalcOrderEquityReward(double price, double sl, double lots, double rrratio);
  };

GetMaxSLPossible() 方法为指定的资产净值风险和允许的最小交易量计算最大止损价格值。

例如,如果帐户余额为 10000 帐户基础货币且风险为 2%,则存在风险时我们最多可投入 200 帐户货币。如果最小交易手数为 0.1 手,本方法为符合持仓为 0.1 手的资产净值风险的 ORDER_TYPE_BUY 或 ORDER_TYPE_SELL 订单返回价格水平。这帮助我们评估在最小手数交易下我们能够承担的最大止损水平。在指定的资产净值风险水平下,这是一个我们不能越过的价格水平。

double CMoneyFixedRiskExt::GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType)
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   double orderTypeMultiplier;
   
   if(m_symbol==NULL) return(0.0);
   
   switch (orderType)
   {
   case ORDER_TYPE_SELL: orderTypeMultiplier = -1.0; break;
   case ORDER_TYPE_BUY: orderTypeMultiplier = 1.0; break;
   default: orderTypeMultiplier = 0.0;
   }
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // 最大亏损 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // 订单亏损值
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
 
   return (price - maxTicksLoss*m_symbol.TickSize()*orderTypeMultiplier);
}

CalcMaxTickLoss() 方法返回在指定的风险和最小允许的手数下我们可以承担损失的最大数量。

起初,最大资产净值损失计算为当前余额的百分比,然后计算指定交易品种的最小允许手数的一个订单号的更改的价格变动值损失。然后价格变动值损失除以最大资产净值损失,结果通过 MathFloor() 函数四舍五入为整数:

double CMoneyFixedRiskExt::CalcMaxTicksLoss()
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   
   if(m_symbol==NULL) return(0.0);
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // 最大亏损 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // 订单亏损值
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
   
   return (maxTicksLoss);
}

CalcOrderEquityRisk() 方法返回指定价格、止损水平和手数的资产净值风险。它通过将价格变动损失值乘以手数和价格,然后再乘以当前价格和止损水平的差值计算:

double CMoneyFixedRiskExt::CalcOrderEquityRisk(double price,double sl, double lots)
{
   double equityRisk;
   
   equityRisk = lots*m_symbol.TickValueLoss()*(MathAbs(price-sl)/m_symbol.TickSize()); 
   
   if (dbg) Print("calcEquityRisk: lots = " + DoubleToString(lots) +
                 " TickValueLoss = " + DoubleToString(m_symbol.TickValueLoss()) +
                 " risk = " + DoubleToString(equityRisk));
   
   return equityRisk;
}

CalcOrderEquityReward() 方法和 CalcOrderEquityRisk() 方法类似,但前者使用的是 TickValueProfit() 而不是 TickValueLoss() 方法,且结果和指定的风险/回报比相乘: 

double CMoneyFixedRiskExt::CalcOrderEquityReward(double price,double sl, double lots, double rrratio)
{
   double equityReward; 
   equityReward = lots*m_symbol.TickValueProfit()*(MathAbs(price-sl)/m_symbol.TickSize())*rrratio; 
   
   if (dbg) Print("calcEquityReward: lots = " + DoubleToString(lots) + 
                   " TickValueProfit = " + DoubleToString(m_symbol.TickValueProfit()) +
                 " reward = " + DoubleToString(equityReward));
   return equityReward;
}

这些方法足以计算最大止损水平和返回实时资产净值风险和回报。CalcMaxTickLoss() 方法用于修正风险矩形绘图 – 如果交易人员希望进行超出他可以承担损失的价格变动次数的交易,仅绘制他可以损失的价格变动最大次数的矩形。

直接从图表上查看令交易变得更加简单。您可以观看文章末尾的演示。

6. 图表事件处理程序的实施

在 m_switchOrderTypeButton 对象上接收到 CHARTEVENT_OBJECT_CLICK 事件后,EA_switchOrderType() 处理程序触发。它在 ORDER_TYPE_BUY 和 ORDER_TYPE_SELL 之间切换订单类型、重置按钮状态和对话框的变量,并在图表上删除风险和回报矩形对象:

void EA_switchOrderType()
  {
   symbolInfo.RefreshRates();

   visualRRDialog.SwitchOrderType();
   visualRRDialog.ResetButtons();
   visualRRDialog.SetSL(0.0);
   visualRRDialog.SetTP(0.0);
   visualRRDialog.SetMaxAllowedLots(0.0);
   visualRRDialog.SetOrderLots(0.0);
   visualRRDialog.SetMaxTicksLoss(0);
   visualRRDialog.SetOrderEquityRisk(0.0);
   visualRRDialog.SetOrderEquityReward(0.0);

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
   SL_arrow.SetInteger(OBJPROP_TIME,0,TimeCurrent());

   rectReward.Delete();
   rectRisk.Delete();

   visualRRDialog.Refresh();

  }

在图表上拖放 SL_arrow 对象后,EA_dragBuyHandle() 处理程序触发。首先,它从图表读取 SL_arrow 对象放置点时间和价格参数,并将价格水平设置为我们交易的假设止损。

然后,它计算对于指定风险我们可以在资产净值上开仓的手数。如果止损值无法保证该交易品种可能的最低交易手数的风险目标,它会自动移动至可能的最大止损水平。这帮助我们评估指定风险的止损空间。

计算风险和回报后,矩形对象在图表上更新。

void EA_dragBuyHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

// 买进
   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);

   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()<SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), 
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

EA_dragSellHandle() 针对卖出订单配置触发。

计算基于 symbolInfo.Bid() 价格,且矩形相应绘制得出,即标示利润低于当前价格水平的绿色区域。

void EA_dragSellHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double maxSLAllowed=MM.GetMaxSLPossible(symbolInfo.Bid(),ORDER_TYPE_SELL);

   if(SL_price>maxSLAllowed)
     {
      SL_price=maxSLAllowed;
      SL_arrow.SetDouble(OBJPROP_PRICE,0,maxSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()>SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   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());

  }

EA_placeOrder() 在按下 m_placeOrderButton 对象后触发。它针对计算得出的止损和获利水平以及指定的手数下达买入或卖出市价单。

请注意,使用 CExpertTrade 类下达市价单是十分容易的。

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()));

   return orderPlaced;
  }

在编辑下列编辑字段之一后,EA_editParamsUpdate() 处理程序在按下 Enter 键后触发:riskRatioEdit、riskValueEdit 和 orderLotsEdit。

当这种情况发生时,需重新计算允许的手数、获利水平、最大价格变动损失、资产净值风险和回报:

void EA_editParamsUpdate()
  {
   MM.Percent(visualRRDialog.GetRiskPercent());

   SL_arrow.GetDouble(OBJPROP_PRICE, 0, SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME, 0, startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);
   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(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();
  }

EA_onTick() 在每次新的价格变动到来时调用。计算仅在订单尚未下达且止损水平已通过拖放 SL_arrow 指针选择时执行。

下单后,风险和回报与获利水平以及风险和回报的重新绘制不是必需的。

void EA_onTick()
  {
   if(SL_price!=0.0 && orderPlaced==false)
     {
      double lotSize=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());
         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(0);
  }

函数 rectUpdate() 用于重新绘制彩色风险和回报矩形。控制点为 SL_arrow 对象起始时间、基于订单类型的当前买入或卖出价值以及止损和获利水平。浅粉色矩形显示当前价格和止损水平之间的价格范围,淡绿色矩形显示当前价格和获利水平之间的价格范围。

两个矩形都是观察风险/回报比对止损和获利价格水平的影响以及在进行交易前帮助调整风险的好工具。

void rectUpdate(ENUM_ORDER_TYPE orderType)
  {
   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   if(orderType==ORDER_TYPE_BUY)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Ask(),currentTime,symbolInfo.Ask()+
                       (symbolInfo.Ask()-visualRRDialog.GetSL())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Ask());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
   else if(orderType==ORDER_TYPE_SELL)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Bid(),currentTime,symbolInfo.Bid()-
                        (visualRRDialog.GetSL()-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Bid());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
  }

 

7. 演示

请观看下面操作中的工作“EA 交易”演示。在 2010 年 1 月 11 日星期一开盘后不久的大反弹后,我下达了卖出订单。

请将视频设为 480p 全屏以获得最佳观看体验。注释随附在视频中:

 

总结

在后续文章中,我介绍了基于预定义的风险和风险/回报比为手动交易建立交互式“EA 交易”的方法。

我介绍了使用标准类在图表上显示内容以及处理图表事件以输入新数据和处理拖放对象的方法。我希望,我提出的理念可作为以 MQL5 构建其他可配置的可视化工具的基础。

所有源文件和位图已附于本文。

由MetaQuotes Software Corp.从英文翻译成
原始文章: https://www.mql5.com/en/articles/192

附加的文件 |

下载ZIP
crrdialog.mqh
(27.8 KB)
images.zip
(159.05 KB)
interactive_expert_mql5_doc.zip
(1445.52 KB)
moneyfixedriskext.mqh
(14.36 KB)
visualrrea.mq5
(27.41 KB)
visualrrids.mqh
(1.61 KB)

 

 


MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投