-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new script that calculates and shows fidelity bond stats for many possible locktimes.
- Loading branch information
1 parent
5bf64ed
commit 7a825d1
Showing
2 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
#!/usr/bin/env python3 | ||
import sys | ||
from bisect import bisect_left | ||
from datetime import datetime | ||
from decimal import Decimal | ||
from json import loads | ||
from optparse import OptionParser | ||
from statistics import quantiles, StatisticsError | ||
|
||
from dateutil.relativedelta import relativedelta | ||
from jmbase import EXIT_ARGERROR, jmprint, get_log, utxostr_to_utxo | ||
from jmbitcoin import amount_to_sat, sat_to_btc | ||
from jmclient import FidelityBondMixin, add_base_options, load_program_config, jm_single, get_interest_rate | ||
|
||
DESCRIPTION = """Given either a Bitcoin UTXO in the form TXID:n | ||
(e.g., 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098:0) | ||
or an amount in either satoshi or bitcoin (e.g., 150000, 0.1, 10.123, 10btc), | ||
calculate fidelity bond values for all possible locktimes in a one-year period | ||
(12 months, you can change that with the `-m --months` option). | ||
By default it uses the values from your joinmarket.cfg, | ||
you can override these with the `-i --interest` and `-e --exponent` options. | ||
Additionally, you can export the orderbook from ob-watcher.py and use the data here | ||
with the `-o --orderbook` option, this will compare the results from this script | ||
with the fidelity bonds in the orderbook. | ||
""" | ||
|
||
log = get_log() | ||
|
||
|
||
def add_one_month(dt: datetime) -> datetime: | ||
return dt + relativedelta(months=1) | ||
|
||
|
||
def main() -> None: | ||
parser = OptionParser( | ||
usage="usage: %prog [options] UTXO or amount", | ||
description=DESCRIPTION, | ||
) | ||
add_base_options(parser) | ||
parser.add_option( | ||
"-i", | ||
"--interest", | ||
action="store", | ||
type="float", | ||
dest="interest", | ||
help="Interest rate to use for fidelity bond calculation (instead of interest_rate config)", | ||
) | ||
parser.add_option( | ||
"-e", | ||
"--exponent", | ||
action="store", | ||
type="float", | ||
dest="exponent", | ||
help="Exponent to use for fidelity bond calculation (instead of bond_value_exponent config)", | ||
) | ||
parser.add_option( | ||
"-m", | ||
"--months", | ||
action="store", | ||
type="int", | ||
dest="months", | ||
help="For how many months to calculate the fidelity bond values, each month has its own stats (default 12)", | ||
default=12, | ||
) | ||
parser.add_option( | ||
"-o", | ||
"--orderbook", | ||
action="store", | ||
type="str", | ||
dest="path_to_json", | ||
help="Path to the exported orderbook in JSON format", | ||
) | ||
|
||
options, args = parser.parse_args() | ||
load_program_config(config_path=options.datadir) | ||
if len(args) != 1: | ||
log.error("Invalid arguments, see --help") | ||
sys.exit(EXIT_ARGERROR) | ||
if options.interest: | ||
jm_single().config.set("POLICY", "interest_rate", str(options.interest)) | ||
interest = get_interest_rate() | ||
if options.exponent: | ||
jm_single().config.set("POLICY", "bond_value_exponent", str(options.exponent)) | ||
current_time = jm_single().bc_interface.get_best_block_median_time() | ||
current_height = jm_single().bc_interface.get_current_block_height() | ||
if options.path_to_json: | ||
try: | ||
with open(options.path_to_json, "r", encoding="UTF-8") as orderbook: | ||
bond_values = [fb["bond_value"] for fb in loads(orderbook.read())["fidelitybonds"]] | ||
percentiles = quantiles(bond_values, n=100, method="inclusive") | ||
except FileNotFoundError as exc: | ||
log.error(exc) | ||
sys.exit(EXIT_ARGERROR) | ||
except StatisticsError: | ||
log.error("Given JSON file does not contain enough fidelity bonds (at least 2 required)") | ||
sys.exit(EXIT_ARGERROR) | ||
|
||
try: | ||
utxo_value = amount_to_sat(args[0]) | ||
except ValueError: | ||
# If it's not a valid amount then it has to be a UTXO | ||
success, utxo = utxostr_to_utxo(args[0]) | ||
if not success: | ||
# utxo contains the error message | ||
log.error(utxo) | ||
sys.exit(EXIT_ARGERROR) | ||
utxo_data = jm_single().bc_interface.query_utxo_set(utxo, includeconf=True)[0] | ||
utxo_value = utxo_data["value"] | ||
block_hash = jm_single().bc_interface.get_block_hash(current_height - utxo_data["confirms"] + 1) | ||
confirm_time = jm_single().bc_interface.get_block_time(block_hash) | ||
else: | ||
# We assume a fidelity bond with confirmation time equal to the current time (last block median time). | ||
# I.e., like if the fidelity bond UTXO with given amount has just confirmed on the blockchain. | ||
confirm_time = current_time | ||
|
||
jmprint(f"Amount locked: {utxo_value} ({sat_to_btc(utxo_value)} btc)") | ||
jmprint(f"Confirmation time: {datetime.fromtimestamp(confirm_time)}") | ||
jmprint(f"Interest rate: {interest} ({interest * 100}%)") | ||
jmprint(f"Exponent: {jm_single().config.get('POLICY', 'bond_value_exponent')}") | ||
jmprint("\nFIDELITY BOND VALUES") | ||
|
||
locktime = add_one_month( | ||
datetime.fromtimestamp(current_time).replace(day=1, hour=0, minute=0, second=0, microsecond=0)) | ||
for _ in range(options.months): | ||
fb_value = FidelityBondMixin.calculate_timelocked_fidelity_bond_value( | ||
utxo_value, | ||
confirm_time, | ||
datetime.timestamp(locktime), | ||
current_time, | ||
interest, | ||
) | ||
# Mimic the locktime value the user would have to insert to create such fidelity bond | ||
jmprint(f"\nLocktime: {locktime.year}-{locktime.month}") | ||
# Mimic orderbook value | ||
jmprint(f"Bond value: {float(Decimal(fb_value) / Decimal(1e16)):.16f}") | ||
if options.path_to_json: | ||
weight = round(fb_value / sum(bond_values), 5) | ||
jmprint(f"Weight: {weight} ({weight * 100}% of all bonds)") | ||
jmprint(f"Top {100 - bisect_left(percentiles, fb_value)}% of the orderbook by value") | ||
locktime = add_one_month(locktime) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |