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

Cryptopool price formula #196

Merged
merged 11 commits into from
Aug 9, 2023
5 changes: 5 additions & 0 deletions changelog.d/20230809_125756_chanhosuh_crypto_price.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added
-----

- Added spot price methods `dydx` and `dydxfee` to the `CurveCryptoPool`.

85 changes: 84 additions & 1 deletion curvesim/pool/cryptoswap/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Mainly a module to house the `CryptoPool`, a cryptoswap implementation in Python.
"""
import time
from math import isqrt
from math import isqrt, prod
from typing import List

from curvesim.exceptions import CalculationError, CryptoPoolError
Expand Down Expand Up @@ -1029,6 +1029,89 @@ def calc_token_amount(self, amounts: List[int]) -> int:
d_token -= self._calc_token_fee(amountsp, xp) * d_token // 10**10 + 1
return d_token

def dydxfee(self, i, j):
"""
Returns the spot price of i-th coin quoted in terms of j-th coin,
i.e. the ratio of output coin amount to input coin amount for
an "infinitesimally" small trade.

Trading fees are deducted.

Parameters
----------
i: int
Index of coin to be priced; in a swapping context, this is
the "in"-token.
j: int
Index of quote currency; in a swapping context, this is the
"out"-token.

Returns
-------
float
Price of i-th coin quoted in j-th coin with fees deducted.

Note
----
This is a "view" function; it doesn't change the state of the pool.
"""
return self.dydx(i, j, use_fee=True)

def dydx(self, i, j, use_fee=False):
"""
Returns the spot price of i-th coin quoted in terms of j-th coin,
i.e. the ratio of output coin amount to input coin amount for
an "infinitesimally" small trade.

Defaults to no fees deducted.

Parameters
----------
i: int
Index of coin to be priced; in a swapping context, this is
the "in"-token.
j: int
Index of quote currency; in a swapping context, this is the
"out"-token.

Returns
-------
float
Price of i-th coin quoted in j-th coin

Note
----
This is a "view" function; it doesn't change the state of the pool.
"""
xp = self._xp()
x_i = xp[i]
x_j = xp[j]
n = len(xp)

D = self.D
A = self.A
A_multiplier = 10**4
gamma = self.gamma

K0 = 10**18 * n**n * prod(xp) / D**n

coeff = gamma**2 * A / (D * (10**18 + gamma - K0) ** 2) / A_multiplier
frac = (10**18 + gamma + K0) * (sum(xp) - D) / (10**18 + gamma - K0)
dydx = x_j * (1 + coeff * (x_i + frac)) / (x_i * (1 + coeff * (x_j + frac)))

if j > 0:
price_scale = self.price_scale[j - 1]
dydx = dydx * 10**18 / price_scale
if i > 0:
price_scale = self.price_scale[i - 1]
dydx = dydx * price_scale / 10**18

if use_fee:
fee = self._fee(xp)
dydx = dydx - dydx * fee / 10**10

return dydx


def _get_unix_timestamp():
"""Get the timestamp in Unix time."""
Expand Down
20 changes: 20 additions & 0 deletions test/unit/test_cryptopool.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,3 +755,23 @@ def test_multiple_exchange_with_repeg(
assert pool.price_scale == expected_price_scale

boa.env.time_travel(time_delta)


def test_dydxfee(vyper_cryptopool):
"""Test spot price formula against execution price for small trades."""
pool = initialize_pool(vyper_cryptopool)

# STG, USDC
decimals = [18, 6]
precisions = [10 ** (18 - d) for d in decimals]

i = 0
j = 1
dx = 10**18

dydx = pool.dydxfee(i, j)
dy = vyper_cryptopool.exchange(i, j, dx, 0)

dx *= precisions[i]
dy *= precisions[j]
assert abs(dydx - dy / dx) < 1e-6
31 changes: 31 additions & 0 deletions test/unit/test_tricrypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Tests are against the tricrypto-ng contract.
"""
import os
from itertools import permutations

import boa
from hypothesis import HealthCheck, assume, given, settings
Expand Down Expand Up @@ -419,3 +420,33 @@ def test__newton_y(vyper_tricrypto, A, gamma, x0, x1, x2, pair, dx_perc):
y = _newton_y(A, gamma, xp, D, j)

assert y == expected_y


def test_dydxfee(vyper_tricrypto):
"""Test spot price formula against execution price for small trades."""
pool = initialize_pool(vyper_tricrypto)

# USDT, WBTC, WETH
decimals = [6, 8, 18]
precisions = [10 ** (18 - d) for d in decimals]

# print("WBTC price:", pool.price_scale[0] / 10**18)
# print("WETH price:", pool.price_scale[1] / 10**18)

dxs = [
10**6,
10**4,
10**15,
]

for pair in permutations([0, 1, 2], 2):
i, j = pair

dydx = pool.dydxfee(i, j)
dx = dxs[i]
dy = vyper_tricrypto.exchange(i, j, dx, 0)
pool.exchange(i, j, dx, 0) # update state to match vyper pool

dx *= precisions[i]
dy *= precisions[j]
assert abs(dydx - dy / dx) / (dy / dx) < 1e-4
Loading