
MetaTrader 5 中的出价/要价(Bid/Ask)点差分析
概述
如果您在交易入场和离场时不使用限价或定损订单,那么您用的就是市价订单,当然,您得到的最终价格则会基于这些订单买卖点差的大小来判定。
当您激发买入按钮时,您实际上以卖家的要价买入,其点差可能比您决定买入时的买家出价高一点。
当您激发卖出按钮时,您实际上以买家的出价卖出,其点差价低于卖家的要价。
当然,在您激发平仓按钮了结您之前做多的持仓时,您实际上以当前的买家出价卖出。
反之亦然,当您激发平仓按钮了结之前做空的持仓时,您实际上以当前卖家的要价回购或回补。
现在我们可以利用来自 MetaTrader 5 的即时报价数据来分析近期历史得真实平均买卖点差是多少。
您不需要查看当前点差,因为若您同时显示出价和要价指示线时,该值已出示。
我们来看看这是为什么以及如何应对
查看这些图表,您可以看到该经纪商平台的点差大部分为 5 个点。
如果是这种情况,那么您在交易得开始和结束往返过程中的成本应该是 1 个点。
故此,对于具有 1/1 回报风险比率、10 点止损和 10 点止盈的交易,您的成本应该为 10% 的风险/投注。
这样的点差足够公平,例如博彩公司的过盈率通常为 15%,赌场的利润率约为 4%。
但实际的平均点差(红线)与经纪商平台记录的点差(黑色虚线)相比,大多是下方数据窗口内所确认的官方宣称点差的两倍。 在早期示例中拥有相同 SL 和 TP,您的成本通常至少为 2 个点或 20%。
若您运用小尺度的剥头皮,即,用 5 个点的 SL 和 TP,或者若您决定如前面例子触发 10 个点 SL 或 TP 即离场,或说亏损 5 个点,那么成本同样是 2 个点,但因为交易后行情开始对您不利,出于安全,百分比成本现在是您投注/风险的 40%。
当我还是萌新交易者时,我一开始用 5 个点 S/l 和 10 个点 T/P 来实现 2:1 的风险/回报比(我怀疑许多萌新交易者都是如此做)。 我并未成功。
因此,我针对 EURUSD M1 图表利用一款可靠的之字指标进行了深入分析。 我将腿长设置为最小 5 个点,对于我来说,这是足以下手的回撤。
结果似乎表明,大多数小幅波动都在 7 个点左右,相比之下,10 个点的腿长相对较小。 当然,我考虑到新闻发布和波动行情,因此结果主要来自交易时段的平均周期。
因此,我选用 10 个点止损,并保留止盈为空,如此我就可以密切监控交易,且若交易盈亏已达 7 个点,则决定交易何时离场。 成果有一些改进,但只给我留了一点蝇头小利。 直到此刻我才注意到与该经纪商对赌时超高的买卖点差,由此转而寻找更好的经纪商。
如果您在新闻发布或行情波动时进行交易,您会看到实际平均点差上升到 15 个点左右,或标准 5 个点的 3 倍,如此您必须支付 3 点或 60% 的投注。
甚至不考虑在英国时间 20:30(图表服务器时间为 21:30)之后交易,那时很可能是 4、5、6 倍甚至更高,尤其是如果您决定持仓过周末,正如您在下面看到的,这几乎是标准 5 个点点差的 10 倍,除非您的止损和止盈价位留有非常大的空间。
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 个点。 另请注意,实际平均点差甚至高于该经纪商的高位。
下面的 GBPAUD 图表示意,日间交易宣称的点差约为 0.00019,比零高约 2.5 个点差水平,因此对应于 EURUSD 图表中的约 12 个点。 另请注意,在该图表中,实际平均点差值非常接近该经纪商宣称的数值。
下面的 GBPJPY 图表示意,日间交易宣称的点差约为 0.020,比零高约 3 个点差水平,因此对应于 EURUSD 图表中的约 15 个点。 另请注意,在该图表中,实际平均点差值非常接近该经纪商宣称的数值。
下面的 USDJPY 图表示意,日间交易宣称的点差约为 0.0050,比零高约 1 个点差水平,因此对应于 EURUSD 图表中的约 5 个点。 另请注意,在此图表中,实际平均点差值再次大致为宣称值的两倍,因此针对 EURUSD 风险/回报百分比水平的评论也同样适用于此处。
此处还有几个例子,您可自行对点差水平之间的关系进行评估。
第二个参数是布尔值 'doPrint',它会在代码中被检查,如果为真,则将各根柱线的统计数据打印到智能系统日志,如下面的示例所示。 如果 'numRecentBarsBack' 的值太大,这会减慢指标的速度,因此默认值为 100。
如果您把 'doPrint' 参数设置为 true,并将 'numRecentBarsBack' 设置为一个合理的值,例如 30 分钟图表的 100,或 1 分钟图表的 300,那么您可以复制日志记录,并将它们发送给您的经纪商作为其平台提供的真实买卖点差的证据。
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 Ltd译自英文
原文地址: https://www.mql5.com/en/articles/9804
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


