mirror of
https://github.com/8go/bitcoin-stock-to-flow.git
synced 2021-05-12 18:52:14 +03:00
210 lines
6.9 KiB
Python
210 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Don't change tabbing, spacing, formating as file is automatically linted.
|
|
# Ignore long lines
|
|
# pylama:format=pep8:linters=pep8:ignore=E501
|
|
|
|
import argparse
|
|
import datetime
|
|
import logging
|
|
import os
|
|
import sys
|
|
import traceback
|
|
|
|
import requests
|
|
|
|
|
|
def infoFromMessari(proxies):
|
|
url = "https://data.messari.io/api/v1/assets/bitcoin/metrics"
|
|
cont = requests.get(url, proxies=proxies).json()
|
|
logger.debug(f"cont {cont}")
|
|
fwd_stock_to_flow = cont["data"]["supply"]["stock_to_flow"]
|
|
# conservative formula: 0.18 * s2f^3.3
|
|
# aggressive formula: exp(-1.84) * s2f^3.36
|
|
# conservative gives slightly lower price
|
|
# conservative formula: 0.18*s2f^3.3
|
|
fwd_stock_to_flow_usd = 0.18 * fwd_stock_to_flow ** 3.3
|
|
annual_inflation_percent = cont["data"]["supply"]["annual_inflation_percent"]
|
|
circulating = cont["data"]["supply"]["circulating"]
|
|
usd_price = cont["data"]["market_data"]["price_usd"]
|
|
logger.debug(
|
|
f"Data {fwd_stock_to_flow} {fwd_stock_to_flow_usd} "
|
|
f"{annual_inflation_percent} {circulating}"
|
|
)
|
|
# example output: Data 54.73928728956236 98097.8891323435
|
|
# 1.826841468925517 18410936.0981691
|
|
return (
|
|
float(usd_price),
|
|
float(circulating),
|
|
float(annual_inflation_percent),
|
|
float(fwd_stock_to_flow),
|
|
float(fwd_stock_to_flow_usd),
|
|
)
|
|
|
|
|
|
def btcSupplyOnDate(date, proxies):
|
|
"""Provides BTC supply on a given date"""
|
|
url = (
|
|
"https://community-api.coinmetrics.io/"
|
|
+ "v2/assets/btc/metricdata?metrics=SplyCur&start="
|
|
+ str(date)
|
|
+ "&end="
|
|
+ str(date)
|
|
)
|
|
cont = requests.get(url, proxies=proxies).json()
|
|
supply = cont["metricData"]["series"][0]["values"][0]
|
|
return float(supply)
|
|
|
|
|
|
def infoFromCoinMetrics(period, proxies):
|
|
dateYesterday = datetime.date.today() - datetime.timedelta(days=1)
|
|
datePeriodInit = dateYesterday - datetime.timedelta(days=period)
|
|
supplyYesterday = btcSupplyOnDate(dateYesterday, proxies)
|
|
supplyPeriodAgo = btcSupplyOnDate(datePeriodInit, proxies)
|
|
stock_to_flow_ratio = supplyPeriodAgo / (
|
|
(supplyYesterday - supplyPeriodAgo) / period * 365
|
|
)
|
|
# conservative formula: 0.18*s2f^3.3, see comments above
|
|
stock_to_flow_usd = 0.18 * stock_to_flow_ratio ** 3.3
|
|
return stock_to_flow_ratio, stock_to_flow_usd
|
|
|
|
|
|
def s2f(args):
|
|
"""Computes and prints the Stock-to-Flow ratio and price.
|
|
For ratio the 463-day Stock-to-Flow is used. Why 463?
|
|
See: https://twitter.com/digitaliknet/status/1270892084929626112?s=21
|
|
It was 365 days in a past formula, then in June 2020 adjusted to 463 as
|
|
463 is the value fitting the curve best
|
|
"""
|
|
if args.tor:
|
|
if os.name == "nt":
|
|
TOR_PORT = 9150 # Windows
|
|
else:
|
|
TOR_PORT = 9050 # LINUX
|
|
proxies = {
|
|
"http": f"socks5://127.0.0.1:{TOR_PORT}",
|
|
"https": f"socks5://127.0.0.1:{TOR_PORT}",
|
|
}
|
|
proxyindicator = " (via Tor)"
|
|
else:
|
|
proxies = {}
|
|
proxyindicator = ""
|
|
|
|
period = 463
|
|
stock_to_flow_ratio, stock_to_flow_usd = infoFromCoinMetrics(period, proxies)
|
|
(
|
|
usd_price,
|
|
circulating,
|
|
annual_inflation_percent,
|
|
fwd_stock_to_flow,
|
|
fwd_stock_to_flow_usd,
|
|
) = infoFromMessari(proxies)
|
|
|
|
if args.verbose:
|
|
print(
|
|
"Read about Stock-to-Flow here: "
|
|
"https://medium.com/@100trillionUSD/fficient-market-hypothesis-"
|
|
"and-bitcoin-stock-to-flow-model-db17f40e6107"
|
|
)
|
|
print(
|
|
"Compare with Stock-to-Flow data: "
|
|
"https://bitcoin.clarkmoody.com/dashboard/"
|
|
)
|
|
print("Compare with Stock-to-Flow graph: https://digitalik.net/btc/")
|
|
|
|
if not args.terse:
|
|
print(
|
|
"Data sources: "
|
|
f"messari.io and coinmetrics.io{proxyindicator}"
|
|
)
|
|
print(f"Calculated for date: {datetime.date.today()}")
|
|
print(f"Circulating BTC: {circulating:,.0f} BTC")
|
|
print(f"Annual inflation: {annual_inflation_percent:.2f} %")
|
|
if args.terse:
|
|
labelForwRatio = "F/"
|
|
labelForwPrice = "F$"
|
|
labelBckwRatio = "B/"
|
|
labelBckwPrice = "B$"
|
|
labelCurrPrice = " $"
|
|
labelDeviation = "D%"
|
|
else:
|
|
labelForwRatio = "Forward stock-to-flow ratio: "
|
|
labelForwPrice = "Forward stock-to-flow price: "
|
|
labelBckwRatio = str(period) + "-day Stock-to-flow ratio: "
|
|
labelBckwPrice = str(period) + "-day Stock-to-flow price: "
|
|
labelCurrPrice = "Current price: "
|
|
labelDeviation = "Deviation of " + str(period) + "-day S2F price: "
|
|
print(f"{labelForwRatio} {fwd_stock_to_flow:.2f}")
|
|
print(f"{labelForwPrice} {fwd_stock_to_flow_usd:,.0f} USD")
|
|
print(f"{labelBckwRatio} {stock_to_flow_ratio:.2f}")
|
|
print(f"{labelBckwPrice} {stock_to_flow_usd:,.0f} USD")
|
|
print(f"{labelCurrPrice} {usd_price:,.0f} USD")
|
|
print(
|
|
f"{labelDeviation} "
|
|
f"{(stock_to_flow_usd -usd_price) / stock_to_flow_usd * 100:,.2f} %"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "DEBUG" in os.environ:
|
|
logging.basicConfig() # initialize root logger, a must
|
|
logging.getLogger().setLevel(logging.DEBUG) # set root logger log level
|
|
else:
|
|
logging.getLogger().setLevel(logging.INFO) # set root logger log level
|
|
|
|
# Construct the argument parser
|
|
ap = argparse.ArgumentParser(
|
|
description="This program prints Bitcoin Stock-to-Flow ratio and price"
|
|
)
|
|
# Add the arguments to the parser
|
|
ap.add_argument(
|
|
"-d",
|
|
"--debug",
|
|
required=False,
|
|
action="store_true",
|
|
help="Print debug information",
|
|
)
|
|
ap.add_argument(
|
|
"-v",
|
|
"--verbose",
|
|
required=False,
|
|
action="store_true",
|
|
help="Print verbose output",
|
|
)
|
|
ap.add_argument(
|
|
"-t",
|
|
"--terse",
|
|
required=False,
|
|
action="store_true",
|
|
help="Print only terse condensed output",
|
|
)
|
|
ap.add_argument(
|
|
"-o", # onion
|
|
"--tor",
|
|
required=False,
|
|
action="store_true",
|
|
help="Use Tor, go through Tor Socks5 proxy",
|
|
)
|
|
args = ap.parse_args()
|
|
if args.debug:
|
|
logging.getLogger().setLevel(logging.DEBUG) # set root logger log level
|
|
logging.getLogger().info("Debug is turned on.")
|
|
logger = logging.getLogger("s2f")
|
|
|
|
try:
|
|
s2f(args)
|
|
except requests.exceptions.ConnectionError:
|
|
if args.tor:
|
|
print("ConnectionError. Maybe Tor is not running.", file=sys.stderr)
|
|
else:
|
|
print(
|
|
"ConnectionError. Maybe network connection is not down.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
except Exception:
|
|
traceback.print_exc(file=sys.stdout)
|
|
sys.exit(1)
|
|
except KeyboardInterrupt:
|
|
sys.exit(1)
|