You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

105 lines
4.1 KiB

"""
Utilities to calculate fidelity bonds values and statistics.
"""
from bisect import bisect_left
from datetime import datetime
from typing import Optional, Dict, Any, Mapping, Tuple, List, Sequence
from jmclient import FidelityBondMixin, jm_single, get_interest_rate
def get_percentiles(data: Sequence[Any]) -> List[Any]:
"""
Custom implementation of statistics.quantiles from Python standard library.
Equivalent to quantiles(data, n=100, method="inclusive")
Used to preserve compatibility with old Python versions.
"""
n = len(data)
if n < 2:
raise ValueError("Not enough data, at least two data points required")
data = sorted(data)
m = n - 1
result = []
for i in range(1, 100):
j, delta = divmod(i * m, 100)
interpolated = (data[j] * (100 - delta) + data[j + 1] * delta) / 100
result.append(interpolated)
return result
def get_next_locktime(dt: datetime) -> datetime:
"""
Return the next valid fidelity bond locktime.
"""
year = dt.year + dt.month // 12
month = dt.month % 12 + 1
return datetime(year, month, 1)
def get_bond_values(amount: int,
months: int,
confirm_time: Optional[float] = None,
interest: Optional[float] = None,
exponent: Optional[float] = None,
orderbook: Optional[Mapping[str, Any]] = None) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
"""
Conveniently generate values [and statistics] for multiple possible fidelity bonds.
Args:
amount: Fidelity bond UTXO amount in satoshi
months: For how many months to calculate the results
confirm_time: Fidelity bond UTXO confirmation time as timestamp, if None, current time is used.
I.e., like if the fidelity bond UTXO with given amount has just confirmed on the blockchain.
interest: Interest rate, if None, value is taken from config
exponent: Exponent, if None, value is taken from config
orderbook: Orderbook data, if given, additional statistics are included in the results.
Returns:
A tuple with 2 elements.
First is a dictionary with all the parameters used to perform fidelity bond calculations.
Second is a list of dictionaries, one for each month, with the results.
"""
current_time = datetime.now().timestamp()
if confirm_time is None:
confirm_time = current_time
if interest is None:
interest = get_interest_rate()
if exponent is None:
exponent = jm_single().config.getfloat("POLICY", "bond_value_exponent")
use_config_exp = True
else:
old_exponent = jm_single().config.get("POLICY", "bond_value_exponent")
jm_single().config.set("POLICY", "bond_value_exponent", str(exponent))
use_config_exp = False
if orderbook:
bond_values = [fb["bond_value"] for fb in orderbook["fidelitybonds"]]
bonds_sum = sum(bond_values)
percentiles = get_percentiles(bond_values)
parameters = {
"amount": amount,
"confirm_time": confirm_time,
"current_time": current_time,
"interest": interest,
"exponent": exponent,
}
locktime = get_next_locktime(datetime.fromtimestamp(current_time))
results = []
for _ in range(months):
fb_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value(
amount,
confirm_time,
locktime.timestamp(),
current_time,
interest,
)
result = {"locktime": locktime.timestamp(),
"value": fb_value}
if orderbook:
result["weight"] = fb_value / (bonds_sum + fb_value)
result["percentile"] = 100 - bisect_left(percentiles, fb_value)
results.append(result)
locktime = get_next_locktime(locktime)
if not use_config_exp:
# We don't want the modified exponent value to persist in memory, so we reset to whatever it was before
jm_single().config.set("POLICY", "bond_value_exponent", old_exponent)
return parameters, results