MetaTrader 5 中的出价/要价(Bid/Ask)点差分析

26 十月 2021, 16:03
Paul Kelly
0
1 241

概述

如果您在交易入场和离场时不使用限价或定损订单,那么您用的就是市价订单,当然,您得到的最终价格则会基于这些订单买卖点差的大小来判定。

当您激发买入按钮时,您实际上以卖家的要价买入,其点差可能比您决定买入时的买家出价高一点。

当您激发卖出按钮时,您实际上以买家的出价卖出,其点差价低于卖家的要价。

当然,在您激发平仓按钮了结您之前做多的持仓时,您实际上以当前的买家出价卖出。

反之亦然,当您激发平仓按钮了结之前做空的持仓时,您实际上以当前卖家的要价回购或回补。

现在我们可以利用来自 MetaTrader 5 的即时报价数据来分析近期历史得真实平均买卖点差是多少。

您不需要查看当前点差,因为若您同时显示出价和要价指示线时,该值已出示。


我们来看看这是为什么以及如何应对

查看这些图表,您可以看到该经纪商平台的点差大部分为 5 个点。

如果是这种情况,那么您在交易得开始和结束往返过程中的成本应该是 1 个点。

故此,对于具有 1/1 回报风险比率、10 点止损和 10 点止盈的交易,您的成本应该为 10% 的风险/投注。

这样的点差足够公平,例如博彩公司的过盈率通常为 15%,赌场的利润率约为 4%。


BAS-EURUSD-M30

但实际的平均点差(红线)与经纪商平台记录的点差(黑色虚线)相比,大多是下方数据窗口内所确认的官方宣称点差的两倍。 在早期示例中拥有相同 SL 和 TP,您的成本通常至少为 2 个点或 20%。 


BAS-EURUSD-M30-DW


若您运用小尺度的剥头皮,即,用 5 个点的 SL 和 TP,或者若您决定如前面例子触发 10 个点 SL 或 TP 即离场,或说亏损 5 个点,那么成本同样是 2 个点,但因为交易后行情开始对您不利,出于安全,百分比成本现在是您投注/风险的 40%。 

当我还是萌新交易者时,我一开始用 5 个点 S/l 和 10 个点 T/P 来实现 2:1 的风险/回报比(我怀疑许多萌新交易者都是如此做)。 我并未成功。

因此,我针对 EURUSD M1 图表利用一款可靠的之字指标进行了深入分析。 我将腿长设置为最小 5 个点,对于我来说,这是足以下手的回撤。

结果似乎表明,大多数小幅波动都在 7 个点左右,相比之下,10 个点的腿长相对较小。 当然,我考虑到新闻发布和波动行情,因此结果主要来自交易时段的平均周期。

因此,我选用 10 个点止损,并保留止盈为空,如此我就可以密切监控交易,且若交易盈亏已达 7 个点,则决定交易何时离场。 成果有一些改进,但只给我留了一点蝇头小利。 直到此刻我才注意到与该经纪商对赌时超高的买卖点差,由此转而寻找更好的经纪商。


BAS-EURUSD-M1

如果您在新闻发布或行情波动时进行交易,您会看到实际平均点差上升到 15 个点左右,或标准 5 个点的 3 倍,如此您必须支付 3 点或 60% 的投注。 


BAS-EURUSD-M1-DW

甚至不考虑在英国时间 20:30(图表服务器时间为 21:30)之后交易,那时很可能是 4、5、6 倍甚至更高,尤其是如果您决定持仓过周末,正如您在下面看到的,这几乎是标准 5 个点点差的 10 倍,除非您的止损和止盈价位留有非常大的空间。

BAS-EURUSD-M30-WeekEnd


OnInit() 代码示例

#property indicator_separate_window

#property indicator_buffers 2
#property indicator_plots   2

//--- plots
#property indicator_label1  "ActSpread"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "DeclaredSpread"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrBlack
#property indicator_style2  STYLE_DASH
#property indicator_width2  2

//--- indicator parameters
input int      numRecentBarsBack=100; //#RecentBarsBack M30+~100, M5~200, M1~500
input bool     doPrint=true;          //true=prints to the toolbox\experts log

