市场数学:盈利、亏损、和成本·进阶篇
📘

市场数学:盈利、亏损、和成本·进阶篇

第 2/2 篇

◍ 拆穿盈亏计算里的反直觉约定

很多技术文献把买入 EURUSD 写成「+EUR / -USD」的双向兑换,读起来像基准货币真进了你口袋。实际开仓时你只是从共享池里借出美元换成欧元,负值根本不会在开仓瞬间出现,而是平仓卖回欧元还美元时才结算。把拆借动作显式拆开,逻辑就顺了:USD1 = Ask1 * Lot * ContractSize 是借入,EUR1 = Lot * ContractSize 是换得,平仓后 Profit_USD = Bid2*Lot*ContractSize - Ask1*Lot*ContractSize。 顺着这个视角,任何品种盈利都只用第二种货币计,基准货币在整段持仓里净额为 0。直盘如 EURUSD 存美元时,TickValue = ContractSize * Point 直接可用;但交叉盘 AUDNZD 赚的是 NZD,要乘转换率 BidAlphaCross 才落回存款币。 转换率取法很直白:看品种第二货币,找含它和存款币的盘。EURCHF 赚 CHF、存 USD,就用 USDCHF 的 PAsk 取倒数 BidAlphaCross = 1/PAsk;AUDNZD 赚 NZD、存 USD,用 NZDUSD 的 PBid,BidAlphaCross = PBid。经纪商在每个兑换动作上抽点差,本质就是兑换税。 把 Bid 换成 Mid 减半点差重写,买卖盈亏最终收敛成同一骨架:Profit = Lot * TickValueCross * [(Mid差)/Point] - Lot * TickValueCross*(S1/2+S2/2)。点差项独立拎出来,持仓多久抽多少一目了然。外汇与贵金属杠杆高,点差和滑点会直接吃掉薄利,开 MT5 用上方公式对账历史订单验证最实在。

掉期利息的逐点汇总写法

前面得到的掉期等式 Swap = Lot * TickValue * SwapPoints * SwapCount 只在最简情形下成立。当盈利货币和存款本币不一致时,TickValue 要换成 TickValueCross = ContractSize * Point * BidAlphaCross,且 BidAlphaCross 随品种和本币变化。 和佣金、点差不同,掉期利息在持仓期间可能记贷多次,尤其在交叉汇率下,每个记贷点的 BidAlphaCross 都会变,单用一个 TickValueCross 会算偏。完整写法要对每一次日期变更(通常每天 0:00)做求和: Swap = SUMM(1…D){ Lot * (SwapPoints * K[i]) * TickValueCross[i] } Swap = SUMM(1…D){ Lot * ContractSize * BidAlpha[i] * (SwapPercent/100 * K[i]) } 其中 K[i] 为 1 或 3(三倍掉期日取 3),TickValueCross[i] 和 BidAlpha[i] 都是按记贷点展开的数组。 为方便核对,把符号缩写为 T[i]=TickValueCross[i]、B[i]=BidAlpha[i]、K[i]=K[i],在 MT5 里用数组循环把每日数值累加即可。外汇与贵金属杠杆品种掉期可为负,且受节假日与经纪商规则影响,实际费用以账户历史成交为准,开 MT5 用下列结构验证自己的品种参数。

「用真实订单回测验证盈亏数学模型」

要把写出来的盈亏公式真正用起来,得先定义一个对照框架:用历史订单的真实盈利(Real)做基准,再分别算出开单时 MQL5 自带 OrderCalcProfit 的值(BasicCalculated)、我们模型在开单时刻的推算(CalculatedStart)、以及平仓时刻的推算(CalculatedEnd)。

衡量误差只看相对偏差。对 n 笔订单,平均相对偏差分三组:AverageDeviationCalculatedMQL、AverageDeviationCalculatedStart、AverageDeviationCalculatedEnd,各自等于 100 *推算值 - 真实值/真实值的求和再除以 n;最大偏差则用 Max 取代 Summ,取单笔峰值。把这些写进任意 EA,回测完打印到终端日志即可。

我跑了全部 28 个直盘与交叉盘。前四种盈利货币等于存款本币时,MQL5 和自写代码都零偏差;接下来三个基准货币等于盈利货币的品种,MQL5 更稳,但自写代码开单时误差明显高于平仓时——这间接说明 TickValueCross 更该在平仓那一瞬间取数。其余交叉盘里自写功能不输内置函数,且平仓计算全程更准。第二区块首行出现零值原因未明,可能和模型无关。 下面这段是模型核心:买入卖出合一,bDirection 控方向,TickValueCross 负责跳价价值。 测试外汇与贵金属前务必清楚:杠杆与汇率跳变属高风险,偏差再小也不代表实盘免亏。

MQL5 / C++
class="type">class="kw">double CalculateProfitTheoretical(class="type">class="kw">string symbol, class="type">class="kw">double lot,class="type">class="kw">double OpenPrice,class="type">class="kw">double ClosePrice,class="type">bool bDirection)
  {
  class=class="str">"cmt">//PrBuy = Lot * TickValueCross * [ ( Bid2 - Ask1 )/Point ]
  class=class="str">"cmt">//PrSell = Lot * TickValueCross * [ ( Bid1 - Ask2 )/Point ]
  if ( bDirection )
    {
    class="kw">return lot * TickValueCross(symbol) * ( (ClosePrice-OpenPrice)/SymbolInfoDouble(symbol,SYMBOL_POINT) );
    }
  else
    {
    class="kw">return lot * TickValueCross(symbol) * ( (OpenPrice-ClosePrice)/SymbolInfoDouble(symbol,SYMBOL_POINT) );
    }  
  }
