Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cryptoswap bonding_curve #272

Merged
merged 6 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Changed
-------

- Updated curvesim.bonding_curve to output bonding curve graphs for Cryptoswap pools.

113 changes: 87 additions & 26 deletions curvesim/tools/bonding_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,34 @@
plots the curves using Matplotlib.
"""
from itertools import combinations
from typing import Dict, List, Tuple, Union

import matplotlib.pyplot as plt
from numpy import linspace

from curvesim.pool import CurveMetaPool
from curvesim.pool import CurveCryptoPool, CurveMetaPool, CurvePool, CurveRaiPool

D_UNIT = 10**18
STABLESWAP = Union[CurvePool, CurveMetaPool, CurveRaiPool]
CRYPTOSWAP = Union[CurveCryptoPool]


# pylint: disable-next=too-many-locals
def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False):
# pylint: disable-next=too-many-locals, too-many-branches
def bonding_curve( # noqa: C901
pool: Union[STABLESWAP, CRYPTOSWAP], *, truncate=None, resolution=1000, plot=False
) -> Dict[Tuple[int, int], List[Tuple[float, float]]]:
"""
Computes and optionally plots a pool's bonding curve and current reserves.

Parameters
----------
pool : CurvePool or CurveMetaPool
pool : CurvePool, CurveMetaPool, CurveRaiPool, or CurveCryptoPool
The pool object for which the bonding curve is computed.

truncate : float, optional (default=0.0005)
truncate : Optional[float], optional (default=None)
Determines where to truncate the bonding curve. The truncation point is given
by D*truncate, where D is the total supply of tokens in the pool.
by D*truncate, where D is the total supply of tokens in the pool. Stableswap
pools apply 0.0005 by default, and Cryptoswap pools apply 1.0 by default.

resolution : int, optional (default=1000)
The number of points to compute along the bonding curve.
Expand All @@ -49,49 +55,104 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False):
"""

if isinstance(pool, CurveMetaPool):
combos = [(0, 1)]
combos: List[Tuple[int, int]] = [(0, 1)]
allt0ld marked this conversation as resolved.
Show resolved Hide resolved
else:
combos = combinations(range(pool.n), 2)

D = pool.D()
xp = pool._xp() # pylint: disable=protected-access
combos = list(combinations(range(pool.n), 2))

xp: List[int] = pool._xp() # pylint: disable=protected-access

if isinstance(pool, STABLESWAP): # type: ignore[misc, arg-type]
allt0ld marked this conversation as resolved.
Show resolved Hide resolved
D: int = pool.D() # type: ignore[assignment]
if truncate is None:
# pylint: disable=pointless-string-statement
"""
This default value works for Stableswap, but will break Cryptoswap.
At this value, the graph usually cuts off cleanly around the points where
the Stableswap pool would depeg, as one stablecoin balance has reached
almost 100% of pool assets.
"""
truncate = 0.0005
elif isinstance(pool, CRYPTOSWAP): # type: ignore[misc, arg-type]
D = pool.D # Don't recalcuate D - it will rebalance the bonding curve(s)
if truncate is None:
# pylint: disable=pointless-string-statement
allt0ld marked this conversation as resolved.
Show resolved Hide resolved
"""
A 1.0 value for Cryptoswap extends the graph to a point at which the pool
would incur massive losses if it were to rebalance. The further away from
(1 / pool.n) truncate is, the more imbalanced the pool is at the end of the
graph before rebalancing, if profit is high enough to rebalance.
"""
truncate = 1.0
allt0ld marked this conversation as resolved.
Show resolved Hide resolved
else:
raise TypeError(f"Bonding curve calculation not supported for {type(pool)}")

pair_to_curve = {}
pair_to_curve: Dict[Tuple[int, int], List[Tuple[float, float]]] = {}
current_points: Dict[Tuple[int, int], Tuple[float, float]] = {}
for (i, j) in combos:
truncated_D = int(D * truncate)
x_max = pool.get_y(j, i, truncated_D, xp)
xs = linspace(truncated_D, x_max, resolution).round()
truncated_D: int = int(D * truncate)
x_limit: int = pool.get_y(j, i, truncated_D, xp)
xs: List[int] = list(linspace(truncated_D, x_limit, resolution).round())

curve = []
curve: List[Tuple[float, float]] = []
for x in xs:
y = pool.get_y(i, j, int(x), xp)
curve.append((x, y))
curve = [(x / D_UNIT, y / D_UNIT) for x, y in curve]
x_float: float = x / D_UNIT
y_float: float = pool.get_y(i, j, int(x), xp) / D_UNIT

if isinstance(pool, CRYPTOSWAP): # type: ignore[misc, arg-type]
if i > 0:
# type: ignore[union-attr]
x_float = x_float * D_UNIT / pool.price_scale[i - 1]

if j > 0:
# type: ignore[union-attr]
y_float = y_float * D_UNIT / pool.price_scale[j - 1]

curve.append((x_float, y_float))

pair_to_curve[(i, j)] = curve

current_x: float = xp[i] / D_UNIT
current_y: float = xp[j] / D_UNIT

if isinstance(pool, CRYPTOSWAP): # type: ignore[misc, arg-type]
allt0ld marked this conversation as resolved.
Show resolved Hide resolved
if i > 0:
# type: ignore[union-attr]
current_x = current_x * D_UNIT / pool.price_scale[i - 1]

if j > 0:
# type: ignore[union-attr]
current_y = current_y * D_UNIT / pool.price_scale[j - 1]

current_points[(i, j)] = (current_x, current_y)

if plot:
labels = pool.coin_names
labels: List[str] = pool.coin_names
if not labels:
labels = [f"Coin {str(label)}" for label in range(pool.n)]

_plot_bonding_curve(pair_to_curve, labels, xp)
_plot_bonding_curve(pair_to_curve, current_points, labels)

return pair_to_curve


def _plot_bonding_curve(pair_to_curve, labels, xp):
n = len(pair_to_curve)
def _plot_bonding_curve(
pair_to_curve: Dict[Tuple[int, int], List[Tuple[float, float]]],
current_points: Dict[Tuple[int, int], Tuple[float, float]],
labels: List[str],
) -> None:
n: int = len(pair_to_curve)
_, axs = plt.subplots(1, n, constrained_layout=True)
if n == 1:
axs = [axs]

for pair, ax in zip(pair_to_curve, axs):
curve = pair_to_curve[pair]
curve: List[Tuple[float, float]] = pair_to_curve[pair]
xs, ys = zip(*curve)
ax.plot(xs, ys, color="black")
ax.plot(xs, ys, color="black") # the entire bonding curve

i, j = pair
ax.scatter(xp[i] / D_UNIT, xp[j] / D_UNIT, s=40, color="black")
x, y = current_points[(i, j)]
ax.scatter(x, y, s=40, color="black") # A single dot at the current point
ax.set_xlabel(labels[i])
ax.set_ylabel(labels[j])

Expand Down
66 changes: 66 additions & 0 deletions test/integration/test_bonding_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,69 @@ def test_bonding_curve_metapool():
}

assert pair_to_curve == expected_result


def test_bonding_curve_cryptoswap():
"""
Simple test of the bonding curve for a regular cryptoswap.