//--- indicator buffers
double         ActSpreadBuf[], DeclaredSpreadBuf[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()  
{
   int numBars=iBars(_Symbol,PERIOD_CURRENT)-2; 
   
   // Check we have enough data for the request before we begin
   if(numRecentBarsBack>numBars) 
   { 
      Alert("Can't Do ", numRecentBarsBack, "! Only ",  
               numBars, " Bars are Available", 
               " try 100 or so for 30+ minute charts,",
               " 200 for 5 minute, or 500 for 1 minute charts.",
               " Otherwise the indicator may be too slow"
           ); 
           
      return(INIT_PARAMETERS_INCORRECT);
   }

   double sumPrice=0; 
   double avgPrice=0; 

   // Get the standard 5 point spread for the standard EURUSD currency
   double stdSpread=0.00005/iClose("EURUSD",PERIOD_M1,1); // 1.2 ~=  EURUSD std price
   
   //Find out the current average price of the instrument we are using, so we can standardise the spread and _Point
   int CheckAvgPriceBars=MathMin(numRecentBarsBack, 200);
   
   int i=0;
   for(; i<CheckAvgPriceBars; i++)
   {
      sumPrice+=iClose(_Symbol,PERIOD_CURRENT,i);
   }
   avgPrice=sumPrice/(i? i: 1.0);
   
   //convert the stdSpread to stdPoint by dividing by 5, so we compare  apples with apples, not oranges
   double stdPoint=StringToDouble(DoubleToString(avgPrice*stdSpread/5.0,6));

   Print(i, "=bars done, avgPrice=", DoubleToString(avgPrice,6), 
            " std=", DoubleToString(1.2*stdSpread, 6), 
            " stdPoint=", DoubleToString(stdPoint, 6)
         );
   
   SetIndexBuffer(0,ActSpreadBuf,INDICATOR_DATA);         
   SetIndexBuffer(1,DeclaredSpreadBuf,INDICATOR_DATA);    
   
   string indName ="BAS("+_Symbol;
          indName+=" TF="+string(_Period);
          indName+=" stdPoint="+DoubleToString(stdPoint, 6);
          indName+=") Last("+string(numRecentBarsBack)+") Bars";
          
   IndicatorSetString(INDICATOR_SHORTNAME, indName); 
   
   IndicatorSetInteger(INDICATOR_DIGITS,6); 
   
   IndicatorSetDouble(INDICATOR_MINIMUM, 0.0); 

   IndicatorSetInteger(INDICATOR_LEVELS, 20);     
   
   //mark out each standard EURUSD 5 point spread, to compare this currencies spread with EURUSD
   IndicatorSetDouble(INDICATOR_LEVELVALUE,0,  0.000000); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,1,  5*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,2, 10*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,3, 15*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,4, 20*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,5, 25*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,6, 30*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,7, 35*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,8, 40*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,9, 45*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,10,50*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,11,55*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,12,60*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,13,65*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,14,70*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,15,75*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,16,80*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,17,85*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,18,90*stdPoint); 
   IndicatorSetDouble(INDICATOR_LEVELVALUE,19,95*stdPoint); 
    
return(INIT_SUCCEEDED);
}

对于这个简单的 2 个绘图笔指标,只有 2 个参数,第一个 'numRecentBarsBack' 是我们想要分析的柱线数。

我们在 OnInit() 中做的第一件任务就是检查我们是否有足够的数据来满足请求,如果我们还没有,那么我们会提醒用户,并建议采用一些可靠值,然后以错误码提早退出指标。

OnInit() 的其余部分是相当标准的,除了指标子窗口中使用的水平,这些水平设置为对应于标准 EURUSD 5 点差的倍数的值。 

这是一个相当重要的步骤,因为除了要查看所声明值与实际平均点差值之间的比较结果,我们还想查看不同货币的点差与标准 EURUSD 相比的差距,通常其会伴随所有货币的最低点差。

这是一个相当复杂的方法,因为我们必须获得当前 EURUSD 价格(如果不存在则用 1.2 替代),并采用 5 个 EURUSD 点除以该价格来构建标准点差。 然后我们遍历当前外汇金融产品的 numRecentBarsBack 价格(我没有选用非外汇金融产品进行测试)以获得该金融产品的平均价格。

当我们拥有了金融产品的平均价格时,我们然后将金融产品的平均价格乘以先前建立的标准点差,并除以 5,即标准 EURUSD 点差来构建一个四舍五入的标准点差。

这个四舍五入的标准点差之后会用在每个价位值,也包含在指标短名称中,如下面 “exotic” USDMXN 图表中的指标名称所示。