class="type">class="kw">double TickValueCross(class="type">class="kw">string symbol,class="type">int prefixcount=class="num">0)
  {
  if ( SymbolValue(symbol) == SymbolBasic() )
    {
    class="kw">return TickValue(symbol);
    }
  else
    {
    class="type">MqlTick last_tick;
    class="type">int total=SymbolsTotal(false);class=class="str">"cmt">//symbols in Market Watch
    for(class="type">int i=class="num">0;i<total;i++) Symbols[i]=SymbolName(i,false);
    class="type">class="kw">string crossinstrument=FindCrossInstrument(symbol);
    if ( crossinstrument != "" )
      {
      SymbolInfoTick(crossinstrument,last_tick);
      class="type">class="kw">string firstVAL=StringSubstr(crossinstrument,prefixcount,class="num">3);
      class="type">class="kw">string secondVAL=StringSubstr(crossinstrument,prefixcount+class="num">3,class="num">3);
      if ( secondVAL==SymbolBasic() && firstVAL == SymbolValue(symbol) )
        {

◍ 跨品种报价与账户币种的对齐逻辑

这段实现解决一个实操痛点:当交易标的的报价币种和账户结算币种不一致时,如何把tick价值换算成账户口径。核心思路是先拆出交易对的基准段与报价段,再去已加载品种列表里反查交叉品种。 FindCrossInstrument 用 StringSubstr 从品种名固定位置截 3 字符,prefixcount 默认 0 时即取前 3 位与第 4–6 位。若反向或正向匹配到 SymbolBasic(账户币种)与 SymbolValue(标的报价币种),就返回该交叉品种名,否则回空串。 SymbolBasic 直接取 ACCOUNT_CURRENCY,SymbolValue 取标的后 3 位,两者组合即可定位如 USDJPY 这类桥接品种。外汇与贵金属杠杆高,跨币种换算误差会放大仓位风险,最好在 MT5 里用 Print 把返回品种打出来核对一遍。

MQL5 / C++
class="type">class="kw">string FindCrossInstrument(class="type">class="kw">string symbol,class="type">int prefixcount=class="num">0)
  {
  class="type">class="kw">string firstVAL;
  class="type">class="kw">string secondVAL;
  for(class="type">int i=class="num">0;i<ArraySize(Symbols);i++)
    {
    firstVAL=StringSubstr(Symbols[i],prefixcount,class="num">3);
    secondVAL=StringSubstr(Symbols[i],prefixcount+class="num">3,class="num">3);
    if ( secondVAL==SymbolBasic() && firstVAL == SymbolValue(symbol) )
      {
      class="kw">return Symbols[i];
      }
    if ( firstVAL==SymbolBasic() && secondVAL == SymbolValue(symbol) )
      {
      class="kw">return Symbols[i];
      }     
    }
  class="kw">return "";
  }
class="type">class="kw">string SymbolValue(class="type">class="kw">string symbol,class="type">int prefixcount=class="num">0)
  {
  class="kw">return StringSubstr(symbol,prefixcount+class="num">3,class="num">3);
  }
class="type">class="kw">string SymbolBasic()
  {
  class="kw">return AccountInfoString(ACCOUNT_CURRENCY);
  }

附带的EA才是验证入口

这套跨币种盈亏与交叉汇率计算模型已在 MT5 策略测试器里跑过,核心函数能直接塞进 EA、指标或脚本里复用,不只是算盈利亏损那么窄。 文末挂了一个 ProfiCalculation.mq5(约 50.77 KB),里面是模型全部函数和调用示例。把它下下来丢进 MT5 编译,自己跑一遍,对照文内表格看数字是否对得上,比只读文字直观得多。 外汇和贵金属杠杆高、滑点跳空频繁,模型算的是理论值,实盘成交价和点差会啃掉一部分,验证时别把回测结果当落地收益。 评论区有人指出原计算混用了 Tick Value 和 Point Size,转换汇率也没区分买卖方向——你跑 EA 时顺手把这几处核对一下,可能比盲信原文更省事。

常见问题

因为平台盈亏计算用了反直觉约定:盈亏 = (收盘价-开仓价)×合约×手数,再减去点差和掉期,点数赚不等于钱赚,先看成交明细里的计算式。
用循环逐点汇总每根 K 线的掉期值再累加,别用收盘一次性扣,能避免跨日跳变漏算,具体可写数组存每日 swap 后求和。
可以,小布能读取你账户历史订单,按文中公式重算盈亏并标出偏差点,打开品种页就能看诊断,不用自己手抄数据。
先把品种报价按实时汇率转成账户币种等价价,再套盈亏公式,否则跨品种会系统性偏小或偏大,汇率取平仓瞬间的就行。
用平台策略测试器加载文中EA跑历史订单回放,比对输出盈亏与账户记录,不一致就说明哪步约定没吃透。