赔付曲线序列化

本文档详细介绍了在离散日志合约(DLC)中,针对数值结果的赔付曲线序列化方法。主要包括通用赔付曲线(由分段多项式或双曲线组成)以及双曲线赔付曲线的具体序列化和反序列化(评估)过程。通过这些方法,可以更有效地表示和处理各种复杂的赔付曲线,尤其是在合约差价(CFD)等场景下,可以简明扼要地表示赔付规则。

赔付曲线序列化

介绍

当为 数值结果 构建 DLC 时,通常会有数量非常庞大的 可能结果,以至于无法在报价中实际枚举所有结果,以及它们相关的赔付。

通常,存在一些宏观结构,其中一些参数决定了 所有可能结果的赔付,使得这些参数成为数值结果 DLC 赔付曲线的更简洁的序列化方式。

本文档首先详细说明了所谓的 通用赔付曲线 的序列化和反序列化(又名评估),这应该足以应对任何简单的赔付曲线,例如那些 由一些直线组合而成的曲线,以及不保证创建自己的类型的自定义赔付曲线。

本文档还详细说明了双曲线形状的赔付曲线段的序列化和反序列化,这些曲线段对于许多反向合约(例如差价合约 (CFD))非常有用,其中赔付 曲线的形式为 constant/outcome(其中 outcome 是输入值)。

目录

通用赔付曲线

设计

此规范的目标是高效、紧凑地启用通用赔付曲线形状,同时确保 更简单、更常见的赔付曲线(例如远期合约的直线)在符合通用结构的同时不会变得复杂。

具体来说,通用赔付曲线规范支持分段函数集(各个段之间没有连续性要求),其中段可以是多项式或双曲线。

如果你希望支持的某些赔付曲线“形状”不能有效地表示为分段多项式 函数,那么你应该提出一种新的 payout_curve_piece 类型,该类型能有效地指定最少的参数集,从中可以确定整个赔付曲线段。 例如,“形状” 1/outcome 仅由几个参数完全确定,但当使用多项式插值来近似时,可能需要数千个插值点,因此引入了特定于双曲线的类型。

请注意,赔付曲线是协议级别的抽象,并且在应用程序层,用户很可能会 与某些合约模板上的一组参数进行交互。 他们不会直接与通用赔付曲线及其序列化进行交互,这些曲线旨在用于在核心 DLC 逻辑实现之间进行高效序列化和确定性再现。 通用赔付曲线还可以处理大量常见应用程序用例的插值逻辑, 也就是说,应用程序可能不必计算所有结果点来序列化为此格式, 而是通常只能使用用户提供的参数来直接计算少量相关的 插值点。

曲线序列化

在本节中,我们将详细介绍通用 payout_function 的 TLV 序列化。

payout_function
  1. 数据:
    • [bigsize:num_pieces]
    • [u64:endpoint_0]
    • [u64:endpoint_payout_0]
    • [u16:extra_precision_0]
    • [payout_curve_piece:piece_1]
    • [u64:endpoint_1]
    • ...
    • [payout_curve_piece:piece_num_pieces]
    • [u64:endpoint_num_pieces]
    • [u64:endpoint_payout_num_pieces]
    • [u16:extra_precision_num_pieces]

num_pieces 是构成赔付曲线及其端点的 payout_curve_pieces 的数量。 每个端点由两个 u64 和一个 u16 组成。

第一个整数称为 endpoint,包含实际的 event_outcome,它对应于赔付曲线上的 x 坐标,该坐标是曲线段之间的边界。 第二个整数称为 endpoint_payout,如果值 endpoint 已签名,则设置为等于本地方的赔付,此赔付对应于赔付曲线上的 y 坐标。 第三个整数,一个 u16,称为 extra_precision,设置为小数点后赔付的前 16 位,而这些位已被四舍五入。 这种额外的精度确保插值不会因 由于四舍五入而导致的 endpoint_payout 中的错误而包含较大错误。 确切地说,用于插值的点应该是: (endpoint, endpoint_payout + double(extra_precision) >> 16)。 对于本文档的其余部分,值 endpoint_payout + double(extra_precision) >> 16 被称为 endpoint_payout

