市场数学:盈利、亏损、和成本
内容
概述
在开发智能系统时,我没有付出太多精力关注某些值在计算盈亏时的含义。 创建 EA 并不需要深入研究这个问题。 实际上,考虑到 MQL5 甚至 MQL4 都包含了执行计算所需的全部函数,我为什么要掌握所有这些值? 然而,经历一定的时间,并积累了一定的经验,问题难以避免地开始出现。 最终,我们开始注意到这些以前对我们来说似乎微不足道的细节。 经过一番深思,您意识到 EA 是一头装在口袋里的猪。 我设法在互联网上查找有关该问题的所有数据,结局是很稀少,且很零散。 故此,我决定自己构造它。 阅读本文后,您将得到一个完整且有效的数学模型,并学会理解和正确计算与订单相关的所有内容。
计算订单盈亏的公式
若要开发一个高效的交易系统,首先,有必要理解每笔订单的盈亏是如何计算的。 我们都能够以某种方式计算我们的盈亏,从而维护我们的资金管理系统。 有人是目测,有人是执行粗略的估算,但几乎所有的 EA 都必须能够计算全部量化值。 开发 EA 会训练您的思维,并令您理解哪些及如何计算,这是无价的。 现在就让我们进入正题。 从如何计算订单盈利这一最简单想法入手是值得的。 就我个人而言,我一直都知道盈利计算本质上相当复杂,但基于一些简单的考虑。 出于简化理解,我们假设点差、掉期利息和佣金不存在。 我想,很多人一开始压根就没考虑到这些参量值。 当然,MQL5 语言提供了内置函数,例如 OrderCalcProfit,可能还有其它函数,但在本文中,我打算过一遍基础知识,如此这般所有人就能理解为何以及如何计算。 如此严谨可能会令人费解,但不注意这些参数,如点差、佣金、和掉期利息、等等,是许多交易者犯下的致命错误。 这些参量值中的每一个都以自己的方式影响盈亏。 在我的计算中,我将考虑所有因素,并展示这些小事如何能提供帮助。 不包括点差、佣金、和掉期利息的订单盈亏:
- PrBuy = Lot * TickValue * [ ( PE - PS )/Point ] — 买单盈亏
- PrSell = Lot * TickValue * [ ( PS - PE )/Point ] — 卖单盈亏
- Point — 所选交易品种的最小可能价格变化
- TickValue — 当价格变动 “1” 个点(Point)时,持仓的盈亏值
- PE — 交易收盘价(出价 - Bid)
- PS — 交易开盘价(出价 - Bid)
MQL5 中诸如 Point 和 TickValue 参量值是在预定义变量级别定义的,或者由内置函数 SymbolInfoDouble 返回值的形式提供。 我会在我的文章中定期以这样或那样的方式触及 MQL5 的主题,因为通常只需分析 MQL5 或其某些功能的构建方式,就可以挖掘出许多问题的根底。
现在我们略微拓宽一下对这个等式的理解。 买入订单以要价(Ask)开立,而卖出订单以出价(Bid)开立。 相应地,买入订单将以出价(Bid)平仓,而卖出订单将以要价(Ask)平仓。 我们根据新的修正重写等式:
- PrBuy = Lot * TickValue * [ ( Bid2 – Ask1 )/Point ] — 买单盈亏
- PrSell = Lot * TickValue * [ ( Bid1 – Ask2 )/Point ] — 卖单盈亏
- Bid1 — 卖出交易开单价
- Ask1 — 买入交易开单价
- Bid2 — 买入交易平单价
- Ask2 — 卖出交易平单价
以下规范的片段包含我们稍后需要的大部分数据:
这只是计算所需数据的一部分。 其余数据可调用内置的各种 MQL5 函数获得。 我们以 USDJPY 为例。 事实上,我们不需要依据规范来编写代码,但理解这些显示数据的所在也许非常有用。
我们上路,这次研究佣金。 每笔订单的佣金可通过多种方式计算,但所有主要方法都汇集为我们交易手数的百分比。 还有其它途径可以获取交易佣金,但我不会在此研究它们,因为我们不需要它们。 我将研究两种计算佣金的可能场景。 我相信,这就足够了。 如果我们取掉期利息为基础,那么掉期利息的示例也许会提出另一种常见的佣金计算方法 — 点数计算。
因此,我们有两种看似不同的方法。 然而,正如我们将看到的,这些方法只是与“征税”方法相同的一种直接感知形式,包括点差。 以下是计算佣金的两个等式:
- Comission = Lot * TickValue * ComissionPoints
- Comission = Lot * ContractSize * BidAlpha * ComissionPercent/100
在此,我们看到新参量值 “ContractSize”,它在 MQL5 中也有一个内置函数级别的实现,可接收来自交易服务器的信息。 该数值是最重要的参量之一,并且于所有盈亏计算中属于绝对存在,尽管是以隐式形式,这是为了简化程序员的计算。 站在程序员的视角,我看到了如此简化的有效性。 但我们目前的目标是理解一切。 您读至本文末尾就会明白为什么要这样做。 此外,我还引入了一个额外的 BidAlpha 变量。 我随后会揭示它的含义。 在品种规范中指定的以下参量值一如所示 :
- ComissionPoints – 佣金以点数为单位
- ComissionPercent – 佣金占合约规模的百分比
- BidAlpha = 1 (如果基准货币与账户货币相同)
- BidAlpha = Bid (所选交易品种)
- BidAlpha = Bid (对应汇率,其中所选交易品种的基准货币与过渡交易品种的基准货币相同,第二种货币与存款货币相同)
- BidAlpha = 1/Ask (对应汇率,其中所选交易品种的基准货币与过渡交易品种的第二个货币相同,基准货币与存款货币相同)
事实上,如果取合约规模应用于 USDCHF 货币对,很明显所选货币对的基准货币是 USD。 假设我们的存款是 USD,那么过渡货币就变成了 USDUSD,因此,它的汇率始终是一。 第二种情况更简单。 假设我们有一个 EURUSD 货币对,这也是转换率,所以它的 Bid 是必需的参量值。 第三种情况可能像这样。 假设我们的货币对是 EURNZD。 然后依序,我们必须找到 EUR 和 USD 的转换率。 故,EURUSD 汇率和其 Bid 是我们需要的。 第四种情况则要复杂一些。 假设我们选择的是 CHFJPY。 很明显,过渡货币对是 USDCHF,但由于外汇里没有 CHFUSD 汇率。 当然,我们可以自行创建合成品种 CHFUSD。 在这种情况下,我们可以使用前一种情况。 但实际上,我们只需要把这个品种颠倒过来,那么它的汇率就等于当前“非直盘”汇率的 “1/Ask”。 事实上,我们创建一个合成品红时无需关注它。 掉期也是如此。 还有其它一些问题亦同。 例如,过渡货币应使用哪个汇率 — 出价、要价 或中间价? 这个问题在目前方式中无法解决。 在此过程中,我们将逐渐步入正轨。 现在我们至少大致定义了改进框架。 为此,我们应能至少编写一般盈亏等式的第一个概略版本,同时考虑到所有“征税”选项,例如点差、掉期利息、和佣金。
为了计算掉期利息,我们得到类似的等式:
- Swap = Lot * TickValue * SwapPoints * SwapCount(StartTime,EndTime)
- Swap = Lot * ContractSize * BidAlpha * SwapPercent/100 * SwapCount(StartTime,EndTime)
等式确实非常相似。 唯一的区别是某些乘数以 SwapCount 函数的形式出现在这里。 我希望您能准予我一些术语自由。 我称之为“函数”,因为掉期利息不会立即收费,而其额度取决于订单的开仓和平仓时间。 在粗略的近似中,我们当然可以不用乘数,并编写以下内容:
- SimpleCount = MathFloor( (EndTime -StartTime) / ( 24 * 60 * 60 ) )
如果我们假设结束时间和开始时间是 “datetime” 类型,那么它们的差值等于订单开仓和平仓点区间的秒数。 掉期利息每天收费一次,因此您只需将此值除以一天中的秒数即可。 以这种方式,我们就可以初步了解如何评估持仓的掉期利息。 当然,这个等式远非完美,但它为一些问题给出了答案,诸如它是什么类型的函数?以及它返回什么? 另外,它可以建议如何(至少大约)计算掉期利息。 它返回持仓生存周期区间内的累积隔夜利息额度。 与此类似,规范中的佣金将是掉期利息的两个可能值之一,并强制指定了计算方法:
- SwapPoints – 以点数为单位的单笔持仓滚延掉期利息
- SwapPercent – 以合约百分比为单位的单笔持仓滚延掉期利息
如果在佣金情况下,等式更简单,并且不需要澄清;那么在掉期利息的情况下,一切都要复杂得多,但我们稍后见识这些简化之处的微妙和细微差别。 首先,我们将盈亏等式(不包括佣金和掉期利息)转换为更一致的形式:
- PrBuy = Lot * TickValue * [ ( Bid2 – (Bid1+S1*Point) )/Point ] — 买单盈亏
- PrSell = Lot * TickValue * [ ( Bid1 – (Bid2+S2*Point) )/Point ] — 卖单盈亏
- S1 — 开立买单时的点差
- S2 — 开立卖单时的点差
很明显,要价包括点差和出价。 我们将点差导致的订单的盈亏分离,为其单独汇总:
- PrBuy = Lot * TickValue * [ ( Bid2 – Bid1)/Point ] + ( - Lot * TickValue * S1 ) — 买单盈亏
- PrSell = Lot * TickValue * [ ( Bid1 – Bid2)/Point ] + ( - Lot * TickValue * S2 ) — 卖单盈亏
可以看出,在这两个等式中,一笔汇总已被分离,这是经纪商收取的部分。 当然,这并不是全部金额,但至少现在您可以更清醒地看到,我们得到了什么,以及经纪商拿走了什么。 请注意,在第一种情况下,我们针对点差的“征税”仅取决于开立“买入”持仓时的点差值,而在第二种情况下,在“卖出”持仓平仓时。 由其证明,我们始终在买入时以点差的形式向经纪商进贡部分利润。 事实上,如果我们深入研究外汇交易,很明显,我们的等式能确认,开立多头持仓与空头持仓平仓是等效行为。 在这种情况下:
- S1 — 开立任何持仓时的点差
- S2 — 平仓时的点差
如果您想显示点差,这些参量值正是您可从“市场观察”窗口中看到的数值。 相应的内置 SymbolInfoInteger MQL5 函数配合对应的输入,返回完全相同的值。 您可在 MQL5 帮助中找到输入。 在这种情况下,我的任务是创建一个依赖于 MQL5 语言的直接数学计算模型,如此这些等式就可立即带入到任何 EA,或任何其它有用的 MQL5 代码之中。 以下是我们的汇总,现在它与掉期利息和佣金两者类似:
- SpreadBuy = - Lot * TickValue * S1
- SpreadSell = - Lot * TickValue * S2
开盘价和收盘价的点差
按约定,点差是在买入点计算的,但现在我将向您揭示为什么这是不正确的。 我做了很多市场调查,价格走势的最可预测点竟然是 “0:00” 时刻。 这是从一天到另一天的过渡时刻。 如果您仔细观察这一时刻,您会在所有货币对上看到大致相同的现象 — 汇率朝下行走势跳跃。 发生这种情况是由于此刻点差增加。 跳越随后是等幅的回滚。 什么是点差? 点差是买入价和卖出价之间的缺口。 按约定,这一缺口相信是来自市场深度的后果。 如果市场深度由限价单撑满,则点差趋于零,如果有些参与者离场,点差就会增加。 我们可以称之为市场深度解体。 即使乍一看,我们也可以说 Bid 不是这里的主要内容。 由其顺推,要价和出价本质上是相等的。 如果我们发挥想象,就很容易理解这一点,例如,可以从 “EURUSD” 构建一个 USDEUR 镜像品种,然后出价变为要价,反之亦然,要价变为出价。 简推之,我们只是逆转市场深度。
要价曲线通常不会显示在图表上,尽管这会很有用:
正如我们所见,随着图表周期的增加,要价和出价开始融合。 也许,由于这些考虑,没有终端显示两条线,尽管我个人认为这是一个必要的选项。 但是,了解这些参量值的存在,及它们的差别并没那么重要,因为您仍然可以在 EA 中使用这些东西。 我并未在此画制中间价,但我想每个人都明白这条线正好位于出价和要价中间。 显然,对于较高时期,这些参量值之间的差值实际上都是无关紧要的角色,且似乎您甚至不需要考虑要价的存在,但实际上它还是有必要的。 这些细节非常重要。
考虑到这一点,我们现在可以绝对肯定地说,在这种转变期间,市场深度的中间价是不变的。 该参量值可如下计算:
- Mid = (Ask + Bid) / 2
考虑到如此表示,并带入最后一个等式,我们可以看到:
- Bid = Mid * 2 – Ask
- Ask = Mid * 2 - Bid
接下来:
- Bid = Mid * 2 – (Bid + S*Point) = Mid – (S*Point)/2
- Ask = Mid * 2 – (Ask - S*Point) = Mid + (S*Point)/2
这些表达式现在可以代入计算订单盈亏的原始等式当中。 准确地获得这些表达式很重要,因为我打算向您展示一些您以前不理解的东西。 由其顺推,经纪商收费金额实际上不仅仅取决于买入点,而是针对任何持仓的入场点和离场点两处。 我们来看看当等式里插入新的扩展定义时,它会变成什么样。 我们可以看到以下内容:
- PrBuy = Lot * TickValue * [ ( (Mid2 – (S2*Point)/2) – (Mid1 + (S1*Point)/2) ) )/Point ]
- PrSell = Lot * TickValue * [ ( (Mid1 – (S1*Point)/2) – (Mid2 + (S2*Point)/2) ) )/Point ]
经过相应地转换,我们可以看到这一点:
- PrBuy = Lot * TickValue * [ (Mid2 – Mid1)/Point ] - Lot * TickValue * ( S1/2 + S2/2 )
- PrSell = Lot * TickValue * [ (Mid1 – Mid2)/Point ] - Lot * TickValue * ( S1/2 + S2/2 )
考虑到:
- Bid1 = Mid1 – (S1*Point)/2
- Bid2 = Mid2 – (S2*Point)/2
- Ask1 = Mid1 + (S1*Point)/2
- Ask2 = Mid2 + (S2*Point)/2
请牢记:
- Mid1 — 开立任何持仓时市场深度的中间值
- Mid2 — 平仓时市场深度的中间值
出于便捷起见,我们把负值合计示数定义为来自点差的亏损,如下:
- Spread = -Lot * TickValue * ( (S1*Point)/2 + (S2*Point)/2 )
因此,相应地,汇总指示盈亏,但不包括点差、佣金和掉期利息,例如:
- ProfitIdealBuy = Lot * TickValue * [ (Mid2 – Mid1)/Point ]
- ProfitIdealSell = Lot * TickValue * [ (Mid1 – Mid2)/Point ]
现在我们可以编写直接等式,参考了点差、佣金、和掉期利息的所有亏损。 我们从表达式原型开始。 我们取最后的订单盈亏等式为基础,点差是此处唯一要考虑的:
- TotalProfitBuy = ProfitIdealBuy + (Spread + Comission + Swap)
- TotalProfitSell= ProfitIdealSell + (Spread + Comission + Swap)
也许,我应该在一开始就写出这个等式,但我认为把它放在这里更合适。 我们可以看到,晦涩的 TickValue 几乎无处不在。 主要问题是如何计算,以及如何在不同时间点取得同一个参量值进行计算。 时间点即指入场持仓和平仓离场。 我想,您应当了解该参量值本质上是动态的,且每个交易品种均有所不同。 若不将该参量值分解为组件,我们将得到离“目标”越大的误差。 换言之,得到的等式只是一个近似值。 有一个绝对精确的等式规避了这些缺点。 上面获得的比率是其极值。 极值本身可以表示如下:
- Lim[ dP -> 0 ] ( PrBuy(Mid1, Mid1+dP… ) ) = TotalProfitBuy(Mid1, Mid1+dP…)
- Lim[ dP -> 0 ] ( PrSell(Mid1, Mid1+dP… ) ) = TotalProfitSEll(Mid1, Mid1+dP…)
- Mid1+dP = Mid2 — 新价格是取前一个价格,加上趋于零的增量得来的
- TotalProfitBuy = TotalProfitBuy(P1,P2… ) — 正如判定的那样,盈亏是中间值和许多其它参量值的函数
- TotalProfitSell = TotalProfitSell(P1,P2… ) — 类似
一般来说,对于形势的总体理解,其等效极值可由多种方式拟定。 没有必要将它们多个并举。 在我们的示例中,取其一就足够清晰了。
尽管我们收到了一些等式,且它们甚至可操作,但可适性的限制要求很高条件。 接下来,我们将着手获取包含此类近似等式的初始等式。 若不知道盈利或亏损的基石,我们就永远得不到这些等式。 反推,这些等式不仅有助于我们发现计算盈亏的最精准比率,还可找到市场走势的失衡,随之从中渔利。
计算订单盈亏的最精准方法
为了理解如何构建这些等式,我们需要回到基础部分,即什么是买入和卖出。 但首先,我认为重要的是要牢记,买入实际上意味着您用自己的钱换取一件产品。 另一种货币亦可被视为商品,因为它象征着其本身具有某些商品的属性。 那么就很明显,出售是将第二种货币兑换为第一种货币的逆向过程。 但如果我们省略所有约定,则由其顺推出买卖是等效的行动。 一种货币兑换成另一种货币,唯一的区别是,我们付出哪种货币,以及我们换回哪种货币。
而在搜索有关这些计算的信息时,我发现了一个奇怪的约定,我个人很长一段时间都无法领悟,因为它们没有根基。 鉴于我是一名技术人员,对于研习各种技术资料拥有丰富的经验,我因此判定了两个非常简单的真像。 如果您搞不懂这些资料,并提出疑问,那么:
- 作者自己并未完全理解,所以他们尽一切办法最大限度地策反您(通常会用反智陈词来达到)
- 故意省略详细信息,从而对您隐瞒不必要的信息。
下图深入发掘了这个思路,令其更容易理解。 它示意出两种市价单类型的开仓和平仓:
现在,我认为点差章节与当前章节将变得更加通透。 通常,此图像与整篇文章相关,但在此模块中它用处最大。
当然,我相信专业文献中会有正确的计算,但显而易见,查找这些信息比自己猜测缺少什么更困难。 约定状态下,当我们买入时,例如,EURUSD,我们是买入欧元,并卖出美元。 我们书写下来:
- EUR = Lot * ContractSize
- USD = - Ask1 * Lot * ContractSize = - (Bid1 + S1*Point) * Lot * ContractSize
在这种情况下,由其顺推,在买入时,我们得到基准货币的正值数额,第二种货币为负值数额。 我相信,我不是唯一认为这完全是胡说八道的人。 在为其付出了努力之后,我得出的结论是,这些比率是正确的,但它们的呈现方式实在不直观。 我们来按照以下方式更改它...为了买入欧元,我们需要另一种货币美元,我们有两种支取方式,从余额款项,或从经纪商那里拆借。 换言之,我们首先从一些共享存储中借出美元。 它看起来像这样:
- USD1 = Ask1 * Lot * ContractSize = (Bid1 + S1*Point) * Lot * ContractSize — 这是我们拆借来的
- EUR1 = Lot * ContractSize — 这是我们用拆借来的资金买入的份额,价格取购买时的汇率要价(Ask)
负值将在稍后出现。 事实上,此刻它不可能在此。 负值出现在我们平仓之时。 因此,如果有持仓,则应将其平仓。 由其顺推,我们需要以相同的手数执行卖出操作。 如果我们坚持按标准考虑:
- EUR2 = Lot * ContractSize
- USD2 = Bid2 * Lot * ContractSize
由其顺推,我们已经卖出欧元,并买入美元。 事关我们的转换,由其顺推,我们把那些用自己拆借来的资金兑换的欧元,再兑换回拆借货币。 盈亏就是从得来的资金中减去拆借的资金:
- Profit_EUR = EUR1 – EUR2 = 0
- Profit_USD = USD2 – USD1 = Bid2 * Lot * ContractSize - (Bid1 + S1*Point) * Lot * ContractSize = Lot * ContractSize * ( Bid2 – Bid1 – S1*Point)
由其顺推,欧元消失了,只剩下美元。 如果我们存入的本币是美元,那么我们不需要将生成的货币转换为存款货币,因为它们已是相同的。 该等式与我们一开始作为基础的等式非常相似,唯一的区别是在此处不考虑佣金和掉期利息,因为它们是分开考虑的。 现在我们稍微重写一下这个表达式:
- Profit_USD = Lot * (ContractSize*Point) * [ ( Bid2 – Bid1 – S1*Point) / Point ]
在此,我们简单地在等式右侧除以点数,然后再乘以点,便得到我们的原始等式。 如果我们使用原始的约定系统,即无论交易方向如何,我们都同时买卖,就可以得到相同的等式。 在这种情况下,所有拆借都有一个减号,象征着它是我们欠的,而买入的金额则带有一个加号。 在这样一个约定体系中,我们不需要考虑我们正在以何物换取什么,以及来自何处。 我们用此方式执行相同的操作:
- EUR1 = Lot * ContractSize
- USD1 = - Ask1 * Lot * ContractSize = - (Bid1 + S1*Point) * Lot * ContractSize
这是买入。 行动一。
- EUR2 = - Lot * ContractSize
- USD2 = Bid1 * Lot * ContractSize
这是卖出。 行动二。
进而,一切都被简化了,因为我们不需要考虑从哪里以及如何减去什么。 我们简单地分别相加所有欧元和所有美元。 无论如何,基准货币都会消失,只留下第二种货币。 我们累加并确保等式与之前的相同:
- Profit_EUR = EUR1 + EUR2 = 0
- Profit_USD = USD1 + USD2 = - (Bid1 + S1*Point) * Lot * ContractSize + Bid2 * Lot * ContractSize = Lot * ContractSize * ( Bid2 – Bid1 – S1*Point)
由其顺推,任何交易品种的盈利都只以第二种货币(而非基准货币)计算,且基准货币在整个开仓-平仓周期中始终不会出现。 自然而然地,对于卖出,一切都是镜像。 我们来编写所有这些,完成我们的计算。 现在我们卖出 EURUSD,然后我们经由“买入”操作平仓:
- EUR1 = - Lot * ContractSize
- USD1 = Bid1 * Lot * ContractSize
这是卖出。 行动一。
- EUR2 = Lot * ContractSize
- USD2 = - (Bid2 + S2*Point) * Lot * ContractSize
这是买入,行动二。
现在我们以相同的方式添加所有值:
- Profit_EUR = EUR1 + EUR2 = 0
- Profit_USD = USD1 + USD2 = Bid1 * Lot * ContractSize - (Bid2 + S2*Point) * Lot * ContractSize = Lot * ContractSize * ( Bid1 – Bid2 – S2*Point)
如您所见,等式的区别仅在于 Bid1 和 Bid2 互换。 当然,点差是在持仓的平仓点收取的,因为平仓点是买入点。 到目前为止,一切都还严格按照原始等式。 还值得注意的是,现在我们已知 TickValue 是什么了,至少如果我们品种的第二货币(不是基准货币)与我们的存款本币相匹配。 我们书写这个参量值的等式:
- TickValue = ContractSize * Point
不过,此参量值依然仅适用于部分交易品种,其中盈利的货币等于我们存款本币。 但对于交叉汇率,例如说 AUDNZD,我们用何呢? 此处的要素不在于交易品种本身,而是这个参量值总是相对于我们存款本币计算的,且我们从交易服务器接收它。 但如果我们采用这个等式来计算交叉汇率,那么由其顺推,它当然有效,只是它不会以我们的存款本币回应我们,而是以交易品种的第二货币。 若要将其转换为存款本币,必需将此值乘以确定的比率,这实际上是我们在上一个区块中研究的转换率。
- TickValueCross = ContractSize * Point * BidAlphaCross
转换率的计算非常简单:
- 查看我们交易品种中的第二货币(不是基准货币)
- 寻找包含此货币,以及和我们存款本币的品种
- 以相应的汇率进行兑换
- 若有必要,变换品种(镜像)
例如,如果我们交易 EURCHF,且我们的本币为 USD,那么初始利润将以 CHF 为单位,因此我们可以利用 USDCHF 这个品种及其汇率。 如此,我们需要将 CHF 兑换成 USD,然后由其顺推,我们要买入 USD 兑换 CHF。 但由于 CHF = PBid * USD,那么 USD = (1/PAsk) * CHF,且相应地:
- BidAlphaCross = 1/PAsk
我们在第二个示例中采用另一个品种。 例如,我们交易 AUDNZD,并以 NZD 获利,然后我们可以取 NZDUSD 汇率,由于 USD = PBid * NZD,那么在这种情况下:
- BidAlphaCross = PBid
我们理清楚。 转换 CHF 为 USD 意即 “+USD ; -CHF”。 换言之,我们舍弃了一种货币,而赚取到另一种。 这意味着以买入 USD,卖出 CHF,价格是以 USDCHF 汇率的 PAsk,这实际上意味着以下内容:“USD = (1/PAsk) * CHF”。 以下方式更容易理解它:买入时,我们收到的美元,比之经纪商从我们的交易操作中分文不取,肯定要略少一点。 这意味着如果我们除以更大的 PAsk,我们得到的数值会更小于 1/P。
在第二种情况下,状况正好相反。 转换 NZD 为 USD 意即 “+USD ;-NZD“,这意味着采用 NZDUSD 汇率以 PBid 价格卖出。 我们为 “USD = PBid * NZD” 设置一个类似的比率。 兑换再次以稍差的汇率进行,即 “PBid”。 一切都匹配。 所有一切都是透明的,易于掌握。 请记住,首要的完美汇率是 “PMid”,我在上面曾研究过。 考虑到这一点,很容易理解点差并无特别之处,只不过是经纪商以所兑换货币形式收费的百分比。 因此,每笔交易,无论是开仓还是平仓,都伴随着经纪商基于货币兑换的征税,称之为点差。 其余的税款还包含佣金和掉期利息。
转换率不是必需的,只当盈利货币与我们的存款本币匹配时,比率才等于 1,如此在直盘货币对的情况下,比率消失,且所有这些货币对的跳价大小固定。 如同前面的情况,我们的交易品种也许顺推变成过渡汇率,故此我们不必在其它交易品种中搜索它。
考虑到新的 BidAlphaCross 值的存在,重写订单盈亏等式时,无需佣金和掉期利息:
- BuyProfit = BidAlphaCross * Lot * ContractSize * ( Bid2 – Bid1 – S1*Point)
- SellProfit = BidAlphaCross * Lot * ContractSize * ( Bid1 – Bid2 – S2*Point)
考虑到:
- Bid1 = Mid1 – (S1*Point)/2
- Bid2 = Mid2 – (S2*Point)/2
我们以更直观的形式重写等式,用 Mid 替换其中比率:
- BuyProfit = BidAlphaCross * Lot * ContractSize * ( Mid2 – (S2*Point)/2 – Mid1 + (S1*Point)/2 – S1*Point)
- SellProfit = BidAlphaCross * Lot * ContractSize * ( Mid1 – (S1*Point)/2 – Mid2 + (S2*Point)/2 – S2*Point)
我们来简化这一切:
- BuyProfit = Lot * BidAlphaCross * ContractSize * Point * [ ( Mid2 – Mid1 )/ Point - ( S1/2 + S2/2 ) ]
- SellProfit = Lot * BidAlphaCross * ContractSize * Point * [ ( Mid1 – Mid2 )/ Point - ( S1/2 + S2/2 ) ]
更多简化:
- BuyProfit = Lot * TickValueCross * [ ( Mid2 – Mid1 )/ Point ] - Lot * TickValueCross * ( S1/2 + S2/2 )
- SellProfit = Lot * TickValueCross * [ ( Mid1 – Mid2 )/ Point ] - Lot * TickValueCross * ( S1/2 + S2/2 )
现在,我认为,它已经变得更容易和清晰。 我特意删除了与点差相关的汇总,如此我们便可以看到这准确的收费额,无论我们的持仓或订单的生存期保持多久。
掉期利息精确计算函数
现在尚需澄清的就剩掉期利息等式。 我们先回忆一下我们在文章开头得到的等式:
- Swap = Lot * TickValue * SwapPoints * SwapCount(StartTime,EndTime)
- Swap = Lot * ContractSize * BidAlpha * SwapPercent/100 * SwapCount(StartTime,EndTime)
在最后一块中,我们发现 TickValue 并不是个单数值参量,且对于不同的货币对,其计算也有所不同。 经判定:
- TickValue = ContractSize * Point
但这仅适用于盈利货币与存款本币匹配的货币对。 在更复杂的情况下,我们采用以下参量值:
- TickValueCross = ContractSize * Point * BidAlphaCross
其中 BidAlphaCross 也是一个有差异的数值,取决于存款本币和所选得交易品种。 所有这些我们已在上面都定义好了。 有基于此,我们需要把等式的第一个版本重写,替换标准常数:
- Swap = Lot * TickValueCross * SwapPoints * SwapCount(StartTime,EndTime)
但这个等式仍然远非完美。 这是因为,不像佣金或点差,当您处于持仓状态时,记入贷方的掉期利息可以是大量的任意次数。 由其顺推,在交叉汇率的情况下,一个 TickValueCross 值不足以描述整体掉期利息总额,因为在每个掉期利息计贷点,由于 BidAlphaCross 值会发生变化,参量值略有不同。 我们编写计算两个“征税”选项的掉期利息的完整等式:
- Swap = SUMM(1 … D) { Lot * (SwapPoints * K[i]) * TickValueCross[i] } — 每当 0:00 时日期变更,汇总所有应计掉期利息的点数
- Swap = SUMM(1 … D) { Lot * ContractSize * BidAlpha[i] * (SwapPercent/100 * K[i]) * } — 按照 %
汇总数组:
- K[i] = 1 或 3 — 如果比率为 “3”,这意味着该日应计掉期利息是三倍
- TickValueCross[i] — 掉期利息记贷点的跳价大小的数组
- BidAlpha[i] — 掉期利息收费点的调整费率数组
我们来看一个任意订单的掉期利息计算示例。 为此,我将引入以下简短注符:
- TickValueCross[i] = T[i]
- BidAlpha[i] = B[i]
- K[i] = K[i]
现在我们以图形方式讲述我们将汇总掉期利息:
我们已分析完毕计算订单盈亏的所有可能示例。
实践部分
在本章节中,我们将测试我们的数学模型。 尤其是,我会特别注意在不考虑佣金和掉期利息的情况下计算盈亏的问题。 是否您还记得,我想知道如果我们以交叉汇率计算利润,我应该在什么时间点去计算 TickValueCross 值? 这是我在整个模型测试中唯一不确定的时刻。 为此,我们首先实现所有必要的功能,基于我们的数学模型计算任何订单的盈亏,在策略测试器中对其进行测试,然后,将我们的计算与交易历史中的真实订单数据进行比较。 最终目标是测试我们的数学模型,同时将其与 MQL5 参考函数(如 OrderCalcProfit)进行比较。
为了评估所有这一切,有必要引入四个量化值:
- Real — 来自历史记录的订单盈利
- BasicCalculated — 同笔订单,开单时调用 OrderCalcProfit 函数计算的利润
- CalculatedStart — 在开单时用我们的数学模型计算的利润
- CalculatedEnd — 在平单时用我们的数学模型计算的利润
这三类盈利值平均偏差:
- AverageDeviationCalculatedMQL = Summ(0..n-1) [ 100 * MathAbs(BasicCalculated - Real)/MathAbs(Real) ] / n : MQL5 代码得出的相对盈利偏差
- AverageDeviationCalculatedStart = Summ(0.. n-1 ) [ 100 * MathAbs(CalculatedStart - Real)/MathAbs(Real) ] / n : 开单时我们自己的代码得出的相对盈利偏差
- AverageDeviationCalculatedEnd = Summ(0.. n-1 ) [ 100 * MathAbs(CalculatedEnd - Real)/MathAbs(Real) ] / n : 平单时我们自己的代码得出的相对盈利偏差
与此类似,您可以输入三种类型的最大偏差:
- MaxDeviationCalculatedMQL = Max(0.. n-1 ) [ (100 * MathAbs(BasicCalculated - Real)/MathAbs(Real)) ] - MQL5 代码得出的相对盈利偏差
- MaxDeviationCalculatedStart = Max(0.. n-1 ) [ (100 * MathAbs(CalculatedStart - Real)/MathAbs(Real)) ] - 开单时我们自己的代码得出的相对盈利偏差
- MaxDeviationCalculatedEnd = Max(0.. n-1 ) [ (100 * MathAbs(CalculatedEnd - Real)/MathAbs(Real)) ] - 平单时我们自己的代码得出的相对盈利偏差
其中:
- Summ(0..n-1) — 所有 “n” 笔订单的全部相对偏差之和
- Max(0..n-1) — 来自所有 “n” 笔订单的最大相对偏差之和
我们可在任意 EA 代码中实现这些计算,从而测试我们的数学模型。 我们从实现自己的盈利等式开始。 我已依照以下方式完成这一点:
double CalculateProfitTheoretical(string symbol, double lot,double OpenPrice,double ClosePrice,bool bDirection) { //PrBuy = Lot * TickValueCross * [ ( Bid2 - Ask1 )/Point ] //PrSell = Lot * TickValueCross * [ ( Bid1 - Ask2 )/Point ] if ( bDirection ) { return lot * TickValueCross(symbol) * ( (ClosePrice-OpenPrice)/SymbolInfoDouble(symbol,SYMBOL_POINT) ); } else { return lot * TickValueCross(symbol) * ( (OpenPrice-ClosePrice)/SymbolInfoDouble(symbol,SYMBOL_POINT) ); } }
在此,我们有两个等式合二为一:买入和卖出。 “bDirection” 标记就是负责于此。 计算跳价大小的附加函数以绿色高亮显示。 我已依照以下方式实现它:
double TickValueCross(string symbol,int prefixcount=0) { if ( SymbolValue(symbol) == SymbolBasic() ) { return TickValue(symbol); } else { MqlTick last_tick; int total=SymbolsTotal(false);//symbols in Market Watch for(int i=0;i<total;i++) Symbols[i]=SymbolName(i,false); string crossinstrument=FindCrossInstrument(symbol); if ( crossinstrument != "" ) { SymbolInfoTick(crossinstrument,last_tick); string firstVAL=StringSubstr(crossinstrument,prefixcount,3); string secondVAL=StringSubstr(crossinstrument,prefixcount+3,3); if ( secondVAL==SymbolBasic() && firstVAL == SymbolValue(symbol) ) { return TickValue(symbol) * last_tick.bid; } if ( firstVAL==SymbolBasic() && secondVAL == SymbolValue(symbol) ) { return TickValue(symbol) * 1.0/last_tick.ask; } } else return TickValue(symbol); } return 0.0; }
对于以下情况,在内部还有两处实现:
- 交易品种的盈利货币与我们存款本币相同
- 所有其它情况(要寻找过渡汇率)
第二种情况还分为两种情况:
- 存款本币位于转换率的顶部
- 存款本币位于转换率的底部
一切都严格遵照数学模型。 为了实现最后的部分,我们首先需要找到合适的品种来计算转换率:
string FindCrossInstrument(string symbol,int prefixcount=0) { string firstVAL; string secondVAL; for(int i=0;i<ArraySize(Symbols);i++) { firstVAL=StringSubstr(Symbols[i],prefixcount,3); secondVAL=StringSubstr(Symbols[i],prefixcount+3,3); if ( secondVAL==SymbolBasic() && firstVAL == SymbolValue(symbol) ) { return Symbols[i]; } if ( firstVAL==SymbolBasic() && secondVAL == SymbolValue(symbol) ) { return Symbols[i]; } } return ""; }
为此,我们需要知道如何从交易品种名称中“提取”基准货币:
string SymbolValue(string symbol,int prefixcount=0) { return StringSubstr(symbol,prefixcount+3,3); }
并调用内置的 MQL5 函数提取盈利货币:
string SymbolBasic() { return AccountInfoString(ACCOUNT_CURRENCY); }
在第一次匹配之前,将其与市场报价内所有品种中的所有货币进行比较。 现在我们可在开单和平单时使用该功能。 如您愿意,可以在文后所附文件中查看其余代码。 我在回测结束后添加了偏差计算。 它们把结果写入终端日志。 我测试了所有 28 个直盘货币对和交叉汇率,并将结果放在一个表格当中,以便我们可以评估数学模型的性能,并将其与 MQL5 内置的实现进行比较。 结果共分为三个条件区块。 前两个如下所示:
如您所见,对于前四种货币对,MQL5 和我们的实现都完美运行,因为盈利货币与存款本币相同。 接下来进入的三个货币对区块,其中基准货币与盈利货币相同。 在这种情况下,MQL5 的实现效果最好,但无论如何,计算出的开单时误差远高于平单时的同一误差。 这间接表明,计算确实应该在平单那一刻执行。 我们来看看其它货币对:
在此,我的功能并不逊色于基本的 MQL5。 此外,很明显,平仓时执行的计算在所有时刻都更加精准。 我唯一无法解释的是第二个区块的第一行中存在零值。 可能有多种原因,但在我看来,它们好似与我的模型无关,尽管我也可能是错的。 至于检查佣金和掉期利息的等式,我认为并无必要。 我对这些等式充满信心,因为它们并无特别棘手之处。
结束语
在本文中,我迈入了一个全新开始创建的数学模型,且仅有零碎的信息片段作为指导。 该模型包含计算直盘货币对订单和交叉汇率所需的一切。 该模型已在策略测试器中进行了测试,可以立即在任何 EA、指标或有用途的脚本中加以运用。 事实上,这个模型的适用性比仅仅计算盈利、亏损或成本的方法要广泛得多,但这是另一篇文章的主题。 所有必要的功能和它的示例用法,您都可在我用来编译表格的已研究 EA 中找到。 该 EA 附在文后。 您可自行运行它,并将结果与表格进行比较。 最重要的是,我相信我已经设法编制了一份简单、但符合逻辑的“手册”。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10211