我疯狂地道歉,但请看下面:
1.任何经纪商都有一个营销部门,负责宣传公司的服务,包括通过尽可能低的点差。不要将广告与实际情况混为一谈。营销人员对公众只有一个有用的功能--吸引您的注意力;他们不对质量负责。
2.作为买卖双方情绪差异的价差在很大程度上取决于市场流动性。而流动性又在一夜之间发生变化,并取决于市场的状况。2014 年,我看到美元兑日元有六个月处于静止状态,价差相当大。你根本无法交易--价格走势不佳,价差是 2012-13 年的 2-3 倍。然后到了年底,一股不可阻挡的趋势开始了,流动性充裕,市场咆哮!有时,3 位数(美元兑日元)的点差达到 1-2 点。至于每天的交易时间,美国人的资金最多(美国是全球的金融中心)。此外,欧洲的总体流动性与美国时段重叠。因此,此时点差低,流动性高,每个人都玩得很兴奋,市场是流动的。如果您进行日内交易,请只选择美国时段。
3.同一时刻同一品种的市场点差是不一样的!同时买入或卖出的交易量越大,得到的价格就越差。有一个价格叠加的 概念。通常,交易量越大,两者之间的价格差异就越大。而最小的交易量则紧挨着。这种琐事以 ticks 显示,作者出于某种原因对其进行了严格的分析。但是,这种琐事并不是市场本身。这就是为什么大型投资基金不会一次性买入整个交易量的原因。一条大鱼要进入市场,必须经过努力,而且通常不是一天两天。最大的参与者开始从那些交易量接近的人那里少量买入资产,按顺序获得头寸。他们还必须按顺序退出。如果它们火力全开,在任何可接受的价格下,都会出现崩盘或因快速退出而急剧上涨。因此,大玩家往往要付出更多的市场成本。因此,他们总是以周为单位,以季度为单位进行博弈。
他们会为这样一篇文章支付 200 美元吗?
I 和一些信号--不同。基于我们以卖出价买入的事实,在买入时我们只对这个价格感兴趣。收盘时实际上是卖出交易,因此我们只对买入价感兴趣。价格是多少、价差是多少都无关紧要。如果价格不错,我们就应该朝适当的方向交易。
示例:通常在隔夜利息中,点差非常大,很多货币对都是平的。在某一点上,您会看到卖出价远远低于形成的走廊。我们知道买入价会在价差恢复时回到走廊顶部,因此买入。在买入的那一刻,我们对买入价完全不感兴趣。
,您可以在信号服务 中看到类似的策略,交易时间为 23 点至凌晨 1 点。顺便说一句,它们是最稳定的策略之一。
这就是 "没有必要在价差增大时交易 "的说法在实践中的体现。