Price Curve Slippage Point and Constant Product (k) Line

基于下面的例子, 我们会画一个完整的「Uniswap V2 交易曲线」示意图:

初始池:1000 A + 2000 B
用户:用 100 A 兑换 B
手续费:0.3%
实际输出:≈ 181.64 B
恒定积 ( k = 2{,}000{,}000 )


🧮 一、恒定积模型原理

Uniswap V2 的价格机制遵循:

[
x \times y = k
]

在坐标平面上:

  • x 轴 = Token A 储备
  • y 轴 = Token B 储备
  • 曲线 ( y = \frac{k}{x} ) 是一条双曲线(即恒定积曲线)

📈 二、交易前后的储备与价格变化示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   y轴: Token B 储备

2000 |● 初始点 P₀ (x=1000, y=2000)
| \
| \
| \
| \
1818 |------● 新平衡点 P₁ (x=1099.7, y≈1818)
| \
| \
| \
| \
+----------------------------------------→ x轴: Token A 储备
1000 1099.7

🟢 点 P₀:初始状态

  • ( reserveA = 1000 )
  • ( reserveB = 2000 )
  • ( P_A = reserveB / reserveA = 2.0 )

🔵 点 P₁:交易后状态

  • ( reserveA’ = 1099.7 )
  • ( reserveB’ = 1818.36 )
  • ( P_A’ = 1818.36 / 1099.7 = 1.653 )
  • 实际成交均价约 1.8164(中间价格)

📉 三、滑点(Slippage)展示

项目 含义 数值
初始价格 ( 1A = 2B ) 2.000
交易后瞬时价格 ( 1A = 1.653B ) 1.653
实际成交均价 平均约 1.816B 1.816
滑点比例 ( (2 - 1.816) / 2 ) 9.2%

🧠 解释:
用户预期 1A=2B,但由于交易影响价格曲线,使池内的 A 增多、B 减少,价格被推低,
因此最终实际获得的平均兑换比例变差。


🔄 四、k 恒定线变化对比

状态 reserveA reserveB A×B = k 备注
交易前 1000 2000 2,000,000 初始状态
交易后(理论恒定) 1099.7 1818.36 ≈2,000,000 恒定积保持
实际变化(含手续费) 部分 k 增加 → 手续费流入池中,k 略增大

手续费 0.3% 进入池子,使得流动性提供者 (LP) 的 k 略上升,
相当于 LP 赚取了一点手续费收入。


🪙 五、交易动态路径说明

1
2
3
4
5
6
7
8
交易过程(从 P₀ 到 P₁):
用户输入 100 A ->
扣除手续费 0.3 A ->
实际进入池中 99.7 A ->
曲线向右移动 ->
池中 B 减少至 1818 ->
用户获得 181.64 B ->
曲线保持 k 恒定

在曲线上移动的轨迹表示价格逐渐变化的路径,
价格不是跳变,而是沿曲线连续变化的。


🧭 六、图像总结(文字版思维导图)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
恒定积公式: x * y = k

├─ 初始点 P₀: (1000, 2000)
│ └─ 初始价格 = 2

├─ 用户输入 100 A
│ ├─ 扣手续费 -> 实际输入 99.7 A
│ ├─ 新A储备 = 1099.7
│ ├─ 计算新B储备 = k / x' = 1818.36
│ └─ 输出B = 2000 - 1818.36 = 181.64

├─ 新点 P₁: (1099.7, 1818.36)
│ └─ 新价格 = 1.653

└─ 价格曲线滑动:
曲线右移 -> 滑点上升 -> 实际价格下降

🧩 七、关键理解总结

概念 解释
k 恒定曲线 确保交易自动计算价格,不需要订单簿
滑点 交易越大,占池子越多,价格偏离越明显
手续费 每笔 0.3% 计入流动性池,增强 LP 收益
价格动态性 交易沿曲线移动,而非线性固定价格
k 的微增 因手续费注入,流动性略微提升

好——下面我把之前的恒积曲线 (y=\dfrac{k}{x}) 用两种形式画出来:

  • 一份 ASCII 文字图(便于在终端或纯文本环境查看),
  • 一份 SVG 矢量图源码(可直接复制到 .svg 查看器 / 浏览器中,或嵌入 HTML,用于精确展示)。

我会在图上明确标出:初始点 (P_0(1000,2000))、交易后点 (P_1(1099.7,;1818.6778))、滑点范围、以及曲线方向箭头。
下面先给出关键数值(逐位计算,便于复核):

  • 初始储备:reserveA = 1000reserveB = 2000
  • 恒定积:(k = 1000 \times 2000 = 2{,}000{,}000)。
  • 用户输入:amountIn = 100(Token A)。
  • 手续费 0.3%,因此 amountInWithFee = 100 × 0.997 = 99.7
  • 交易后 A 储备:(x’ = 1000 + 99.7 = 1099.7)。
  • 交易后 B 储备:(y’ = k / x’ = 2{,}000{,}000 / 1099.7 \approx 1818.67782122397)。
  • 用户获得 B:amountOut = 2000 - y' ≈ 181.32217877603
  • 说明:交易使价格由 1A=2B 降到瞬时价格约 (1A \approx 1.653B)(池内瞬时比值),而用户成交的平均价格约 (1.816B)(见前述推导)。

一、ASCII 版本(文字示意图)

说明:ASCII 用字符网格近似表示曲线位置、P₀、P₁ 与滑点区域(非精确图形,但能直观看出移动方向与相对位置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
                 Token B 储备 (y)
2200 +------------------------------------------------+
| |
| *P0 (1000, 2000) |
2000 +-----------------*-------------------------------+
| /
| /
| /
| /
1850 +----------*P1 (1099.7,1818.68)------------------+ ← 交易后点,y' ≈ 1818.68
| / |
| / |
| / |
| / |
1700 +-------/----------------------------------------+
| / |
| / |
| / |
| / |
1500 +--/---------------------------------------------+
| / |
|/ |
+------------------------------------------------+--> Token A 储备 (x)
800 900 1000 1099.7 1200 1300 1400

说明:
- 曲线为 y = k / x(向右下方渐近)。初始点 P0 在 (1000,2000)。
- 用户以 A 增加 99.7(含手续费后),曲线从 P0 沿恒积曲线向右移动到 P1 (1099.7,1818.68)。
- 滑点体现在 P0 的切线价格(约 2.0)与 P1 的瞬时价格(约 1.653)之间的差距;用户实际成交均价介于二者(≈1.816)。
- 曲线方向由上左指向下右(箭头方向表明当 x 增大时 y 减小)。

二、SVG 版本(可复制为 .svg 文件并在浏览器打开)

下面是一个完整的 SVG 文本(宽 900px × 高 600px),它绘制了 (y=k/x) 的曲线(在 x 范围 600…1600),并标注了 P₀、P₁、滑点箭头与说明文字。你可直接把下列内容保存为 uniswap_curve.svg 然后在浏览器打开,或在 README/网页中嵌入 <svg>...</svg>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<?xml version="1.0" encoding="UTF-8"?>
<svg width="900" height="600" viewBox="0 0 900 600" xmlns="http://www.w3.org/2000/svg">

<!-- Background -->
<rect x="0" y="0" width="900" height="600" fill="#ffffff" />

<!-- Axes -->
<g id="axes" stroke="#444" stroke-width="1" fill="none">
<!-- X axis -->
<line x1="80" y1="520" x2="820" y2="520" />
<!-- Y axis -->
<line x1="80" y1="520" x2="80" y2="80" />
</g>

<!-- Axis labels -->
<text x="820" y="540" font-family="Arial" font-size="12" fill="#222">Token A 储备 (x)</text>
<text x="20" y="80" font-family="Arial" font-size="12" fill="#222" transform="rotate(-90 20,80)">Token B 储备 (y)</text>

<!-- Tick marks and labels on X -->
<g font-family="Arial" font-size="11" fill="#333">
<!-- We'll map x in [600,1600] to svg x in [100,760] -->
<!-- ticks at 700,800,900,1000,1100,1200,1300,1400,1500 -->
<!-- compute positions manually approximate -->
<line x1="100" y1="520" x2="100" y2="525" stroke="#666"/>
<text x="92" y="540">700</text>

<line x1="160" y1="520" x2="160" y2="525" stroke="#666"/>
<text x="152" y="540">800</text>

<line x1="220" y1="520" x2="220" y2="525" stroke="#666"/>
<text x="212" y="540">900</text>

<line x1="280" y1="520" x2="280" y2="525" stroke="#666"/>
<text x="272" y="540">1000</text>

<line x1="340" y1="520" x2="340" y2="525" stroke="#666"/>
<text x="332" y="540">1100</text>

<line x1="400" y1="520" x2="400" y2="525" stroke="#666"/>
<text x="392" y="540">1200</text>

<line x1="460" y1="520" x2="460" y2="525" stroke="#666"/>
<text x="452" y="540">1300</text>

<line x1="520" y1="520" x2="520" y2="525" stroke="#666"/>
<text x="512" y="540">1400</text>

<line x1="580" y1="520" x2="580" y2="525" stroke="#666"/>
<text x="572" y="540">1500</text>
</g>

<!-- Tick marks and labels on Y -->
<g font-family="Arial" font-size="11" fill="#333">
<!-- map y in [1400,2200] to svg y in [420,100] -->
<line x1="80" y1="420" x2="75" y2="420" stroke="#666"/>
<text x="40" y="424">1400</text>

<line x1="80" y1="360" x2="75" y2="360" stroke="#666"/>
<text x="40" y="364">1600</text>

<line x1="80" y1="300" x2="75" y2="300" stroke="#666"/>
<text x="40" y="304">1800</text>

<line x1="80" y1="240" x2="75" y2="240" stroke="#666"/>
<text x="40" y="244">2000</text>

<line x1="80" y1="180" x2="75" y2="180" stroke="#666"/>
<text x="40" y="184">2200</text>
</g>

<!-- Draw y = k / x curve (sampled points) -->
<!-- Map function: x in [600,1600] -> sx in [100,760]; y in [1400,2200] -> sy in [420,100] -->
<!-- We'll sample x values and compute y = 2,000,000 / x -->
<path d="
M 100 ( ${ /* placeholder - will be replaced by computed points below */ '' } )
" stroke="#ff6a00" stroke-width="2" fill="none" id="curve"/>

<!-- Instead of trying to construct path via string interpolation here,
we'll include a precomputed path below for the range x=600..1600 sampled. -->

<path d="
M 100,351.666
C 130,342 160,334 190,327
C 220,320 250,314 280,309
C 310,304 340,300 370,296
C 400,292 430,289 460,286
C 490,283 520,281 550,279
C 580,277 610,275 640,274
C 670,272 700,271 730,270
C 760,269
" stroke="#ff6a00" stroke-width="3" fill="none" opacity="0.95"/>

<!-- Note: 上面曲线路径是手工近似采样,以下再准确绘制并标注关键点 P0 和 P1 -->

<!-- Precise mapping helper lines & compute positions (we will place points by computed coords) -->
<!-- Mapping functions used to compute below:
sx(x) = 100 + (x - 600) * (660 / 1000) (maps 600..1600 to 100..760)
sy(y) = 420 - (y - 1400) * (320 / 800) (maps 1400..2200 to 420..100)
-->

<!-- Compute P0: x=1000, y=2000 -->
<!-- sx(1000) = 100 + (1000-600)*(660/1000) = 100 + 400*0.66 = 100 + 264 = 364 -->
<!-- sy(2000) = 420 - (2000-1400)*(320/800) = 420 - 600*(0.4) = 420 - 240 = 180 -->

<!-- Compute P1: x=1099.7, y≈1818.67782122397 -->
<!-- sx(1099.7) = 100 + (1099.7-600)*0.66 ≈ 100 + 499.7*0.66 ≈ 100 + 329.802 ≈ 429.802 -->
<!-- sy(1818.6778) = 420 - (1818.6778-1400)*0.4 ≈ 420 - 418.6778*0.4 ≈ 420 - 167.47112 ≈ 252.52888 -->

<!-- Mark P0 -->
<circle cx="364" cy="180" r="5" fill="#0055aa" />
<text x="374" y="176" font-family="Arial" font-size="12" fill="#0055aa">P₀ (1000, 2000)</text>

<!-- Mark P1 -->
<circle cx="429.8" cy="252.53" r="5" fill="#d9534f" />
<text x="440" y="248" font-family="Arial" font-size="12" fill="#d9534f">P₁ (1099.7, 1818.68)</text>

<!-- Draw an arrow along the curve from P0 to P1 to show movement -->
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="6" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#333" />
</marker>
</defs>

<!-- A short curved arrow between P0 and P1 -->
<path d="M 370 190 Q 400 220 426 250" stroke="#333" stroke-width="1.4" fill="none" marker-end="url(#arrow)" />

<!-- Draw small vertical bracket showing sliding from initial price to new price (visual of slippage) -->
<!-- At x ~ 364 (P0 x), show vertical from y=180 to y≈? corresponding P1 price line for that x (visual) -->
<line x1="364" y1="180" x2="364" y2="252.53" stroke="#999" stroke-dasharray="4 4"/>
<text x="290" y="220" font-family="Arial" font-size="12" fill="#666">滑点: 价格从 2.00 → 1.653 (瞬时)</text>

<!-- Annotate amountOut -->
<text x="520" y="300" font-family="Arial" font-size="12" fill="#222">
用户输入 100 A → 实得 ≈ 181.322 B (amountOut)
</text>

<!-- Legend -->
<rect x="600" y="60" width="270" height="110" fill="#f8f8f8" stroke="#ddd"/>
<text x="612" y="80" font-family="Arial" font-size="13" fill="#333">图例 / 说明</text>
<circle cx="620" cy="98" r="5" fill="#0055aa" />
<text x="634" y="102" font-family="Arial" font-size="12" fill="#333">P₀: 交易前 (1000,2000)</text>
<circle cx="620" cy="120" r="5" fill="#d9534f" />
<text x="634" y="124" font-family="Arial" font-size="12" fill="#333">P₁: 交易后 (1099.7,1818.68)</text>
<line x1="610" y1="140" x2="636" y2="140" stroke="#333" stroke-width="3" />
<text x="640" y="144" font-family="Arial" font-size="12" fill="#333">曲线: y = k / x (k = 2,000,000)</text>

<!-- Footer text -->
<text x="80" y="565" font-family="Arial" font-size="11" fill="#333">
注: 曲线为 y = k/x 的近似示意;坐标映射为 x∈[600,1600] → svg_x∈[100,760], y∈[1400,2200] → svg_y∈[420,100]。
</text>
</svg>

说明(关于 SVG 中的映射与精度):

  • 为了把数学坐标映射到 SVG 像素,我对 x,y 做了线性缩放(在注释处给出公式);
  • 曲线路径做了近似采样与手工平滑绘制以保证视觉美观;关键点 P₀、P₁ 的像素位置按精确数值计算并标注(你可按需用更高采样密度替换曲线路径以提高精确度)。

✅ 这里是完整的结果,包括两种可视化形式:


1️⃣ 数学真实曲线 (SVG/图形版)

上面的图展示了 Uniswap v2 的恒定乘积曲线 ( y = \frac{k}{x} ),其中:

  • P₀ (绿色点):交易前储备状态

    • ReserveA = 1000
    • ReserveB = 2000
    • ( P₀ = \frac{2000}{1000} = 2 )
  • P₁ (红色点):交易后储备状态

    • ReserveA = 1100
    • ReserveB ≈ 1818.18
    • ( P₁ = \frac{1818.18}{1100} ≈ 1.653 )
  • 蓝色箭头表示交易方向(用户加入 A → 储备A上升 → 储备B下降)

  • 曲线向右下弯曲说明:随着 A 储备增加,B 储备减少,价格曲线非线性,滑点出现。


2️⃣ ASCII 文字版示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  Token B Reserve (y)
|
2200 | *
| *
| *
| *
2000 | * ← P₀ (Before Trade)
| *
| *
|*
1800 |* ← P₁ (After Trade)
|
|_________________________________
Token A Reserve (x)

Trade Direction
(User adds A → Reserve A↑, Reserve B↓)

解释总结:

项目 说明
k = x × y 恒定乘积不变,曲线满足 y = k/x
滑点产生 因为曲线非线性,加入 100 A 不会线性换出 200 B,而是少于 200
价格变化 交易前 1A = 2B,交易后 1A ≈ 1.653B
k 恒定线 图中曲线即为 “k 恒定线”,任何交易都在曲线上移动,但不离开它
视觉方向 从左上到右下,表示 “买入 A 会导致 B 价格上涨(A 贬值)”