Skip to content

Commit

Permalink
Mazygio/tests/utils (#66)
Browse files Browse the repository at this point in the history
* add test data for parse_config

* parse_config test cleanup

* add tests for calc_total_liquidity_from_reserves_and_price

* add tests for calc_base_asset_reserves

* cleanup comments in price.py

* rename test_price.py to test_price_utils.py. Add tests for calc_liquidity

* refactor apr_decimal variable to apr in test_price_utils.py

* linting and stuff

* add tests for calc_spot_price_from_apr

* add tests for calc_spot_price_from_apr

* finish adding tests for price utils

* rebase time.py and price.py comment cleanup onto main

* comment cleanup on test_price_utils and create test_time.py file

* add some time_utils tests

* finish adding tests for time utilities

* add some error tests for test_price_utils.py

* finish adding error tests for price utils

* fix parse_config tests after logging update

* updates minor linting errors & resolves remaining comments

* removes accidental merge code

* fixes up a bug from calling the wrong parse_config function; moves pricing_model to amm config

* removes accidental notebook

Co-authored-by: Dylan <dpaiton@gmail.com>
  • Loading branch information
MazyGio and dpaiton authored Dec 7, 2022
1 parent e3a7114 commit f4e3201
Show file tree
Hide file tree
Showing 11 changed files with 1,616 additions and 51 deletions.
2 changes: 1 addition & 1 deletion config/example_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ max_vault_apy = 0.9 # as a decimal
base_asset_price = 1 # aka market price

[amm]
pricing_model_name = "Hyperdrive" # specify a pricing model
# random variables
min_fee = 0.1 # decimal that assigns fee_percent
max_fee = 0.5 # decimal that assigns fee_percent
Expand All @@ -29,7 +30,6 @@ num_trading_days = 180 # should be <= pool_duration
num_blocks_per_day = 7200 # 24 * 60 * 60 / 12 = 12 second block time
token_duration = 0.24657534246575342465753424657534 # 90 / 365 = time lapse between token mint and expiry as a yearfrac
precision = 64 # 64 is max precision (and the default for numpy)
pricing_model_name = "Element" # specify a pricing model
user_policies = ["single_long"] # specify a list of trading strategies by name
shuffle_users = true # shuffle order of action (as if random gas paid)
init_lp = true # use initial LP to seed pool
Expand Down
4 changes: 1 addition & 3 deletions src/elfpy/simulators.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,7 @@ def setup_simulated_entities(self, override_dict=None):
), "ERROR: You must run simulator.set_random_variables() before constructing simulation entities"
if override_dict is not None:
self.override_variables(override_dict) # apply the override dict
pricing_model = self._get_pricing_model(
self.config.simulator.pricing_model_name
) # construct pricing model object
pricing_model = self._get_pricing_model(self.config.amm.pricing_model_name) # construct pricing model object
# setup market
time_stretch_constant = pricing_model.calc_time_stretch(self.config.simulator.init_pool_apy)
# calculate reserves needed to deposit to hit target liquidity and APY
Expand Down
11 changes: 4 additions & 7 deletions src/elfpy/utils/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""
Config structure
"""
# dataclasses can have many attributes
# pylint: disable=too-many-instance-attributes


from dataclasses import dataclass, field

Expand All @@ -9,9 +12,6 @@
class MarketConfig:
"""config parameters specific to the market"""

# pylint: disable=too-many-instance-attributes
# dataclasses can have many attributes

min_target_liquidity: float = field(default=1e6, metadata={"hint": "shares"})
max_target_liquidity: float = field(default=10e6, metadata={"hint": "shares"})
min_target_volume: float = field(default=0.001, metadata={"hint": "fraction of pool liquidity"})
Expand All @@ -27,6 +27,7 @@ class MarketConfig:
class AMMConfig:
"""config parameters specific to the amm"""

pricing_model_name: str = field(default="Element", metadata={"hint": 'Must be "Element" or "Hyperdrive"'})
min_fee: float = field(default=0.1, metadata={"hint": "decimal that assignes fee_percent"})
max_fee: float = field(default=0.5, metadata={"hint": "decimal that assignes fee_percent"})
min_pool_apy: float = field(default=0.02, metadata={"hint": "as a decimal"})
Expand All @@ -39,17 +40,13 @@ class AMMConfig:
class SimulatorConfig:
"""config parameters specific to the simulator"""

# pylint: disable=too-many-instance-attributes
# dataclasses can have many attributes

pool_duration: int = field(default=180, metadata={"hint": "in days"})
num_trading_days: int = field(default=180, metadata={"hint": "in days; should be <= pool_duration"})
num_blocks_per_day: int = field(default=7_200, metadata={"hint": "int"})
token_duration: float = field(
default=90 / 365, metadata={"hint": "time lapse between token mint and expiry as a yearfrac"}
)
precision: int = field(default=64, metadata={"hint": "precision of calculations; max is 64"})
pricing_model_name: str = field(default="Element", metadata={"hint": 'Must be "Element" or "Hyperdrive"'})
user_policies: list = field(default_factory=list, metadata={"hint": "List of strings naming user strategies"})
shuffle_users: bool = field(default=True, metadata={"hint": "shuffle order of action (as if random gas paid)"})
init_lp: bool = field(default=True, metadata={"hint": "use initial LP to seed pool"})
Expand Down
27 changes: 18 additions & 9 deletions src/elfpy/utils/parse_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""
Utilities for parsing & loading user config TOML files
"""

