Skip to content

Commit

Permalink
Dpaiton/calc bonds (#94)
Browse files Browse the repository at this point in the history
# Description

#75 removed a function
that was used by `agent0`. This PR brings the function back under a
different, more accurate name.

I also had to write a new test for it since the function does not exist
in solidity.
  • Loading branch information
dpaiton authored May 11, 2024
1 parent 873dc57 commit 3a44b63
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 15 deletions.
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ members = [

[workspace.package]
name="hyperdrive-rs"
version="0.15.1"
version="0.15.2"
authors = [
"Alex Towle <[email protected]>",
"Dylan Paiton <[email protected]>",
"Jonny Rhea <[email protected]>",
"matthew Brown <[email protected]>",
"Matthew Brown <[email protected]>",
"Mihai Cosma <[email protected]>",
"Ryan Goree <[email protected]>",
"Sheng Lundquist <[email protected]>",
Expand Down
2 changes: 1 addition & 1 deletion bindings/hyperdrivepy/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "hyperdrivepy"
version = "0.15.1"
version = "0.15.2"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
Expand Down
70 changes: 70 additions & 0 deletions bindings/hyperdrivepy/python/hyperdrivepy/hyperdrive_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,73 @@ def calculate_effective_share_reserves(
The adjusted share reserves, accounting for the zeta factor.
"""
return rust_module.calculate_effective_share_reserves(share_reserves, share_adjustment)


def calculate_bonds_given_effective_shares_and_rate(
effective_share_reserves: str,
target_rate: str,
initial_vault_share_price: str,
position_duration: str,
time_stretch: str,
) -> str:
"""Calculates the bond reserves assuming that the pool has a given
effective share reserves and fixed rate APR.
..note::
This function should not be used for computing reserve levels when
initializing a pool. Instead use `lp::calculate_initial_reserves`.
..math::
r = ((1/p)-1)/t = (1-p)/(pt)
p = ((u * z) / y) ** t
y = mu * (z - zeta) * (1 + apr * t) ** (1/tau)
Arguments
---------
effective_share_reserves: str (FixedPoint)
The pool's effective share reserves. The effective share
reserves are a modified version of the share reserves
used when pricing trades.
target_rate: str (FixedPoint)
The target pool's fixed APR.
initial_vault_share_price: str (FixedPoint)
The pool's initial share price.
position_duration: str (FixedPoint)
The amount of time until maturity in seconds.
time_stretch: str (FixedPoint)
The time stretch parameter (tau).
Returns
-------
bond_reserves: str (FixedPoint)
The bond reserves that make the pool have a specified APR.
"""
return rust_module.calculate_bonds_given_effective_shares_and_rate(
effective_share_reserves, target_rate, initial_vault_share_price, position_duration, time_stretch
)


def calculate_rate_given_fixed_price(
price: str,
position_duration: str,
) -> str:
"""Calculate the rate assuming a given price is constant for some annualized duration.
We calculate the rate for a fixed length of time as:
..math::
r = (1 - p) / (p t)
where $p$ is the price and $t$ is the length of time that this price is
assumed to be constant, in units of years. For example, if the price is
constant for 6 months, then $t=0.5$.
In our case, $t = \text{position_duration} / (60*60*24*365)$.
Arguments
---------
price: str (FixedPoint)
The spot price of bonds fora given pool.
position_duration: str (FixedPoint)
The pool bond position duration, over which the price is assumed to be constant.
"""
return rust_module.calculate_rate_given_fixed_price(price, position_duration)
2 changes: 1 addition & 1 deletion bindings/hyperdrivepy/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="hyperdrivepy",
version="0.15.1",
version="0.15.2",
packages=["hyperdrivepy"],
package_dir={"": "python"},
rust_extensions=[
Expand Down
58 changes: 58 additions & 0 deletions bindings/hyperdrivepy/src/hyperdrive_utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,54 @@
use ethers::core::types::{I256, U256};
use fixed_point::FixedPoint;
use hyperdrive_math::{
calculate_bonds_given_effective_shares_and_rate as rs_calculate_bonds_given_effective_shares_and_rate,
calculate_effective_share_reserves as rs_calculate_effective_share_reserves,
calculate_rate_given_fixed_price as rs_calculate_rate_given_fixed_price,
calculate_time_stretch as rs_calculate_time_stretch,
};
use pyo3::{exceptions::PyValueError, prelude::*};

#[pyfunction]
pub fn calculate_bonds_given_effective_shares_and_rate(
effective_share_reserves: &str,
target_rate: &str,
initial_vault_share_price: &str,
position_duration: &str,
time_stretch: &str,
) -> PyResult<String> {
let effective_share_reserves_fp =
FixedPoint::from(U256::from_dec_str(effective_share_reserves).map_err(|_| {
PyErr::new::<PyValueError, _>(
"Failed to convert effective_share_reserves string to U256",
)
})?);
let target_rate_fp = FixedPoint::from(U256::from_dec_str(target_rate).map_err(|_| {
PyErr::new::<PyValueError, _>("Failed to convert target_rate string to U256")
})?);
let initial_vault_share_price_fp =
FixedPoint::from(U256::from_dec_str(initial_vault_share_price).map_err(|_| {
PyErr::new::<PyValueError, _>(
"Failed to convert initial_vault_share_price string to U256",
)
})?);
let position_duration_fp =
FixedPoint::from(U256::from_dec_str(position_duration).map_err(|_| {
PyErr::new::<PyValueError, _>("Failed to convert position_duration string to U256")
})?);
let time_stretch_fp = FixedPoint::from(U256::from_dec_str(time_stretch).map_err(|_| {
PyErr::new::<PyValueError, _>("Failed to convert time_stretch string to U256")
})?);
let result_fp = rs_calculate_bonds_given_effective_shares_and_rate(
effective_share_reserves_fp,
target_rate_fp,
initial_vault_share_price_fp,
position_duration_fp,
time_stretch_fp,
);
let result = U256::from(result_fp).to_string();
Ok(result)
}

#[pyfunction]
pub fn calculate_effective_share_reserves(
share_reserves: &str,
Expand All @@ -22,6 +65,21 @@ pub fn calculate_effective_share_reserves(
Ok(result)
}

#[pyfunction]
pub fn calculate_rate_given_fixed_price(price: &str, position_duration: &str) -> PyResult<String> {
let price_fp =
FixedPoint::from(U256::from_dec_str(price).map_err(|_| {
PyErr::new::<PyValueError, _>("Failed to convert price string to U256")
})?);
let position_duration_fp =
FixedPoint::from(U256::from_dec_str(position_duration).map_err(|_| {
PyErr::new::<PyValueError, _>("Failed to convert position_duration string to U256")
})?);
let result_fp = rs_calculate_rate_given_fixed_price(price_fp, position_duration_fp);
let result = U256::from(result_fp).to_string();
Ok(result)
}

#[pyfunction]
pub fn calculate_time_stretch(rate: &str, position_duration: &str) -> PyResult<String> {
let rate_fp = FixedPoint::from(
Expand Down
10 changes: 9 additions & 1 deletion bindings/hyperdrivepy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ mod utils;

use hyperdrive_state::HyperdriveState;
pub use hyperdrive_state_methods::*;
pub use hyperdrive_utils::{calculate_effective_share_reserves, calculate_time_stretch};
pub use hyperdrive_utils::{
calculate_bonds_given_effective_shares_and_rate, calculate_effective_share_reserves,
calculate_rate_given_fixed_price, calculate_time_stretch,
};
pub use pool_config::PyPoolConfig;
pub use pool_info::PyPoolInfo;
use pyo3::prelude::*;
Expand All @@ -18,7 +21,12 @@ use pyo3::prelude::*;
#[pyo3(name = "hyperdrivepy")]
fn hyperdrivepy(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<HyperdriveState>()?;
m.add_function(wrap_pyfunction!(
calculate_bonds_given_effective_shares_and_rate,
m
)?)?;
m.add_function(wrap_pyfunction!(calculate_effective_share_reserves, m)?)?;
m.add_function(wrap_pyfunction!(calculate_rate_given_fixed_price, m)?)?;
m.add_function(wrap_pyfunction!(calculate_time_stretch, m)?)?;
Ok(())
}
28 changes: 28 additions & 0 deletions bindings/hyperdrivepy/tests/wrapper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,34 @@ def test_calculate_spot_price():
assert int(spot_price) > 0, "Expected spot price to > 0."


def test_calculate_bonds_given_effective_shares_and_rate():
"""test calculate_bonds_given_effective_shares_and_rate."""
effective_share_reserves = hyperdrivepy.calculate_effective_share_reserves(
str(POOL_INFO.shareReserves),
str(POOL_INFO.shareAdjustment),
)
rate = hyperdrivepy.calculate_spot_rate(POOL_CONFIG, POOL_INFO)
bonds = hyperdrivepy.calculate_bonds_given_effective_shares_and_rate(
effective_share_reserves,
rate,
str(POOL_CONFIG.initialVaultSharePrice),
str(POOL_CONFIG.positionDuration),
str(POOL_CONFIG.timeStretch),
)
assert bonds is not None, "Failed to calculate bonds."
assert isinstance(bonds, str), "Expected bonds to be a string."
assert int(bonds) > 0, "Expected bonds to be > 0."


def test_calculate_rate_given_fixed_price():
"""test calculate_rate_given_fixed_price."""
price = hyperdrivepy.calculate_spot_price(POOL_CONFIG, POOL_INFO)
rate = hyperdrivepy.calculate_rate_given_fixed_price(price, str(POOL_CONFIG.positionDuration))
assert rate is not None, "Failed to calculate spot rate."
assert isinstance(rate, str), "Expected spot rate to be a string."
assert float(rate) > 0, "Expected spot rate to > 0."


def test_calculate_time_stretch():
"""test calculate_time_stretch."""
time_stretch = hyperdrivepy.calculate_time_stretch(
Expand Down
Loading

0 comments on commit 3a44b63

Please sign in to comment.