请注意,此 payout_function 是从报价方的角度来看的。 要评估接受方的 payout_function,你必须在给定的 event_outcome 处评估报价方的 payout_function,并从 total_collateral 中减去结果赔付。 重要的是,你不要通过将报价方的 payout_function 中的所有 payout 字段替换为 total_collateral - payout 并插入结果点来构建接受方的 payout_function。 这不起作用,因为由于四舍五入,双方的 payout_function 的输出之和可能为 total_collateral - 1,并且包括检查此情况(从任一结果中缺少一个 satoshi)到验证算法中 可能会使验证时间最多慢四倍,并通过打破合理的假设来增加协议的复杂性。

要求
  • num_pieces 必须至少为 1
  • endpoint 必须严格增加。 如果需要不连续性,则应使用“空” polynomial_curve_piece,从而导致 连续 endpoint 值之间的线。 这样做是为了避免关于不连续处的值的歧义。

通用函数评估

给定一个潜在的 event_outcome,按如下方式计算 outcome_payout

  • event_outcome 二分搜索 endpoint
    • 如果找到,则返回 endpoint_payout
    • 否则,在先前和下一个 endpoint 之间(包括端点)评估 payout_curve_piece 上的 event_outcome
参考实现

CET 计算期间的优化评估

当像 CET 计算期间那样重复且顺序地评估插值时,可以对这种分段插值函数进行许多优化。

  • 在计算顺序输入时,可以避免二分查找。

  • 在评估多项式段时,可以为多项式段中的每个 i 缓存值 points(i).outcome_payout / PROD(j = 0, j < points.length && j != i, points(i).event_outcome - points(j).event_outcome),将其称为 coef_i。 对于给定的 event_outcome,令 all_prod = PROD(i = 0, i < points.length, event_outcome - points(i).event_outcome)

然后可以将总和计算为 SUM(i = 0, i < points.length, coef_i * all_prod / (event_outcome - points(i).event_outcome))

  • 引入精度范围时,可以使用曲线段的导数来减少所需的计算量。 例如,在处理三次多项式段时,如果你从左到右移动并且在第一个导数(斜率)为正且第二个导数(凹度)为负时输入新的模精度值,则可以采用该点处曲线的切线,并找到其与下一个模精度值边界的交点。 如果导数的符号相同,则保证从当前 x 坐标到交点 x 坐标的间隔内所有值的模精度都相同。

赔付曲线段

多项式曲线段

由于直线是多项式,因此当以分段多项式函数的语言表示时,简单曲线保持简单。 可以使用巧妙构造的 多项式插值 来紧密近似任何有趣的(例如,非随机的)赔付曲线。 最后,只需提供每个多项式段的几个点即可紧凑地完成这些函数的序列化,以便 使通信中的接收方能够从这些最少的信息中插入多项式。

但重要的是要注意,由于 龙格现象,客户端通常最好使用某些 样条插值 而不是直接使用多项式插值来构造其赔付曲线(除非线性近似就足够了) 其中样条由多项式段组成,因此可以将结果插值写成分段多项式。

多项式序列化
  1. 实现:payout_curve_piece
  2. 类型:0
  3. 数据:
    • [bigsize:num_pts]
    • [u64:event_outcome_1]
    • [u64:outcome_payout_1]
    • [u16:extra_precision_1]
    • ...
    • [u64:event_outcome_num_pts]
    • [u64:outcome_payout_num_pts]
    • [u16:extra_precision_num_pts]

num_pts 是在此曲线段中指定的中点数,这些中点将与周围的 endpoint 一起用于执行插值。 每个点由两个 bigsize 整数和一个 u16 组成,它们的解释方式与 通用赔付曲线endpoint 的解释方式完全相同,即作为 xy 坐标。

num_pts0 的特殊情况下,仅使用端点,这意味着在端点之间插入一条直线。

多项式评估

有很多方法可以计算由某些插值点集确定的唯一多项式。 我选择在此处详细说明 拉格朗日插值,因为它相对简单,但是任何算法都应该有效,只要它不会导致近似值出现过大的误差,以至于无法通过 验证。 仅举几个其他算法,如果你对替代方法感兴趣,你可能希望使用 范德蒙德矩阵 或 另一种替代方法,差商 方法。