在该 USDMXN 示例中,日间交易宣称的点差约为 0.0025,比零点高约 3 个点差水平,因此对应于 EURUSD 图表中的约 15 个点。 另请注意,实际平均点差甚至高于该经纪商的高位。

BAS-USDMXN-M30

下面的 GBPAUD 图表示意,日间交易宣称的点差约为 0.00019,比零高约 2.5 个点差水平,因此对应于 EURUSD 图表中的约 12 个点。 另请注意,在该图表中,实际平均点差值非常接近该经纪商宣称的数值。

 BAS-GBPAUD-M30

下面的 GBPJPY 图表示意,日间交易宣称的点差约为 0.020,比零高约 3 个点差水平,因此对应于 EURUSD 图表中的约 15 个点。 另请注意,在该图表中,实际平均点差值非常接近该经纪商宣称的数值。

BAS-GBPJPY-M30

下面的 USDJPY 图表示意,日间交易宣称的点差约为 0.0050,比零高约 1 个点差水平,因此对应于 EURUSD 图表中的约 5 个点。 另请注意,在此图表中,实际平均点差值再次大致为宣称值的两倍,因此针对 EURUSD 风险/回报百分比水平的评论也同样适用于此处。

BAS-USDJPY-M30


此处还有几个例子,您可自行对点差水平之间的关系进行评估。

BAS-GBPUSD-M30

BAS-EURGBP-M30

第二个参数是布尔值 'doPrint',它会在代码中被检查,如果为真,则将各根柱线的统计数据打印到智能系统日志,如下面的示例所示。 如果 'numRecentBarsBack' 的值太大,这会减慢指标的速度,因此默认值为 100。

如果您把 'doPrint' 参数设置为 true,并将 'numRecentBarsBack' 设置为一个合理的值,例如 30 分钟图表的 100,或 1 分钟图表的 300,那么您可以复制日志记录,并将它们发送给您的经纪商作为其平台提供的真实买卖点差的证据。

Bid/Ask Spread M20 Log

Bid/Ask Spread M1 Log


OnCalculate() 代码示例

//--- Global variables
//--- Set the date formatting for printing to the log
const uint dtFormat=uint(TIME_DATE|TIME_MINUTES); 

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   //--- Check for no data or Stop flag before we begin               
   if(_StopFlag || rates_total<2)  
   { 
         Alert("Error, StopFlag=", _StopFlag, " #Bars=", rates_total);    
         return(rates_total);    
   }
   
   //only do the report at indicator start up or refresh 
   if(prev_calculated>2) 
   {         
      // if we have already nulled the ActSpreadBuf just do the DeclaredSpreadBuf[] and return.
      if(prev_calculated==rates_total)
      {
         int currBar=rates_total-1;
         DeclaredSpreadBuf[currBar]=spread[currBar]*_Point;
         return(rates_total);
      }
      // else its the start of a new bar so null the ActSpreadBuf 
      else
      {
         int currBar=rates_total-1;
         ActSpreadBuf[currBar]=EMPTY_VALUE;
         return(rates_total);
      }
   }
         
         
   static int start=rates_total-numRecentBarsBack;
   
   MqlTick tickBuf[]; 
   
   double sumSpread=0;
   double thisSpread=0;
   
   int ticks=0; 
   int bid_tick=0; 
   int ask_tick=0; 
   int k=0;
   
   ArrayInitialize(ActSpreadBuf, EMPTY_VALUE);      
   ArrayInitialize(DeclaredSpreadBuf, EMPTY_VALUE); 
   
   for(int i=start; i<rates_total; i++) 
   { 
      sumSpread=0;
      thisSpread=0;
      bid_tick=0;
      ask_tick=0;
      k=0;
      
      ticks=CopyTicksRange(_Symbol, tickBuf, 
                           COPY_TICKS_INFO, // Only bid and ask changes are required
                           time[i-1]*1000,  // Start time of previous bar
                           time[i  ]*1000   // End time of previous bar
                           );
      
      while(k<ticks) 
      {
         if((tickBuf[k].flags&TICK_FLAG_ASK)==TICK_FLAG_ASK)  
            ask_tick++; 
            
         if((tickBuf[k].flags&TICK_FLAG_BID)==TICK_FLAG_BID)  
            bid_tick++; 
         
         sumSpread+=tickBuf[k].ask-tickBuf[k].bid;
         
         k++;
      }
      
      // Ensure no divide by zero errors for any missing tick data
      if(ticks>0) {                    
         thisSpread=sumSpread/ticks;
         ActSpreadBuf[i-1]=thisSpread;  
      }
      else  { 
         thisSpread=0.0; 
         ActSpreadBuf[i-1]=EMPTY_VALUE;  
      }

      DeclaredSpreadBuf[i-1]=spread[i-1]*_Point;
      
      if(doPrint) 
      {            
                  Print(TimeToString(time[i-1], dtFormat), 
                  "  NumTicks="+string(ticks),
                  "  b="+string(bid_tick),
                  "  a="+string(ask_tick),
                  "  AvgSpread=",  DoubleToString(thisSpread/_Point, 1),
                  "  DeclaredSpread=", string(spread[i-1]) 
                  );
      }
   
   }
   
   //don't do stats for incomplete current bar, but can do DeclaredSpread if it has a value
   DeclaredSpreadBuf[rates_total-1]=(spread[rates_total-1]*_Point);

