本文档详细介绍了在离散日志合约(DLC)中,针对数值结果的赔付曲线序列化方法。主要包括通用赔付曲线(由分段多项式或双曲线组成)以及双曲线赔付曲线的具体序列化和反序列化(评估)过程。通过这些方法,可以更有效地表示和处理各种复杂的赔付曲线,尤其是在合约差价(CFD)等场景下,可以简明扼要地表示赔付规则。
当为 数值结果 构建 DLC 时,通常会有数量非常庞大的 可能结果,以至于无法在报价中实际枚举所有结果,以及它们相关的赔付。
通常,存在一些宏观结构,其中一些参数决定了 所有可能结果的赔付,使得这些参数成为数值结果 DLC 赔付曲线的更简洁的序列化方式。
本文档首先详细说明了所谓的 通用赔付曲线 的序列化和反序列化(又名评估),这应该足以应对任何简单的赔付曲线,例如那些 由一些直线组合而成的曲线,以及不保证创建自己的类型的自定义赔付曲线。
本文档还详细说明了双曲线形状的赔付曲线段的序列化和反序列化,这些曲线段对于许多反向合约(例如差价合约 (CFD))非常有用,其中赔付
曲线的形式为 constant/outcome
(其中 outcome
是输入值)。
此规范的目标是高效、紧凑地启用通用赔付曲线形状,同时确保 更简单、更常见的赔付曲线(例如远期合约的直线)在符合通用结构的同时不会变得复杂。
具体来说,通用赔付曲线规范支持分段函数集(各个段之间没有连续性要求),其中段可以是多项式或双曲线。
如果你希望支持的某些赔付曲线“形状”不能有效地表示为分段多项式
函数,那么你应该提出一种新的 payout_curve_piece
类型,该类型能有效地指定最少的参数集,从中可以确定整个赔付曲线段。
例如,“形状” 1/outcome
仅由几个参数完全确定,但当使用多项式插值来近似时,可能需要数千个插值点,因此引入了特定于双曲线的类型。
请注意,赔付曲线是协议级别的抽象,并且在应用程序层,用户很可能会 与某些合约模板上的一组参数进行交互。 他们不会直接与通用赔付曲线及其序列化进行交互,这些曲线旨在用于在核心 DLC 逻辑实现之间进行高效序列化和确定性再现。 通用赔付曲线还可以处理大量常见应用程序用例的插值逻辑, 也就是说,应用程序可能不必计算所有结果点来序列化为此格式, 而是通常只能使用用户提供的参数来直接计算少量相关的 插值点。
在本节中,我们将详细介绍通用 payout_function
的 TLV 序列化。
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 计算期间那样重复且顺序地评估插值时,可以对这种分段插值函数进行许多优化。
在计算顺序输入时,可以避免二分查找。
在评估多项式段时,可以为多项式段中的每个 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))
。
由于直线是多项式,因此当以分段多项式函数的语言表示时,简单曲线保持简单。 可以使用巧妙构造的 多项式插值 来紧密近似任何有趣的(例如,非随机的)赔付曲线。 最后,只需提供每个多项式段的几个点即可紧凑地完成这些函数的序列化,以便 使通信中的接收方能够从这些最少的信息中插入多项式。
但重要的是要注意,由于 龙格现象,客户端通常最好使用某些 样条插值 而不是直接使用多项式插值来构造其赔付曲线(除非线性近似就足够了) 其中样条由多项式段组成,因此可以将结果插值写成分段多项式。
payout_curve_piece
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
的解释方式完全相同,即作为 x
和 y
坐标。
在 num_pts
为 0
的特殊情况下,仅使用端点,这意味着在端点之间插入一条直线。
有很多方法可以计算由某些插值点集确定的唯一多项式。 我选择在此处详细说明 拉格朗日插值,因为它相对简单,但是任何算法都应该有效,只要它不会导致近似值出现过大的误差,以至于无法通过 验证。 仅举几个其他算法,如果你对替代方法感兴趣,你可能希望使用 范德蒙德矩阵 或 另一种替代方法,差商 方法。
请注意,虽然以下内容可能看起来很复杂,但它应该归结为很少的几行代码。 此外,这个问题是众所周知的,并且大多数语言的解决方案可能都存在于 Stack Overflow 等站点上,可以从中复制这些解决方案,因此只需要进行一些细微的美学修改。
给定一个潜在的 event_outcome
,按如下方式计算 outcome_payout
(其中 points 是一个包括 endpoint
的列表):
lagrange_line(i, j) = (event_outcome - points(j).event_outcome)/(points(i).event_outcome - points(j).event_outcome)
lagrange(i) := PROD(j = 0, j < points.length && j != i, lagrange_line(i, j))
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'
。
payout_curve_piece
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_outcome
和 translate_payout
分别对应于值 f_1
和 f_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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!