Parameters taken from 0xd51a44d3fae010294c616388b506acda1bfaae46
(Tricrypto-2 pool) on Oct. 10, 2023, ~4 PM EDT.
"""
A = 1707629
gamma = 11809167828997
n = 3
precisions = [1000000000000, 10000000000, 1]
mid_fee = 3000000
out_fee = 30000000
allowed_extra_profit = 2000000000000
fee_gamma = 500000000000000
adjustment_step = 490000000000000
ma_half_time = 600
price_scale = [27823549548207248490238, 1580164282540758832038]
balances = [21282026780687, 77735630688, 13482330187707402680192]
D = 64214523455757010937592598

pool = curvesim.pool.CurveCryptoPool(
A,
gamma,
n,
precisions,
mid_fee,
out_fee,
allowed_extra_profit,
fee_gamma,
adjustment_step,
ma_half_time,
price_scale,
balances=balances,
D=D,
)

pair_to_curve = bonding_curve(pool, resolution=5)

expected_result = {
(0, 1): [
(64214523.455757014, 257.0830793764587),
(49949133.541076906, 330.6293390019353),
(35683743.626396805, 463.0556701281358),
(21418353.711716697, 772.4361029340507),
(7152963.7970365975, 2307.919891547214),
],
(0, 2): [
(64214523.455757014, 4458.778590637991),
(49922293.25998866, 5737.424624762622),
(35630063.064220294, 8043.197113798094),
(21337832.868451938, 13447.046511020473),
(7045602.672683579, 40637.87807714901),
],
(1, 2): [
(2307.9198915472143, 4531.460155851949),
(1795.277966845345, 5827.600890376515),
(1282.6360421434754, 8161.168801564149),
(769.9941174416057, 13611.63199329776),
(257.3521927397361, 40637.87807714902),
],
}

assert pair_to_curve == expected_result
Loading