import logging
Expand All @@ -12,7 +11,6 @@
def load_and_parse_config_file(config_file):
"""
Wrapper function for loading a toml config file and parsing it.
Arguments
---------
config_file : str
Expand All @@ -29,7 +27,6 @@ def load_and_parse_config_file(config_file):
def load_config_file(config_file):
"""
Load a config file as a dictionary
Arguments
---------
config_file : str
Expand Down Expand Up @@ -63,14 +60,25 @@ def parse_simulation_config(config_dict):
amm=AMMConfig(**config_dict["amm"]),
simulator=SimulatorConfig(**config_dict["simulator"]),
)
return apply_config_logging(simulation_config)
simulation_config.simulator.logging_level = text_to_logging_level(simulation_config.simulator.logging_level)
return simulation_config


def apply_config_logging(raw_config: Config):
def text_to_logging_level(logging_text: str) -> int:
"""
Applies config logging from config settings
Converts logging level description to an integer
Arguments
---------
logging_text : str
String description of the logging level; must be in ["debug", "info", "warning", "error", "critical"]
Returns
-------
int
Logging level integer corresponding to the string input
"""
match raw_config.simulator.logging_level.lower():
match logging_text.lower():
case "debug":
level = logging.DEBUG
case "info":
Expand All @@ -81,5 +89,6 @@ def apply_config_logging(raw_config: Config):
level = logging.ERROR
case "critical":
level = logging.CRITICAL
raw_config.simulator.logging_level = level
return raw_config
case _:
raise ValueError(f'{logging_text=} must be in ["debug", "info", "warning", "error", "critical"]')
return level
117 changes: 112 additions & 5 deletions src/elfpy/utils/price.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

def calc_total_liquidity_from_reserves_and_price(base_asset_reserves, token_asset_reserves, spot_price):
"""
Returns the total liquidity in the pool in terms of base
We are using spot_price when calculating total_liquidity to convert the two tokens into the same units.
Otherwise we're comparing apples(base_asset_reserves in ETH) and oranges (token_asset_reserves in ptETH)
ptEth = 1.0 ETH at maturity ONLY
Expand All @@ -26,6 +28,20 @@ def calc_total_liquidity_from_reserves_and_price(base_asset_reserves, token_asse
which measures the opportunity cost of getting a dollar tomorrow instead of today.
discount rate = (1 + r)^n
spot price APR = 1 / (1 + r)^n
Arguments
---------
base_asset_reserves : float
Base reserves in the pool
token_asset_reserves : float
Bond (pt) reserves in the pool
spot_price : float
Price of bonds (pts) in terms of base
Returns
-------
float
Total liquidity in the pool in terms of base, calculated from the provided parameters
"""
return base_asset_reserves + token_asset_reserves * spot_price