请注意,虽然以下内容可能看起来很复杂,但它应该归结为很少的几行代码。 此外,这个问题是众所周知的,并且大多数语言的解决方案可能都存在于 Stack Overflow 等站点上,可以从中复制这些解决方案,因此只需要进行一些细微的美学修改。

给定一个潜在的 event_outcome,按如下方式计算 outcome_payout(其中 points 是一个包括 endpoint 的列表):

  1. lagrange_line(i, j) = (event_outcome - points(j).event_outcome)/(points(i).event_outcome - points(j).event_outcome)
  2. lagrange(i) := PROD(j = 0, j < points.length && j != i, lagrange_line(i, j))
  3. 返回 SUM(i = 0, i < points.length, points(i).outcome_payout * lagrange(i))

双曲线曲线段

此规范的目标是高效、紧凑地启用通用双曲线形状,同时确保更简单、更常见的赔付曲线(例如 constant/outcome)在符合通用结构的同时不会变得复杂。

这是通过总共使用 7 个参数来实现的,这些参数可以(几乎)唯一地表达曲线 1/x 的每个仿射变换。 具体来说,我们有 (f_1, f_2) + ((a, b), (c, d))*(x, 1/x),它是由点 (f_1, f_2) 平移添加到由矩阵 ((a, b), (c, d)) 变换的曲线 (x, 1/x),其中 a*d =/= b*c。 最后一个参数是一个布尔值,用于指定存在歧义时要使用的曲线段。

这种方案使简单曲线保持简单,为了表示任何形式为 constant/x + constant' 的曲线,我们只需 设置 f_1 = b = c = 0, a = 1, d = constant, f_2 = constant'

双曲线序列化
  1. 实现:payout_curve_piece
  2. 类型:1
  3. 数据:
    • [bool:use_positive_piece]
    • [bool:translate_outcome_sign]
    • [u64:translate_outcome]
    • [u16:translate_outcome_extra_precision]
    • [bool:translate_payout_sign]
    • [u64:translate_payout]
    • [u16:translate_payout_extra_precision]
    • [bool:a_sign]
    • [u64:a]
    • [u16:a_extra_precision]
    • [bool:b_sign]
    • [u64:b]
    • [u16:b_extra_precision]
    • [bool:c_sign]
    • [u64:c]
    • [u16:c_extra_precision]
    • [bool:d_sign]
    • [u64:d]
    • [u16:d_extra_precision]

如果 use_positive_piece 设置为 true,则使用 y_1,否则使用 y_2

然后有六个数值,表示为一个 bool 符号(对于正数设置为 true,对于负数设置为 false)、一个 u64 整数和一个 u16 额外精度。 确切地说,使用的数字应该是:(num_sign)( num + double(num_extra_precision) >> 16)。

字段 translate_outcometranslate_payout 分别对应于值 f_1f_2

请注意,此 payout_function 是从报价方的角度来看的。 要评估接受方的 payout_function,你必须在给定的 event_outcome 处评估报价方的 payout_function,并从 total_collateral 中减去结果赔付。

要求
  • a*d 必须不等于 b*c
  • 结果曲线必须为每个 event_outcome 定义(没有除以零)。
双曲线评估

两个曲线段(其中一个由布尔标志选择)只是上述表达式中 y 坐标 的参数化 通过展开表达式时的 x

y_1 = c * (x - f_1 + sqrt((x - f_1)^2 - 4*a*b))/(2*a) + 2*a*d/(x - f_1 + sqrt((x - f_1)^2 - 4*a*b)) + f_2

y_2 = c * (x - f_1 - sqrt((x - f_1)^2 - 4*a*b))/(2*a) + 2*a*d/(x - f_1 - sqrt((x - f_1)^2 - 4*a*b)) + f_2

我们将 y_1 称为正段,将 y_2 称为负段,仅仅是因为它们分别使用正平方根和负平方根。

作者

Nadav Kohen <nadavk25@gmail.com>

知识共享许可协议 <br> 本作品已获得 知识共享署名 4.0 国际许可协议 的许可。

  • 原文链接: github.com/discreetlogco...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
discreetlogcontracts
discreetlogcontracts
江湖只有他的大名,没有他的介绍。