//--- return value of prev_calculated for next call
return(rates_total);
}

从上面的 OnCalculate() 示例中,需要注意的主要点是使用 CopyTicksRange() 仅获取前一根索引柱线/蜡烛图的起点,和当前索引柱线/蜡烛图的起点之间的即时报价数据。 另请注意,我们必须通过乘以 1000 将 time[] 数组转换为毫秒,因为日期时间数据只能精确到秒,而 CopyTicksRange() 需要毫秒。

ticks=CopyTicksRange(_Symbol, tickBuf, 
                           COPY_TICKS_INFO, // Only bid and ask changes are required
                           time[i-1]*1000,  // Start time of previous bar
                           time[i  ]*1000   // End time of previous bar
                           );
      

另请注意,我们累积了出价和要价,尽管我们不会用绘图笔绘制它们。 出价的即时报价值应与 tick_volume[] 数组中的值相匹配,并如数据窗口中所示。


有关下载即时报价的额外说明...

如果您打算检查平常不会用到的货币,您需要从查看\品种符号菜单项双击该货币,添加该货币,之后即可显示该品种。 在此窗口中,您还应该转到即时报价选项卡,并在第一个日期菜单中选定今天之前一个月左右天数的所有即时报价数据;然后把第二个日期菜单设置为明天,从而把数据投喂到本地即时报价数据库。


结束语

在我们交易一种货币之前,我们应该了解我们正考虑的交易类型(剥头皮、区间摇摆、持仓……)的风险百分比是多少,并将我们最喜欢的货币与其它使用通用标准点差的货币进行比较。

根据我的研究,我建议交易者坚持选用与美元直接挂钩的主要货币,即 USDCAD、USDCHF、USDJPY、EURUSD 和 GBPUSD; 因为它们的整体点差最低。

我们都需要让自己的经纪商知道我们现在可以看到他们的真实买卖点差,即使我们好像只支付了交易佣金,但如果他们将点差提高到非常高的水平,我们的成本和风险一样会增加。 祝好运,请记住,如果您在交易时间内找不到具有合理买卖点差的经纪商,请不要进行交易,因为您根本无法获胜!

在任何人提问之前,所述过程只能在 MetaTrader 5 上运行,因为在 MetaTrader 4 中没有即时报价数据,所以这是一个升级的很好理由。

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

附加的文件 |
探索创建多彩烛条的选项 探索创建多彩烛条的选项
在本文中,我将探讨创建烛条自定义指标的可能性,并指出它们的优缺点。
DoEasy 函数库中的图形(第八十二部分):函数库对象重构和图形对象集合 DoEasy 函数库中的图形(第八十二部分):函数库对象重构和图形对象集合
在本文中,我将通过为每个对象分配唯一类型来改进所有库对象,并继续开发库图形对象集合类。
DoEasy 函数库中的图形(第八十三部分):抽象标准图形对象类 DoEasy 函数库中的图形(第八十三部分):抽象标准图形对象类
在本文中,我将创建抽象图形对象类。 该对象用作创建标准图形对象类的基础。 图形对象拥有多种属性。 因此,在实际创建抽象图形对象类之前,我还需要做很多的准备工作。 这项工作包括在函数库的枚举中设置属性。
更好的程序员(第 04 部分):如何成为更迅捷的开发人员 更好的程序员(第 04 部分):如何成为更迅捷的开发人员
每位开发人员都希望能够更快地编写代码,且能够更快、更有效地编写代码并非只是少数人与生俱来的特殊能力。 无论有多少年的敲键盘经验,这项技能每位编码员都可以学习。