Expand All @@ -38,7 +54,29 @@ def calc_base_asset_reserves(
init_share_price,
share_price,
):
"""Returns the assumed base_asset reserve amounts given the token_asset reserves and APR"""
"""
Returns the assumed base_asset reserve amounts given the token_asset reserves and APR
Arguments
---------
apr_decimal : float
Current fixed APR in decimal units (for example, 5% APR would be 0.05)
token_asset_reserves : float
Bond (pt) reserves in the pool
days_remaining : float
Amount of days left until bond maturity
time_stretch : float
Time stretch parameter, in years
init_share_price : float
Original share price when the pool started
share_price : float
Current share price
Returns
-------
float
The expected amount of base asset in the pool, calculated from the provided parameters
"""
normalized_days_remaining = time_utils.norm_days(days_remaining)
time_stretch_exp = 1 / time_utils.stretch_time(normalized_days_remaining, time_stretch)
numerator = 2 * share_price * token_asset_reserves # 2*c*y
Expand All @@ -51,7 +89,7 @@ def calc_base_asset_reserves(
def calc_liquidity(
target_liquidity_usd,
market_price,
apr,
apr, # decimal APR
days_remaining,
time_stretch,
init_share_price: float = 1,
Expand All @@ -65,6 +103,29 @@ def calc_liquidity(
total_liquidity = in USD terms, used to target liquidity as passed in (in USD terms)
total_reserves = in arbitrary units (AU), used for yieldspace math
Arguments
---------
target_liquidity_usd : float
Amount of liquidity, denominated in USD, that the simulation is trying to achieve in a given market
market_price : float
Price of the base asset, denominated in USD
apr : float
Fixed APR that the bonds should provide, in decimal form (for example, 5% APR is 0.05)
days_remaining : float
Amount of days left until bond maturity
time_stretch : float
Time stretch parameter, in years
init_share_price : float
Original share price when the pool started. Defaults to 1
share_price : float
Current share price. Defaults to 1
Returns
-------
(float, float, float)
Tuple that contains (base_asset_reserves, token_asset_reserves, total_liquidity)
calculated from the provided parameters
"""
# estimate reserve values with the information we have
spot_price = calc_spot_price_from_apr(apr, time_utils.norm_days(days_remaining))
Expand Down Expand Up @@ -95,7 +156,21 @@ def calc_liquidity(


def calc_apr_from_spot_price(price, normalized_days_remaining):
"""Returns the APR (decimal) given the current (positive) base asset price and the remaining pool duration"""
"""
Returns the APR (decimal) given the current (positive) base asset price and the remaining pool duration
Arguments
---------
price : float
Spot price of bonds in terms of base
normalized_days_remaining : float
Time remaining until bond maturity, in yearfracs
Returns
-------
float
APR (decimal) calculated from the provided parameters
"""
assert price > 0, (
"utils.price.calc_apr_from_spot_price: ERROR: "
f"Price argument should be greater or equal to zero, not {price}"
Expand All @@ -108,15 +183,47 @@ def calc_apr_from_spot_price(price, normalized_days_remaining):


def calc_spot_price_from_apr(apr_decimal, normalized_days_remaining):
"""Returns the current spot price based on the current APR (decimal) and the remaining pool duration"""
"""
Returns the current spot price based on the current APR (decimal) and the remaining pool duration
Arguments
---------
apr_decimal : float
Current fixed APR in decimal units (for example, 5% APR would be 0.05)
normalized_days_remaining : float
Time remaining until bond maturity, in yearfracs
Returns
-------
float
Spot price of bonds in terms of base, calculated from the provided parameters
"""
return 1 / (1 + apr_decimal * normalized_days_remaining) # price = 1 / (1 + r * t)


### YieldSpace ###


def calc_k_const(share_reserves, bond_reserves, share_price, init_share_price, time_elapsed):
"""Returns the 'k' constant variable for trade mathematics"""
"""
Returns the 'k' constant variable for trade mathematics
Arguments
---------
share_reserves : float
bond_reserves : float
share_price : float
Current share price
init_share_price : float
Original share price when the pool started
time_elapsed : float
Amount of time that has elapsed in the current market, in yearfracs
Returns
-------
float
'k' constant used for trade mathematics, calculated from the provided parameters
"""
scale = share_price / init_share_price
total_reserves = bond_reserves + share_price * share_reserves
return scale * (init_share_price * share_reserves) ** (time_elapsed) + (bond_reserves + total_reserves) ** (
Expand Down
Loading

0 comments on commit f4e3201

Please sign in to comment.