From 871e17beafe994325b9337cb95f4489f2b113ba9 Mon Sep 17 00:00:00 2001 From: Allen Plummer Date: Thu, 21 Sep 2023 14:58:48 -0500 Subject: [PATCH 1/3] Initial Commit for LastPrice, Readme --- LastPrice.py | 444 +++++++++++++++++++++++++++++++++++++++++++++++++++ Readme.md | 47 ++++++ 2 files changed, 491 insertions(+) create mode 100644 LastPrice.py create mode 100644 Readme.md diff --git a/LastPrice.py b/LastPrice.py new file mode 100644 index 0000000..2638a11 --- /dev/null +++ b/LastPrice.py @@ -0,0 +1,444 @@ +############################################################################### + +# Introduction This was copied from UTXOracle.py, and stripped down +# - Credit to @SteveSimple on Twitter: https://twitter.com/SteveSimple/status/1704864674431332503 + +############################################################################### + + +# This python program estimates the daily USD price of bitcoin using only +# your bitcoin Core full node. It will work even while you are disconnected +# from the internet because it only reads blocks from your machine. It does not +# save files, write cookies, or access any wallet information. It only reads +# blocks, analyzes output patterns, and estimates a daily average a USD +# price of bitcoin. The call to your node is the standard "bitcoin-cli". The +# date and price ranges expected to work for this version are from 2020-7-26 +# and from $10,000 to $100,000 + +print("LastPrice version 1\n") + +############################################################################### + +# Quick Start + +############################################################################### + + +# 1. Make sure you have python3 and bitcoin-cli installed +# 2. Make sure "server = 1" is in bitcoin.conf +# 3. Run this file as "python3 LastPrice.py" + +# If this isn't working for you, you'll likely need to explore the +# bitcon-cli configuration options below: + + +# configuration options for bitcoin-cli +datadir = "" +rpcuser = "" +rpcpassword = "" +rpcookiefile = "" +rpcconnect = "" +rpcport = "" +conf = "" + +# add the configuration options to the bitcoin-cli call +bitcoin_cli_options = [] +if datadir != "": + bitcoin_cli_options.append('-datadir=' + datadir) +if rpcuser != "": + bitcoin_cli_options.append("-rpcuser=" + rpcuser) +if rpcpassword != "": + bitcoin_cli_options.append("-rpcpassword=" + rpcpassword) +if rpcookiefile != "": + bitcoin_cli_options.append("-rpcookiefile=" + rpcookiefile) +if rpcconnect != "": + bitcoin_cli_options.append("-rpcconnect=" + rpcconnect) +if rpcport != "": + bitcoin_cli_options.append("-rpcport=" + rpcport) +if conf != "": + bitcoin_cli_options.append("-conf=" + conf) + +############################################################################### + +# Part 1) Defining a shortcut function to call your node + +############################################################################### + +# Here we define define a shortcut (a function) for calling the node as we do +# this many times through out the program. The function will return the +# answer it gets from your node with the "command" that you asked it for. +# If we don't get an answer, the problem is likely that you don't have +# sever=1 in your bitcoin conf file. + + +import subprocess # a built in python library for sending command line commands + + +def Ask_Node(command): + # 'bitcoin-cli' is how the command window calls your node so + # it needs to be the first word in any request for data from the node + # other options are added if given + for o in bitcoin_cli_options: + command.insert(0, o) + command.insert(0, "bitcoin-cli") + + # get the answer from the node and return it to the program + answer = None + try: # python try is used when we need to deal with errors after + answer = subprocess.check_output(command) + except Exception as e: + # something went wrong while getting the answer + print("Error connecting to your node. Trouble shooting steps:\n") + print("\t 1) Make sure bitcoin-cli is working. Try command 'bitcoin-cli getblockcount'") + print("\t 2) Make sure config file bitcoin.conf has server=1") + print("\t 3) Explore the bitcoin-cli options in UTXOracle.py (line 38)") + print("\nThe command was:" + str(command)) + print("\nThe error from bitcoin-cli was:\n") + print(e) + exit() + + # answer received, return this answer to the program + return answer + + +############################################################################### + +# Part 2) Get the latest block from the node + +############################################################################### + +# The first request to the node is to ask it how many blocks it has. This +# let's us know the maximum possible day for which we can request a +# btc price estimate. The time information of blocks is listed in the block +# header, so we ask for the header only when we just need to know the time. + + +# get current block height from local node and exit if connection not made +block_count_b = Ask_Node(['getblockcount']) +block_count = int(block_count_b) # convert text to integer + +# get block header from current block height +block_hash_b = Ask_Node(['getblockhash', block_count_b]) +block_header_b = Ask_Node(['getblockheader', block_hash_b[:64], 'true']) +import json # a built in tool for deciphering lists of embedded lists + +block_header = json.loads(block_header_b) +print("Connected to local node at block #:\t" + str(block_count)) + +# ############################################################################### +# +# # Part 3) Ask for the desired date to estimate the price +# +# ############################################################################### +# NOTE: see original UTXOracle.py; I removed that +# ############################################################################## +# +# # Part 4) Hunt through blocks to find the first block on the target day +# +# ############################################################################## +# NOTE: see original UTXOracle.py; I removed that +# +############################################################################## + +# Part 5) Build the container to hold the output amounts bell curve + +############################################################################## + +# In pure math a bell curve can be perfectly smooth. But to make a bell curve +# from a sample of data, one must specifiy a series of buckets, or bins, and then +# count how many samples are in each bin. If the bin size is too large, say just one +# large bin, a bell curve can't appear because it will have only one bar. The bell +# curve also doesn't appear if the bin size is too small because then there will +# only be one sample in each bin and we'd fail to have a distribution of bin heights. + +# Although several bin sizes would work, I have found over many years, that 200 bins +# for every 10x of bitcoin amounts works very well. We use 'every 10x' because just +# like a long term bitcoin price chart, viewing output amounts in log scale provides +# a more comprehensive and detailed overview of the amounts being analyzed. + + +# Define the maximum and minimum values (in log10) of btc amounts to use +first_bin_value = -6 +last_bin_value = 6 # python -1 means last in list +range_bin_values = last_bin_value - first_bin_value + +# create a list of output_bell_curve_bins and add zero sats as the first bin +output_bell_curve_bins = [0.0] # a decimal tells python the list will contain decimals + +# calculate btc amounts of 200 samples in every 10x from 100 sats (1e-6 btc) to 100k (1e5) btc +for exponent in range(-6, 6): # python range uses 'less than' for the big number + + # add 200 bin_width increments in this 10x to the list + for b in range(0, 200): + bin_value = 10 ** (exponent + b / 200) + output_bell_curve_bins.append(bin_value) + +# Create a list the same size as the bell curve to keep the count of the bins +number_of_bins = len(output_bell_curve_bins) +output_bell_curve_bin_counts = [] +for n in range(0, number_of_bins): + output_bell_curve_bin_counts.append(float(0.0)) + +############################################################################## + +# Part 6) Get all output amounts from last block + +############################################################################## + +from math import log10 # built in math functions needed logarithms + +# get the full data of the first target day block from the node +print("Loading in the data #:\t" + str(block_count)) +block_height = block_count +block_hash_b = Ask_Node(['getblockhash', str(block_height)]) +block_b = Ask_Node(['getblock', block_hash_b[:64], '2']) +block = json.loads(block_b) + +# go through all the txs in the block which are stored in a list called 'tx' +for tx in block['tx']: + # txs have more than one output which are stored in a list called 'vout' + outputs = tx['vout'] + # go through all outputs in the tx + for output in outputs: + + # the bitcoin output amount is called 'value' in Core, add this to the list + amount = float(output['value']) + + # tiny and huge amounts aren't used by the USD price finder + if 1e-6 < amount < 1e6: + # take the log + amount_log = log10(amount) + # find the right output amount bin to increment + percent_in_range = (amount_log - first_bin_value) / range_bin_values + bin_number_est = int(percent_in_range * number_of_bins) + + # search for the exact right bin (won't be less than) + while output_bell_curve_bins[bin_number_est] <= amount: + bin_number_est += 1 + bin_number = bin_number_est - 1 + + # increment the output bin + output_bell_curve_bin_counts[bin_number] += 1.0 # += means increment + +############################################################################## + +# Part 7) Remove non-usd related output amounts from the bell curve + +############################################################################## + + +# This sections aims to remove non-usd denominated samples from the bell curve +# of outputs. The two primary steps are to remove very large/small outputs +# and then to remove round btc amounts. We don't set the round btc amounts +# to zero because if the USD price of bitcoin is also round, then round +# btc amounts will co-align with round usd amounts. There are many ways to deal +# with this. One way we've found to work is to smooth over the round btc amounts +# using the neighboring amounts in the bell curve. The last step is to normalize +# the bell curve. Normalizing is done by dividing the entire curve by the sum +# of the curve, and then removing extreme values. + +# remove ouputs below 1k sats +for n in range(0, 201): + output_bell_curve_bin_counts[n] = 0 + +# remove outputs above ten btc +for n in range(1601, number_of_bins): + output_bell_curve_bin_counts[n] = 0 + +# create a list of round btc bin numbers +round_btc_bins = [ + 201, # 1k sats + 401, # 10k + 461, # 20k + 496, # 30k + 540, # 50k + 601, # 100k + 661, # 200k + 696, # 300k + 740, # 500k + 801, # 0.01 btc + 861, # 0.02 + 896, # 0.03 + 940, # 0.04 + 1001, # 0.1 + 1061, # 0.2 + 1096, # 0.3 + 1140, # 0.5 + 1201 # 1 btc +] + +# smooth over the round btc amounts +for r in round_btc_bins: + amount_above = output_bell_curve_bin_counts[r + 1] + amount_below = output_bell_curve_bin_counts[r - 1] + output_bell_curve_bin_counts[r] = .5 * (amount_above + amount_below) + +# get the sum of the curve +curve_sum = 0.0 +for n in range(201, 1601): + curve_sum += output_bell_curve_bin_counts[n] + +# normalize the curve by dividing by it's sum and removing extreme values +for n in range(201, 1601): + output_bell_curve_bin_counts[n] /= curve_sum + + # remove extremes (the iterative process mentioned below found 0.008 to work) + if output_bell_curve_bin_counts[n] > 0.008: + output_bell_curve_bin_counts[n] = 0.008 + +############################################################################## + +# Part 8) Construct the USD price finder stencil + +############################################################################## + +# We now have a bell curve of outputs which should contain round USD outputs +# as it's prominent features. To expose these prominent features even more, +# and estimate a usd price, we slide a stencil over the bell curve and look +# for where the slide location is maximized. There are several stencil designs +# and maximization strategies which could accomplish this. The one used here +# is a stencil whose locations and heights have been found using the averages of +# running this algorithm iteratively over the years 2020-2023. The stencil is +# centered around 0.01 btc = $10,00 as this was the easiest mark to identify. +# The result of this process has produced the following stencil design: + + +# create an empty stencil the same size as the bell curve +round_usd_stencil = [] +for n in range(0, number_of_bins): + round_usd_stencil.append(0.0) + +# fill the round usd stencil with the values found by the process mentioned above +round_usd_stencil[401] = 0.0005957955691168063 # $1 +round_usd_stencil[402] = 0.0004454790662303128 # (next one for tx/atm fees) +round_usd_stencil[429] = 0.0001763099393598914 # $1.50 +round_usd_stencil[430] = 0.0001851801497144573 +round_usd_stencil[461] = 0.0006205616481885794 # $2 +round_usd_stencil[462] = 0.0005985696860584984 +round_usd_stencil[496] = 0.0006919505728046619 # $3 +round_usd_stencil[497] = 0.0008912933078342840 +round_usd_stencil[540] = 0.0009372916238804205 # $5 +round_usd_stencil[541] = 0.0017125522985034724 # (larger needed range for fees) +round_usd_stencil[600] = 0.0021702347223143030 +round_usd_stencil[601] = 0.0037018622326411380 # $10 +round_usd_stencil[602] = 0.0027322168706743802 +round_usd_stencil[603] = 0.0016268322583097678 # (larger needed range for fees) +round_usd_stencil[604] = 0.0012601953416497664 +round_usd_stencil[661] = 0.0041425242880295460 # $20 +round_usd_stencil[662] = 0.0039247767475640830 +round_usd_stencil[696] = 0.0032399441632017228 # $30 +round_usd_stencil[697] = 0.0037112959007355585 +round_usd_stencil[740] = 0.0049921908828370000 # $50 +round_usd_stencil[741] = 0.0070636869018197105 +round_usd_stencil[801] = 0.0080000000000000000 # $100 +round_usd_stencil[802] = 0.0065431388282424440 # (larger needed range for fees) +round_usd_stencil[803] = 0.0044279509203361735 +round_usd_stencil[861] = 0.0046132440551747015 # $200 +round_usd_stencil[862] = 0.0043647851395531140 +round_usd_stencil[896] = 0.0031980892880846567 # $300 +round_usd_stencil[897] = 0.0034237641632481910 +round_usd_stencil[939] = 0.0025995335505435034 # $500 +round_usd_stencil[940] = 0.0032631930982226645 # (larger needed range for fees) +round_usd_stencil[941] = 0.0042753262790881080 +round_usd_stencil[1001] = 0.0037699501474772350 # $1,000 +round_usd_stencil[1002] = 0.0030872891064215764 # (larger needed range for fees) +round_usd_stencil[1003] = 0.0023237040836798163 +round_usd_stencil[1061] = 0.0023671764210889895 # $2,000 +round_usd_stencil[1062] = 0.0020106877104798474 +round_usd_stencil[1140] = 0.0009099214128654502 # $3,000 +round_usd_stencil[1141] = 0.0012008546799361498 +round_usd_stencil[1201] = 0.0007862586076341524 # $10,000 +round_usd_stencil[1202] = 0.0006900048077192579 + +############################################################################## + +# Part 9) Slide the stencil over the output bell curve to find the best fit + +############################################################################## + +# This is the final step. We slide the stencil over the bell curve and see +# where it fits the best. The best fit location and it's neighbor are used +# in a weighted average to estimate the best fit USD price + + +# set up scores for sliding the stencil +best_slide = 0 +best_slide_score = 0.0 +total_score = 0.0 +number_of_scores = 0 + +# upper and lower limits for sliding the stencil +min_slide = -200 +max_slide = 200 + +# slide the stencil and calculate slide score +for slide in range(min_slide, max_slide): + + # shift the bell curve by the slide + shifted_curve = output_bell_curve_bin_counts[201 + slide:1401 + slide] + + # score the shift by multiplying the curve by the stencil + slide_score = 0.0 + for n in range(0, len(shifted_curve)): + slide_score += shifted_curve[n] * round_usd_stencil[n + 201] + + # increment total and number of scores + total_score += slide_score + number_of_scores += 1 + + # see if this score is the best so far + if slide_score > best_slide_score: + best_slide_score = slide_score + best_slide = slide + +# estimate the usd price of the best slide +usd100_in_btc_best = output_bell_curve_bins[801 + best_slide] +btc_in_usd_best = 100 / (usd100_in_btc_best) + +# find best slide neighbor up +neighbor_up = output_bell_curve_bin_counts[201 + best_slide + 1:1401 + best_slide + 1] +neighbor_up_score = 0.0 +for n in range(0, len(neighbor_up)): + neighbor_up_score += neighbor_up[n] * round_usd_stencil[n + 201] + +# find best slide neighbor down +neighbor_down = output_bell_curve_bin_counts[201 + best_slide - 1:1401 + best_slide - 1] +neighbor_down_score = 0.0 +for n in range(0, len(neighbor_down)): + neighbor_down_score += neighbor_down[n] * round_usd_stencil[n + 201] + +# get best neighbor +best_neighbor = +1 +neighbor_score = neighbor_up_score +if neighbor_down_score > neighbor_up_score: + best_neighbor = -1 + neighbor_score = neighbor_down_score + +# get best neighbor usd price +usd100_in_btc_2nd = output_bell_curve_bins[801 + best_slide + best_neighbor] +btc_in_usd_2nd = 100 / (usd100_in_btc_2nd) + +# weight average the two usd price estimates +avg_score = total_score / number_of_scores +a1 = best_slide_score - avg_score +a2 = abs(neighbor_score - avg_score) # theoretically possible to be negative +w1 = a1 / (a1 + a2) +w2 = a2 / (a1 + a2) +price_estimate = int(w1 * btc_in_usd_best + w2 * btc_in_usd_2nd) + +# report the price estimate +print("\nThe btc price estimate is: $" + f'{price_estimate:,}') + + + + + + + + + + + + + + diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..447db0d --- /dev/null +++ b/Readme.md @@ -0,0 +1,47 @@ +# UTXOracle + +This python program estimates the daily USD price of bitcoin using only your bitcoin Core full node. It will work even while you are disconnected +from the internet because it only reads blocks from your machine. + +It does not save files, write cookies, or access any wallet information. It only reads blocks, analyzes output patterns, and estimates a daily average a USD price of bitcoin. + +The call to your node is the standard "bitcoin-cli". The date and price ranges expected to work for this version are from 2020-7-26 and from $10,000 to $100,000 + +### Run? +* Have a bitcoin node. You'll need to configure your RPC settings somewhere like `~/.bitcoin/bitcoin.conf` +* `python UTXOracle.py` or `python3 UTXOracle.py` + +# LastPrice.py + +## What is this? + +It leverages @SteveSimple's good work with [utxo.live](https://utxo.live/oracle/). Instead of looking at a full set of blocks for a given day, it just looks at the last block. + +## What's it do? + +>TLDR: It analyzes the last block in your node, and infers a USD price for bitcoin based on assumptions made that people transact generally in round-USD amounts. + +### Details +* Gets your last block on your node. +* Walks through transactions in the last block. +* Fancy bell curve thingees. +* Throws out some edge cases. +* Builds a template of sorts of USD/BTC things. +* Lays that template across the array. +* Infers a price. + +### Run? +* Have a bitcoin node. You'll need to configure your RPC settings somewhere like `~/.bitcoin/bitcoin.conf` +* `python3 LastPrice.py` + +### FAQ +* Is this accurate? + * I have no idea. Seems to be with 1/2% of coingecko, etc. +* How does this work? + * @SteveSimple is smarter than me; did some fancy math and bell curves and whatnot. Basically, finds the `val` of each transaction in a given block, and throws out even BTC numbers, assumes some round dollar amounts and clustering, and spits out a number. +* Is there any license? + * Probably should be an MIT or Apache2 or something. I'll update this if/when I can find the original. +* Why is this in _your_ github, and not @SteveSimple? + * I couldn't find the original repo, but the original python file is located [here](https://utxo.live/oracle/UTXOracle.py). I also included that file in this repo. NOTE: if/when I find the original repo, I'll collapse/delete this one, and fork the original, and do a proper Pull Request into the original. +* Why didn't you just modify @SteveSimple's original code? + * Frankly, I don't understand the math. I have a working knowledge of Python, and wanted to see if I could just pick up the _last_ block in my node, and infer a price. \ No newline at end of file From 850ae50c6ea308c8fb9543f01c1194d904b53ba2 Mon Sep 17 00:00:00 2001 From: Allen Plummer Date: Thu, 21 Sep 2023 16:25:07 -0500 Subject: [PATCH 2/3] Fixed up spellings, added lastonly functionality --- Readme.md | 41 ++----- UTXOracle.py | 305 +++++++++++++++++++++++++-------------------------- 2 files changed, 160 insertions(+), 186 deletions(-) diff --git a/Readme.md b/Readme.md index 447db0d..f7bb8eb 100644 --- a/Readme.md +++ b/Readme.md @@ -7,41 +7,24 @@ It does not save files, write cookies, or access any wallet information. It only The call to your node is the standard "bitcoin-cli". The date and price ranges expected to work for this version are from 2020-7-26 and from $10,000 to $100,000 -### Run? -* Have a bitcoin node. You'll need to configure your RPC settings somewhere like `~/.bitcoin/bitcoin.conf` -* `python UTXOracle.py` or `python3 UTXOracle.py` - -# LastPrice.py - -## What is this? - -It leverages @SteveSimple's good work with [utxo.live](https://utxo.live/oracle/). Instead of looking at a full set of blocks for a given day, it just looks at the last block. +>TLDR: It analyzes a day's worth of blocks in your node, and infers a USD price for bitcoin based on assumptions made that people transact generally in round-USD amounts. -## What's it do? +## Run? +* Have a bitcoin node. You'll need to configure your RPC settings somewhere like `~/.bitcoin/bitcoin.conf` +* `python UTXOracle.py` or `python3 UTXOracle.py`. Follow the instructions on the screen. +* Conversely, for a "last block", set an envvar, and run: `LASTRUN=true python3 UTXOracle.py` for example. +### LastPrice >TLDR: It analyzes the last block in your node, and infers a USD price for bitcoin based on assumptions made that people transact generally in round-USD amounts. +* For "last block price", set an envvar, and run: `LASTRUN=true python3 UTXOracle.py` for example. + +* It leverages @SteveSimple's good work with [utxo.live](https://utxo.live/oracle/). Instead of looking at a full set of blocks for a given day, it just looks at the last block. -### Details -* Gets your last block on your node. -* Walks through transactions in the last block. +## Details +* Gets your last block (or a day's worth of blocks) on your node. +* Walks through transactions in the last block/s. * Fancy bell curve thingees. * Throws out some edge cases. * Builds a template of sorts of USD/BTC things. * Lays that template across the array. * Infers a price. - -### Run? -* Have a bitcoin node. You'll need to configure your RPC settings somewhere like `~/.bitcoin/bitcoin.conf` -* `python3 LastPrice.py` - -### FAQ -* Is this accurate? - * I have no idea. Seems to be with 1/2% of coingecko, etc. -* How does this work? - * @SteveSimple is smarter than me; did some fancy math and bell curves and whatnot. Basically, finds the `val` of each transaction in a given block, and throws out even BTC numbers, assumes some round dollar amounts and clustering, and spits out a number. -* Is there any license? - * Probably should be an MIT or Apache2 or something. I'll update this if/when I can find the original. -* Why is this in _your_ github, and not @SteveSimple? - * I couldn't find the original repo, but the original python file is located [here](https://utxo.live/oracle/UTXOracle.py). I also included that file in this repo. NOTE: if/when I find the original repo, I'll collapse/delete this one, and fork the original, and do a proper Pull Request into the original. -* Why didn't you just modify @SteveSimple's original code? - * Frankly, I don't understand the math. I have a working knowledge of Python, and wanted to see if I could just pick up the _last_ block in my node, and infer a price. \ No newline at end of file diff --git a/UTXOracle.py b/UTXOracle.py index 15a026c..364d740 100644 --- a/UTXOracle.py +++ b/UTXOracle.py @@ -14,7 +14,7 @@ # price of bitcoin. The call to your node is the standard "bitcoin-cli". The # date and price ranges expected to work for this version are from 2020-7-26 # and from $10,000 to $100,000 - +import os print("UTXOracle version 6\n") @@ -41,6 +41,7 @@ rpcconnect = "" rpcport = "" conf = "" +lastonly = (os.getenv('LASTONLY', 'False').upper() == 'TRUE') #add the configuration options to the bitcoin-cli call @@ -77,7 +78,7 @@ # this many times through out the program. The function will return the # answer it gets from your node with the "command" that you asked it for. # If we don't get an answer, the problem is likely that you don't have -# sever=1 in your bitcoin conf file. +# server=1 in your bitcoin conf file. @@ -160,10 +161,11 @@ def Ask_Node(command): latest_price_date = latest_price_day.strftime("%Y-%m-%d") -# tell the user that a connection has been made and state the lastest price date -print("Connected to local noode at block #:\t"+str(block_count)) -print("Latest available price date is: \t"+latest_price_date) -print("Earliest available price date is:\t2020-07-26 (full node)") +print("Connected to local node at block #:\t"+str(block_count)) +if not lastonly: + # tell the user that a connection has been made and state the latest price date + print("Latest available price date is: \t"+latest_price_date) + print("Earliest available price date is:\t2020-07-26 (full node)") @@ -181,44 +183,44 @@ def Ask_Node(command): ############################################################################### -# Part 3) Ask for the desired date to estimate the price +# Part 3) Ask for the desired date to estimate the price (for full day) ############################################################################### +if not lastonly: + #use python input to get date from the user + date_entered = input("\nEnter date in YYYY-MM-DD (or 'q' to quit):") -#use python input to get date from the user -date_entered = input("\nEnter date in YYYY-MM-DD (or 'q' to quit):") - -# quit if desired -if date_entered == 'q': - exit() - -#check to see if this is a good date -try: - year = int(date_entered.split('-')[0]) - month = int(date_entered.split('-')[1]) - day = int(date_entered.split('-')[2]) - - #make sure this date is less than the max date - datetime_entered = datetime(year,month,day,0,0,0,tzinfo=timezone.utc) - if datetime_entered.timestamp() >= latest_utc_midnight.timestamp(): - print("\nThe date entered is not before the current date, please try again") - exit() - - #make sure this date is after the min date - july_26_2020 = datetime(2020,7,26,0,0,0,tzinfo=timezone.utc) - if datetime_entered.timestamp() < july_26_2020.timestamp(): - print("\nThe date entered is before 2020-07-26, please try again") + # quit if desired + if date_entered == 'q': exit() -except: - print("\nError interpreting date. Likely not entered in format YYYY-MM-DD") - print("Please try again\n") - exit() + #check to see if this is a good date + try: + year = int(date_entered.split('-')[0]) + month = int(date_entered.split('-')[1]) + day = int(date_entered.split('-')[2]) + + #make sure this date is less than the max date + datetime_entered = datetime(year,month,day,0,0,0,tzinfo=timezone.utc) + if datetime_entered.timestamp() >= latest_utc_midnight.timestamp(): + print("\nThe date entered is not before the current date, please try again") + exit() + + #make sure this date is after the min date + july_26_2020 = datetime(2020,7,26,0,0,0,tzinfo=timezone.utc) + if datetime_entered.timestamp() < july_26_2020.timestamp(): + print("\nThe date entered is before 2020-07-26, please try again") + exit() + + except: + print("\nError interpreting date. Likely not entered in format YYYY-MM-DD") + print("Please try again\n") + exit() -#get the seconds and printable date string of date entered -price_day_seconds = int(datetime_entered.timestamp()) -price_day_date_utc = datetime_entered.strftime("%B %d, %Y") + #get the seconds and printable date string of date entered + price_day_seconds = int(datetime_entered.timestamp()) + price_day_date_utc = datetime_entered.strftime("%B %d, %Y") @@ -238,7 +240,7 @@ def Ask_Node(command): ############################################################################## -# Part 4) Hunt through blocks to find the first block on the target day +# Part 4) Hunt through blocks to find the first block on the target day ############################################################################## @@ -247,73 +249,73 @@ def Ask_Node(command): # specific time. Instead one must ask for a block, look at it's time, then estimate # the number of blocks to jump for the next guess. Rinse and repeat. +if not lastonly: + #first estimate of the block height of the price day + seconds_since_price_day = latest_time_in_seconds - price_day_seconds + blocks_ago_estimate = round(144*float(seconds_since_price_day)/float(seconds_in_a_day)) + price_day_block_estimate = block_count - blocks_ago_estimate -#first estimate of the block height of the price day -seconds_since_price_day = latest_time_in_seconds - price_day_seconds -blocks_ago_estimate = round(144*float(seconds_since_price_day)/float(seconds_in_a_day)) -price_day_block_estimate = block_count - blocks_ago_estimate - -#check the time of the price day block estimate -block_hash_b = Ask_Node(['getblockhash',str(price_day_block_estimate)]) -block_header_b = Ask_Node(['getblockheader',block_hash_b[:64],'true']) -block_header = json.loads(block_header_b) -time_in_seconds = block_header['time'] - -#get new block estimate from the seconds difference using 144 blocks per day -seconds_difference = time_in_seconds - price_day_seconds -block_jump_estimate = round(144*float(seconds_difference)/float(seconds_in_a_day)) - -#iterate above process until it oscillates around the correct block -last_estimate = 0 -last_last_estimate = 0 -while block_jump_estimate >6 and block_jump_estimate != last_last_estimate: - - #when we osciallate around the correct block, last_last_estimate = block_jump_estimate - last_last_estimate = last_estimate - last_estimate = block_jump_estimate - - #get block header or new estimate - price_day_block_estimate = price_day_block_estimate-block_jump_estimate + #check the time of the price day block estimate block_hash_b = Ask_Node(['getblockhash',str(price_day_block_estimate)]) block_header_b = Ask_Node(['getblockheader',block_hash_b[:64],'true']) block_header = json.loads(block_header_b) - - #check time of new block and get new block jump estimate time_in_seconds = block_header['time'] + + #get new block estimate from the seconds difference using 144 blocks per day seconds_difference = time_in_seconds - price_day_seconds block_jump_estimate = round(144*float(seconds_difference)/float(seconds_in_a_day)) - -#the oscillation may be over multiple blocks so we add/subtract single blocks -#to ensure we have exactly the first block of the target day -if time_in_seconds > price_day_seconds: - - # if the estimate was after price day look at earlier blocks - while time_in_seconds > price_day_seconds: - - #decrement the block by one, read new block header, check time - price_day_block_estimate = price_day_block_estimate-1 - block_hash_b = Ask_Node(['getblockhash',str(price_day_block_estimate)]) - block_header_b = Ask_Node(['getblockheader',block_hash_b[:64],'true']) - block_header = json.loads(block_header_b) - time_in_seconds = block_header['time'] - - #the guess is now perfectly the first block before midnight - price_day_block_estimate = price_day_block_estimate + 1 -# if the estimate was before price day look for later blocks -elif time_in_seconds < price_day_seconds: - - while time_in_seconds < price_day_seconds: - - #increment the block by one, read new block header, check time - price_day_block_estimate = price_day_block_estimate+1 + #iterate above process until it oscillates around the correct block + last_estimate = 0 + last_last_estimate = 0 + while block_jump_estimate >6 and block_jump_estimate != last_last_estimate: + + #when we osciallate around the correct block, last_last_estimate = block_jump_estimate + last_last_estimate = last_estimate + last_estimate = block_jump_estimate + + #get block header or new estimate + price_day_block_estimate = price_day_block_estimate-block_jump_estimate block_hash_b = Ask_Node(['getblockhash',str(price_day_block_estimate)]) block_header_b = Ask_Node(['getblockheader',block_hash_b[:64],'true']) block_header = json.loads(block_header_b) + + #check time of new block and get new block jump estimate time_in_seconds = block_header['time'] + seconds_difference = time_in_seconds - price_day_seconds + block_jump_estimate = round(144*float(seconds_difference)/float(seconds_in_a_day)) + + #the oscillation may be over multiple blocks so we add/subtract single blocks + #to ensure we have exactly the first block of the target day + if time_in_seconds > price_day_seconds: + + # if the estimate was after price day look at earlier blocks + while time_in_seconds > price_day_seconds: + + #decrement the block by one, read new block header, check time + price_day_block_estimate = price_day_block_estimate-1 + block_hash_b = Ask_Node(['getblockhash',str(price_day_block_estimate)]) + block_header_b = Ask_Node(['getblockheader',block_hash_b[:64],'true']) + block_header = json.loads(block_header_b) + time_in_seconds = block_header['time'] + + #the guess is now perfectly the first block before midnight + price_day_block_estimate = price_day_block_estimate + 1 + + # if the estimate was before price day look for later blocks + elif time_in_seconds < price_day_seconds: + + while time_in_seconds < price_day_seconds: -#assign the estimate as the price day block since it is correct now -price_day_block = price_day_block_estimate + #increment the block by one, read new block header, check time + price_day_block_estimate = price_day_block_estimate+1 + block_hash_b = Ask_Node(['getblockhash',str(price_day_block_estimate)]) + block_header_b = Ask_Node(['getblockheader',block_hash_b[:64],'true']) + block_header = json.loads(block_header_b) + time_in_seconds = block_header['time'] + + #assign the estimate as the price day block since it is correct now + price_day_block = price_day_block_estimate @@ -395,24 +397,26 @@ def Ask_Node(command): # from those blocks and places each tx output value into the bell curve +from math import log10 # built in math functions needed logarithms +if not lastonly: + # print header line of update table + print("\nReading all blocks on " + price_day_date_utc + "...") + print("\nThis will take a few minutes (~144 blocks)...") + print("\nHeight\tTime(utc)\t\tTime(32bit)\t\t Completion %") -from math import log10 #built in math functions needed logarithms - -#print header line of update table -print("\nReading all blocks on "+price_day_date_utc+"...") -print("\nThis will take a few minutes (~144 blocks)...") -print("\nHeight\tTime(utc)\t\tTime(32bit)\t\t Completion %") + # get the full data of the first target day block from the node + block_height = price_day_block +else: + block_height = block_count -#get the full data of the first target day block from the node -block_height=price_day_block -block_hash_b = Ask_Node(['getblockhash',str(block_height)]) -block_b = Ask_Node(['getblock',block_hash_b[:64],'2']) +block_hash_b = Ask_Node(['getblockhash', str(block_height)]) +block_b = Ask_Node(['getblock', block_hash_b[:64], '2']) block = json.loads(block_b) -#get the time of the first block +# get the time of the first block time_in_seconds = int(block['time']) -time_datetime = datetime.fromtimestamp(time_in_seconds,tz=timezone.utc) +time_datetime = datetime.fromtimestamp(time_in_seconds, tz=timezone.utc) time_utc = time_datetime.strftime("%H:%M:%S") hour_of_day = int(time_datetime.strftime("%H")) minute_of_hour = float(time_datetime.strftime("%M")) @@ -420,77 +424,61 @@ def Ask_Node(command): target_day_of_month = day_of_month time_32bit = f"{time_in_seconds & 0b11111111111111111111111111111111:32b}" - -#read in blocks until we get a block on the day after the target day +# read in blocks until we get a block on the day after the target day while target_day_of_month == day_of_month: - - #get progress estimate - progress_estimate = 100.0*(hour_of_day+minute_of_hour/60)/24.0 - - #print progress update - print(str(block_height)+"\t"+time_utc+"\t"+time_32bit+"\t"+f"{progress_estimate:.2f}"+"%") - - #go through all the txs in the block which are stored in a list called 'tx' + + # get progress estimate + progress_estimate = 100.0 * (hour_of_day + minute_of_hour / 60) / 24.0 + if not lastonly: + # print progress update + print(str(block_height) + "\t" + time_utc + "\t" + time_32bit + "\t" + f"{progress_estimate:.2f}" + "%") + + # go through all the txs in the block which are stored in a list called 'tx' for tx in block['tx']: - - #txs have more than one output which are stored in a list called 'vout' + + # txs have more than one output which are stored in a list called 'vout' outputs = tx['vout'] - - #go through all outputs in the tx + + # go through all outputs in the tx for output in outputs: - - #the bitcoin output amount is called 'value' in Core, add this to the list + + # the bitcoin output amount is called 'value' in Core, add this to the list amount = float(output['value']) - - #tiny and huge amounts aren't used by the USD price finder + + # tiny and huge amounts aren't used by the USD price finder if 1e-6 < amount < 1e6: - - #take the log + + # take the log amount_log = log10(amount) - - #find the right output amount bin to increment - percent_in_range = (amount_log-first_bin_value)/range_bin_values + + # find the right output amount bin to increment + percent_in_range = (amount_log - first_bin_value) / range_bin_values bin_number_est = int(percent_in_range * number_of_bins) - - #search for the exact right bin (won't be less than) + + # search for the exact right bin (won't be less than) while output_bell_curve_bins[bin_number_est] <= amount: bin_number_est += 1 bin_number = bin_number_est - 1 - - #increment the output bin - output_bell_curve_bin_counts[bin_number] += 1.0 #+= means increment - - - #get the full data of the next block + + # increment the output bin + output_bell_curve_bin_counts[bin_number] += 1.0 # += means increment + if lastonly: + break + # get the full data of the next block block_height = block_height + 1 - block_hash_b = Ask_Node(['getblockhash',str(block_height)]) - block_b = Ask_Node(['getblock',block_hash_b[:64],'2']) + block_hash_b = Ask_Node(['getblockhash', str(block_height)]) + block_b = Ask_Node(['getblock', block_hash_b[:64], '2']) block = json.loads(block_b) - #get the time of the next block + # get the time of the next block time_in_seconds = int(block['time']) - time_datetime = datetime.fromtimestamp(time_in_seconds,tz=timezone.utc) + time_datetime = datetime.fromtimestamp(time_in_seconds, tz=timezone.utc) time_utc = time_datetime.strftime("%H:%M:%S") day_of_month = int(time_datetime.strftime("%d")) minute_of_hour = float(time_datetime.strftime("%M")) hour_of_day = int(time_datetime.strftime("%H")) time_32bit = f"{time_in_seconds & 0b11111111111111111111111111111111:32b}" - - - - - - - - - - - - - - - ############################################################################## # Part 7) Remove non-usd related output amounts from the bell curve @@ -499,7 +487,7 @@ def Ask_Node(command): -# This sectoins aims to remove non-usd denominated samples from the bell curve +# This section is intended to remove non-usd denominated samples from the bell curve # of outputs. The two primary steps are to remove very large/small outputs # and then to remove round btc amounts. We don't set the round btc amounts # to zero because if the USD price of bitcoin is also round, then round @@ -725,8 +713,11 @@ def Ask_Node(command): w2 = a2/(a1+a2) price_estimate = int(w1*btc_in_usd_best + w2*btc_in_usd_2nd) -#report the price estimate -print("\nThe "+price_day_date_utc+" btc price estimate is: $" + f'{price_estimate:,}') +if not lastonly: + #report the price estimate + print("\nThe "+price_day_date_utc+" btc price estimate is: $" + f'{price_estimate:,}') +else: + print("\nThe btc price estimate is: $" + f'{price_estimate:,}') From 677850f64e1f8e3e7f8af953b4b9e50f6e949414 Mon Sep 17 00:00:00 2001 From: Allen Plummer Date: Thu, 21 Sep 2023 16:25:46 -0500 Subject: [PATCH 3/3] Removed redundant file --- LastPrice.py | 444 --------------------------------------------------- 1 file changed, 444 deletions(-) delete mode 100644 LastPrice.py diff --git a/LastPrice.py b/LastPrice.py deleted file mode 100644 index 2638a11..0000000 --- a/LastPrice.py +++ /dev/null @@ -1,444 +0,0 @@ -############################################################################### - -# Introduction This was copied from UTXOracle.py, and stripped down -# - Credit to @SteveSimple on Twitter: https://twitter.com/SteveSimple/status/1704864674431332503 - -############################################################################### - - -# This python program estimates the daily USD price of bitcoin using only -# your bitcoin Core full node. It will work even while you are disconnected -# from the internet because it only reads blocks from your machine. It does not -# save files, write cookies, or access any wallet information. It only reads -# blocks, analyzes output patterns, and estimates a daily average a USD -# price of bitcoin. The call to your node is the standard "bitcoin-cli". The -# date and price ranges expected to work for this version are from 2020-7-26 -# and from $10,000 to $100,000 - -print("LastPrice version 1\n") - -############################################################################### - -# Quick Start - -############################################################################### - - -# 1. Make sure you have python3 and bitcoin-cli installed -# 2. Make sure "server = 1" is in bitcoin.conf -# 3. Run this file as "python3 LastPrice.py" - -# If this isn't working for you, you'll likely need to explore the -# bitcon-cli configuration options below: - - -# configuration options for bitcoin-cli -datadir = "" -rpcuser = "" -rpcpassword = "" -rpcookiefile = "" -rpcconnect = "" -rpcport = "" -conf = "" - -# add the configuration options to the bitcoin-cli call -bitcoin_cli_options = [] -if datadir != "": - bitcoin_cli_options.append('-datadir=' + datadir) -if rpcuser != "": - bitcoin_cli_options.append("-rpcuser=" + rpcuser) -if rpcpassword != "": - bitcoin_cli_options.append("-rpcpassword=" + rpcpassword) -if rpcookiefile != "": - bitcoin_cli_options.append("-rpcookiefile=" + rpcookiefile) -if rpcconnect != "": - bitcoin_cli_options.append("-rpcconnect=" + rpcconnect) -if rpcport != "": - bitcoin_cli_options.append("-rpcport=" + rpcport) -if conf != "": - bitcoin_cli_options.append("-conf=" + conf) - -############################################################################### - -# Part 1) Defining a shortcut function to call your node - -############################################################################### - -# Here we define define a shortcut (a function) for calling the node as we do -# this many times through out the program. The function will return the -# answer it gets from your node with the "command" that you asked it for. -# If we don't get an answer, the problem is likely that you don't have -# sever=1 in your bitcoin conf file. - - -import subprocess # a built in python library for sending command line commands - - -def Ask_Node(command): - # 'bitcoin-cli' is how the command window calls your node so - # it needs to be the first word in any request for data from the node - # other options are added if given - for o in bitcoin_cli_options: - command.insert(0, o) - command.insert(0, "bitcoin-cli") - - # get the answer from the node and return it to the program - answer = None - try: # python try is used when we need to deal with errors after - answer = subprocess.check_output(command) - except Exception as e: - # something went wrong while getting the answer - print("Error connecting to your node. Trouble shooting steps:\n") - print("\t 1) Make sure bitcoin-cli is working. Try command 'bitcoin-cli getblockcount'") - print("\t 2) Make sure config file bitcoin.conf has server=1") - print("\t 3) Explore the bitcoin-cli options in UTXOracle.py (line 38)") - print("\nThe command was:" + str(command)) - print("\nThe error from bitcoin-cli was:\n") - print(e) - exit() - - # answer received, return this answer to the program - return answer - - -############################################################################### - -# Part 2) Get the latest block from the node - -############################################################################### - -# The first request to the node is to ask it how many blocks it has. This -# let's us know the maximum possible day for which we can request a -# btc price estimate. The time information of blocks is listed in the block -# header, so we ask for the header only when we just need to know the time. - - -# get current block height from local node and exit if connection not made -block_count_b = Ask_Node(['getblockcount']) -block_count = int(block_count_b) # convert text to integer - -# get block header from current block height -block_hash_b = Ask_Node(['getblockhash', block_count_b]) -block_header_b = Ask_Node(['getblockheader', block_hash_b[:64], 'true']) -import json # a built in tool for deciphering lists of embedded lists - -block_header = json.loads(block_header_b) -print("Connected to local node at block #:\t" + str(block_count)) - -# ############################################################################### -# -# # Part 3) Ask for the desired date to estimate the price -# -# ############################################################################### -# NOTE: see original UTXOracle.py; I removed that -# ############################################################################## -# -# # Part 4) Hunt through blocks to find the first block on the target day -# -# ############################################################################## -# NOTE: see original UTXOracle.py; I removed that -# -############################################################################## - -# Part 5) Build the container to hold the output amounts bell curve - -############################################################################## - -# In pure math a bell curve can be perfectly smooth. But to make a bell curve -# from a sample of data, one must specifiy a series of buckets, or bins, and then -# count how many samples are in each bin. If the bin size is too large, say just one -# large bin, a bell curve can't appear because it will have only one bar. The bell -# curve also doesn't appear if the bin size is too small because then there will -# only be one sample in each bin and we'd fail to have a distribution of bin heights. - -# Although several bin sizes would work, I have found over many years, that 200 bins -# for every 10x of bitcoin amounts works very well. We use 'every 10x' because just -# like a long term bitcoin price chart, viewing output amounts in log scale provides -# a more comprehensive and detailed overview of the amounts being analyzed. - - -# Define the maximum and minimum values (in log10) of btc amounts to use -first_bin_value = -6 -last_bin_value = 6 # python -1 means last in list -range_bin_values = last_bin_value - first_bin_value - -# create a list of output_bell_curve_bins and add zero sats as the first bin -output_bell_curve_bins = [0.0] # a decimal tells python the list will contain decimals - -# calculate btc amounts of 200 samples in every 10x from 100 sats (1e-6 btc) to 100k (1e5) btc -for exponent in range(-6, 6): # python range uses 'less than' for the big number - - # add 200 bin_width increments in this 10x to the list - for b in range(0, 200): - bin_value = 10 ** (exponent + b / 200) - output_bell_curve_bins.append(bin_value) - -# Create a list the same size as the bell curve to keep the count of the bins -number_of_bins = len(output_bell_curve_bins) -output_bell_curve_bin_counts = [] -for n in range(0, number_of_bins): - output_bell_curve_bin_counts.append(float(0.0)) - -############################################################################## - -# Part 6) Get all output amounts from last block - -############################################################################## - -from math import log10 # built in math functions needed logarithms - -# get the full data of the first target day block from the node -print("Loading in the data #:\t" + str(block_count)) -block_height = block_count -block_hash_b = Ask_Node(['getblockhash', str(block_height)]) -block_b = Ask_Node(['getblock', block_hash_b[:64], '2']) -block = json.loads(block_b) - -# go through all the txs in the block which are stored in a list called 'tx' -for tx in block['tx']: - # txs have more than one output which are stored in a list called 'vout' - outputs = tx['vout'] - # go through all outputs in the tx - for output in outputs: - - # the bitcoin output amount is called 'value' in Core, add this to the list - amount = float(output['value']) - - # tiny and huge amounts aren't used by the USD price finder - if 1e-6 < amount < 1e6: - # take the log - amount_log = log10(amount) - # find the right output amount bin to increment - percent_in_range = (amount_log - first_bin_value) / range_bin_values - bin_number_est = int(percent_in_range * number_of_bins) - - # search for the exact right bin (won't be less than) - while output_bell_curve_bins[bin_number_est] <= amount: - bin_number_est += 1 - bin_number = bin_number_est - 1 - - # increment the output bin - output_bell_curve_bin_counts[bin_number] += 1.0 # += means increment - -############################################################################## - -# Part 7) Remove non-usd related output amounts from the bell curve - -############################################################################## - - -# This sections aims to remove non-usd denominated samples from the bell curve -# of outputs. The two primary steps are to remove very large/small outputs -# and then to remove round btc amounts. We don't set the round btc amounts -# to zero because if the USD price of bitcoin is also round, then round -# btc amounts will co-align with round usd amounts. There are many ways to deal -# with this. One way we've found to work is to smooth over the round btc amounts -# using the neighboring amounts in the bell curve. The last step is to normalize -# the bell curve. Normalizing is done by dividing the entire curve by the sum -# of the curve, and then removing extreme values. - -# remove ouputs below 1k sats -for n in range(0, 201): - output_bell_curve_bin_counts[n] = 0 - -# remove outputs above ten btc -for n in range(1601, number_of_bins): - output_bell_curve_bin_counts[n] = 0 - -# create a list of round btc bin numbers -round_btc_bins = [ - 201, # 1k sats - 401, # 10k - 461, # 20k - 496, # 30k - 540, # 50k - 601, # 100k - 661, # 200k - 696, # 300k - 740, # 500k - 801, # 0.01 btc - 861, # 0.02 - 896, # 0.03 - 940, # 0.04 - 1001, # 0.1 - 1061, # 0.2 - 1096, # 0.3 - 1140, # 0.5 - 1201 # 1 btc -] - -# smooth over the round btc amounts -for r in round_btc_bins: - amount_above = output_bell_curve_bin_counts[r + 1] - amount_below = output_bell_curve_bin_counts[r - 1] - output_bell_curve_bin_counts[r] = .5 * (amount_above + amount_below) - -# get the sum of the curve -curve_sum = 0.0 -for n in range(201, 1601): - curve_sum += output_bell_curve_bin_counts[n] - -# normalize the curve by dividing by it's sum and removing extreme values -for n in range(201, 1601): - output_bell_curve_bin_counts[n] /= curve_sum - - # remove extremes (the iterative process mentioned below found 0.008 to work) - if output_bell_curve_bin_counts[n] > 0.008: - output_bell_curve_bin_counts[n] = 0.008 - -############################################################################## - -# Part 8) Construct the USD price finder stencil - -############################################################################## - -# We now have a bell curve of outputs which should contain round USD outputs -# as it's prominent features. To expose these prominent features even more, -# and estimate a usd price, we slide a stencil over the bell curve and look -# for where the slide location is maximized. There are several stencil designs -# and maximization strategies which could accomplish this. The one used here -# is a stencil whose locations and heights have been found using the averages of -# running this algorithm iteratively over the years 2020-2023. The stencil is -# centered around 0.01 btc = $10,00 as this was the easiest mark to identify. -# The result of this process has produced the following stencil design: - - -# create an empty stencil the same size as the bell curve -round_usd_stencil = [] -for n in range(0, number_of_bins): - round_usd_stencil.append(0.0) - -# fill the round usd stencil with the values found by the process mentioned above -round_usd_stencil[401] = 0.0005957955691168063 # $1 -round_usd_stencil[402] = 0.0004454790662303128 # (next one for tx/atm fees) -round_usd_stencil[429] = 0.0001763099393598914 # $1.50 -round_usd_stencil[430] = 0.0001851801497144573 -round_usd_stencil[461] = 0.0006205616481885794 # $2 -round_usd_stencil[462] = 0.0005985696860584984 -round_usd_stencil[496] = 0.0006919505728046619 # $3 -round_usd_stencil[497] = 0.0008912933078342840 -round_usd_stencil[540] = 0.0009372916238804205 # $5 -round_usd_stencil[541] = 0.0017125522985034724 # (larger needed range for fees) -round_usd_stencil[600] = 0.0021702347223143030 -round_usd_stencil[601] = 0.0037018622326411380 # $10 -round_usd_stencil[602] = 0.0027322168706743802 -round_usd_stencil[603] = 0.0016268322583097678 # (larger needed range for fees) -round_usd_stencil[604] = 0.0012601953416497664 -round_usd_stencil[661] = 0.0041425242880295460 # $20 -round_usd_stencil[662] = 0.0039247767475640830 -round_usd_stencil[696] = 0.0032399441632017228 # $30 -round_usd_stencil[697] = 0.0037112959007355585 -round_usd_stencil[740] = 0.0049921908828370000 # $50 -round_usd_stencil[741] = 0.0070636869018197105 -round_usd_stencil[801] = 0.0080000000000000000 # $100 -round_usd_stencil[802] = 0.0065431388282424440 # (larger needed range for fees) -round_usd_stencil[803] = 0.0044279509203361735 -round_usd_stencil[861] = 0.0046132440551747015 # $200 -round_usd_stencil[862] = 0.0043647851395531140 -round_usd_stencil[896] = 0.0031980892880846567 # $300 -round_usd_stencil[897] = 0.0034237641632481910 -round_usd_stencil[939] = 0.0025995335505435034 # $500 -round_usd_stencil[940] = 0.0032631930982226645 # (larger needed range for fees) -round_usd_stencil[941] = 0.0042753262790881080 -round_usd_stencil[1001] = 0.0037699501474772350 # $1,000 -round_usd_stencil[1002] = 0.0030872891064215764 # (larger needed range for fees) -round_usd_stencil[1003] = 0.0023237040836798163 -round_usd_stencil[1061] = 0.0023671764210889895 # $2,000 -round_usd_stencil[1062] = 0.0020106877104798474 -round_usd_stencil[1140] = 0.0009099214128654502 # $3,000 -round_usd_stencil[1141] = 0.0012008546799361498 -round_usd_stencil[1201] = 0.0007862586076341524 # $10,000 -round_usd_stencil[1202] = 0.0006900048077192579 - -############################################################################## - -# Part 9) Slide the stencil over the output bell curve to find the best fit - -############################################################################## - -# This is the final step. We slide the stencil over the bell curve and see -# where it fits the best. The best fit location and it's neighbor are used -# in a weighted average to estimate the best fit USD price - - -# set up scores for sliding the stencil -best_slide = 0 -best_slide_score = 0.0 -total_score = 0.0 -number_of_scores = 0 - -# upper and lower limits for sliding the stencil -min_slide = -200 -max_slide = 200 - -# slide the stencil and calculate slide score -for slide in range(min_slide, max_slide): - - # shift the bell curve by the slide - shifted_curve = output_bell_curve_bin_counts[201 + slide:1401 + slide] - - # score the shift by multiplying the curve by the stencil - slide_score = 0.0 - for n in range(0, len(shifted_curve)): - slide_score += shifted_curve[n] * round_usd_stencil[n + 201] - - # increment total and number of scores - total_score += slide_score - number_of_scores += 1 - - # see if this score is the best so far - if slide_score > best_slide_score: - best_slide_score = slide_score - best_slide = slide - -# estimate the usd price of the best slide -usd100_in_btc_best = output_bell_curve_bins[801 + best_slide] -btc_in_usd_best = 100 / (usd100_in_btc_best) - -# find best slide neighbor up -neighbor_up = output_bell_curve_bin_counts[201 + best_slide + 1:1401 + best_slide + 1] -neighbor_up_score = 0.0 -for n in range(0, len(neighbor_up)): - neighbor_up_score += neighbor_up[n] * round_usd_stencil[n + 201] - -# find best slide neighbor down -neighbor_down = output_bell_curve_bin_counts[201 + best_slide - 1:1401 + best_slide - 1] -neighbor_down_score = 0.0 -for n in range(0, len(neighbor_down)): - neighbor_down_score += neighbor_down[n] * round_usd_stencil[n + 201] - -# get best neighbor -best_neighbor = +1 -neighbor_score = neighbor_up_score -if neighbor_down_score > neighbor_up_score: - best_neighbor = -1 - neighbor_score = neighbor_down_score - -# get best neighbor usd price -usd100_in_btc_2nd = output_bell_curve_bins[801 + best_slide + best_neighbor] -btc_in_usd_2nd = 100 / (usd100_in_btc_2nd) - -# weight average the two usd price estimates -avg_score = total_score / number_of_scores -a1 = best_slide_score - avg_score -a2 = abs(neighbor_score - avg_score) # theoretically possible to be negative -w1 = a1 / (a1 + a2) -w2 = a2 / (a1 + a2) -price_estimate = int(w1 * btc_in_usd_best + w2 * btc_in_usd_2nd) - -# report the price estimate -print("\nThe btc price estimate is: $" + f'{price_estimate:,}') - - - - - - - - - - - - - -