Skip to content

Instantly share code, notes, and snippets.

@brianddk
Last active January 16, 2021 20:57
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brianddk/6b836fc6421e757d39239bd217d98a2b to your computer and use it in GitHub Desktop.
Save brianddk/6b836fc6421e757d39239bd217d98a2b to your computer and use it in GitHub Desktop.
Crypto Cost

Crypto Cost

A stupid little cost calculator to compare the quote on a BTC buy/sell against pro.coinbase.com order book. The results are the premium payed for the buy or sell.

Usage

  • Console: python crypt_cost.py counter base [exchange:base-counter]

    • counter: The counter currency (dollars) transacted in the buy/sell
    • base: The base currency (satoshis) transacted in the buy/sell
    • exchange: The exchange to use (coinbase, kraken, gemini).
    • base-cntr: The "base-counter" currency pair (btc-usd, ltc-btc, eth-btc... etc)
  • Output:

    • spot: The current USD/BTC spot price quoted on pro.coinbase.com order book
    • deal: The USD/BTC price you are about to pay on your current transaction
    • best: The smallest fee possible by crossing the spread and issuing a limit order on pro.coinbase.com
    • buy : The premium / discount you are paying / getting if this is a buy of BTC
    • sell: The premium / discount you are paying / getting if this is a sell of BTC

Install

To install, you will need either python installed. Either version 2 or version 3. If you want to run the KivyGui, you will need to perform the Kivy steps as well. For Android, there is a generic launcher on PlayStore. I've moved those instructions to a separate file.

Console

  1. Install Python. Either version 2 or 3 will work.
  2. Download a ZIP of the gist from here (Zip Download) or just clone the git.
  3. Follow the instructions in the Usage section above.

KivyGui

The KivyGui is really intended for Android, but you can run it on desktop if you want. To do so:

  1. Install Python. Either version 2 or 3 will work.
  2. Download a ZIP of the gist from here (Zip Download) or just clone the git.
  3. pip install kivy_deps.sdl2 kivy_deps.glew kivy_deps.gstreamer
  4. pip install kivy
  5. python cc_kivy.py

Android

Examples

  • Example: An example of a BTC buy quote on Coinbase.com Mobile App

    • Console: python crypt_cost.py 100 892030
    • Output:
      • spot: 10,817.57
      • deal: 11,210.39
      • best: 0.1834 %
      • buy : 3.6313 %
      • sell: -3.6313 %
    • Conclusion: By using the mobile app I would be paying a 3.6 % premium over spot. If I went to pro.coinbase.com and issued a limit order, it would have cost me 0.18 %.
  • Example: An example of a BTC buy quote on CashApp Mobile App (about an hour later)

    • Console: python crypt_cost.py 100 913916 btc-usd
    • Output:
      • spot: 10,739.94
      • deal: 10,941.92
      • best: 0.1501 %
      • buy : 1.8807 %
      • sell: -1.8807 %
    • Conclusion: By using the CashApp I would be paying a 1.9 % premium over spot. If I went to pro.coinbase.com and issued a limit order, it would have cost me 0.15 %. Note the use of the trading pair without the exchange. In the absence of a named exchange, Coinbase is assumed.
  • Example: An example of a BitRefill order for a Walmart Giftcard on Lightning Network. This would count as a sell of BTCLN for USD denominated Giftcard btw.

    • Console: python crypt_cost.py 100 979216 coinbase:btc-usd
    • Output:
      • spot: 10,704.68
      • deal: 10,212.25
      • best: 0.2068 %
      • buy : -4.6001 %
      • sell: 4.6001 %
    • Conclusion: By using the BitRefill to purchase the GiftCard, I would be paying a 4.6 % premium over spot. If I went to pro.coinbase.com and issued a limit order, to sell BTC for USD, it would have cost me 0.21 %. Notice the use of the named exchange in the pair. This could have been kraken: or gemini but it is just a simple example of the arguments form.
  • Example: An example of using it to calculate the on-chain fees of a TXN. 100,000 sats on chain with a 1,604 sat fee. Note the btc-btc pair.

    • Console: python crypt_cost.py 100000 101604 btc-btc
    • Output:
      • spot: 1.00000000
      • deal: 0.98421322
      • best: 0.1500 %
      • buy : -1.5787 %
      • sell: 1.6040 %
    • Conclusion: By using an on-chain BTC transaction, you are paying about 1.6 % in miner fees. Obviously larger transactions or batched transactions can lower this fee down to where it is barely noticable, but it is always good to be aware of what the fee is. There are special cases for the btc-btc or ltc-ltc or xxx-xxx key pairs. These default to a simple percentage calculator.

Thoughts

Please keep in mind that all the companies are taking substantial risk and great effort to provide these services. Although everyone wants these conversions to be as inexpensive as possible, there is the undeniable truth that these companies have overhead to sustain. My purpose is simply to provide a very simple price comparator to allow people to shop services effectively. These companies should be commended for treading into the waters of a cryptocurrency buisness.

Questions

If you have any questions, just add a comment to this gist (bottom of this page) and I will see it. You could also always catch me on reddit

Tips

Expand for tip addresses BTC-LN: https://tippin.me/@dkbriand
BTC-b32: bc1qwc2203uym96u0nmq04pcgqfs9ldqz9l3mz8fpj
BTC: 3AAzK4Xbu8PTM8AD3fDnmjdNkXkmu6PS7R
LTC-b32: ltc1q5uucgx9f8n70nq7jmjy03rpg84cm4tm70z5rz6
LTC: MKcAge42cX6WZnnPfFGJAxReUYZUbsi6t3
ETH / BAT: 0xBc72A79357Ff7A59265725ECB1A9bFa59330DB4b
*.pyc
__pycache__
.buildozer/
bin/

Android Setup

A stupid little cost calculator to compare the quote on a BTC buy/sell against pro.coinbase.com order book. The results are the premium payed for the buy or sell.

Install

I've included a kludgey Kivy layout for Android users. I haven't packaged an APK, but you can still run it with the Kivy Launcher (play store). Here's how:

  1. Install Kivy Launcher from the Android Play Store.
  2. Download a ZIP of the gist from here: Zip Download
  3. Create a folder called kivy on your android phone (/sdcard mount point)
  4. Extract the contents of the ZIP to a folder in the kivy dir.
  5. Final layout should look like this:
    /sdcard/kivy/<some_folder>/android.txt
    /sdcard/kivy/<some_folder>/main.py
    /sdcard/kivy/<some_folder>/cc_kivy.py
    /sdcard/kivy/<some_folder>/crypt_cost.py

If all goes well the Crypto Cost Kivy app should show up in the launcher. Keep in mind, different Android file browsers refer to internal storage differently. My Android puts the /sdcard mount point as the root directory in my file browser. I simply made a kivy directory in the same directory as my default Download folder. The easiest way to find the /sdcard mount point is to just use an adb shell, but I leave that up to you. Its rather kludegy right now, and I'll likely do the APK build later, but that requires a bit more work than I'm willing to expend today.

Screenshot

Image of Android App

Questions

If you have any questions, just add a comment to this gist (bottom of this page) and I will see it. You could also always catch me on reddit

Tips

Expand for tip addresses BTC-LN: https://tippin.me/@dkbriand
BTC-b32: bc1qwc2203uym96u0nmq04pcgqfs9ldqz9l3mz8fpj
BTC: 3AAzK4Xbu8PTM8AD3fDnmjdNkXkmu6PS7R
LTC-b32: ltc1q5uucgx9f8n70nq7jmjy03rpg84cm4tm70z5rz6
LTC: MKcAge42cX6WZnnPfFGJAxReUYZUbsi6t3
ETH / BAT: 0xBc72A79357Ff7A59265725ECB1A9bFa59330DB4b
title: Crypto Cost
author: brianddk
orientation: "portrait"
[app]
title = Crypto Cost
package.name = cryptcost
package.domain = io.github.brianddk
source.dir = .
source.include_exts = py,png,jpg,kv,atlas
version = 0.1
requirements = python3,kivy,certifi
orientation = portrait
fullscreen = 0
android.permissions = INTERNET
android.arch = armeabi-v7a
[buildozer]
log_level = 2
warn_on_root = 1
#!/usr/bin/env python2
# [rights] Copyright brianddk 2019 https://github.com/brianddk
# [license] Apache 2.0 License https://www.apache.org/licenses/LICENSE-2.0
# [repo] https://gist.github.com/brianddk/6b836fc6421e757d39239bd217d98a2b
# [lntip] BTCLN: https://tippin.me/@dkbriand
# [tipjar] https://gist.github.com/brianddk/3ec16fbf1d008ea290b0
# [refered] https://www.reddit.com/r/CoinBase/comments/ce2f2z/
# [usage] KivyGui: python2 cc_kivy.py
# [android] Launcher: play.google.com/store/apps/details?id=org.kivy.pygame
# [require] pip install kivy_deps.sdl2 kivy_deps.glew kivy_deps.gstreamer
# pip install kivy
from __future__ import print_function, division
from crypt_cost import calculate
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
class CryptCost(App):
def __init__(self, **kwargs):
super(CryptCost, self).__init__(**kwargs)
self.pair = "coinbase:btc-usd" # base-cntr
def lbl_update(self, trup):
(
self.lblSpot.text,
self.lblDeal.text,
self.lblBest.text,
self.lblBuy.text,
self.lblSell.text,
) = trup
def btn_press(self, btn):
try:
self.txtPair_focus(btn, False)
counterCur = float(self.txtCounter.text)
baseCur = float(self.txtBase.text)
self.lbl_update(calculate(counterCur, baseCur, self.pair))
except Exception as e:
rtn = "Failed!"
self.lbl_update((rtn, rtn, rtn, rtn, rtn))
def txtPair_focus(self, instance, value):
if not value:
try:
self.pair = self.txtPair.text.lower()
(txtBase, txtCounter) = self.pair.split(":")[-1].upper().split("-")
self.lblCounter.text = "{} Amount: ".format(txtCounter)
self.lblBase.text = "{} Amount: ".format(txtBase)
except Exception as e:
self.lblCounter.text = "-ERR Amount: "
self.lblBase.text = "ERR- Amount: "
def size_pos(self, num, col=1, textbox=False, double_wide=False):
elems = 11
pos = num / elems
sz = 1 / elems
txt_sz_delta = sz/2 if textbox else 0
txt_pos_delta = sz/4 if textbox else 0
cpos = .1 if col==1 else .5
ps_hnt = dict(x=cpos, y=pos + txt_pos_delta)
width = .8 if double_wide else .4
return dict(size_hint=(width, sz - txt_sz_delta), pos_hint=ps_hnt)
def build(self):
self.layout = FloatLayout()
self.layout.add_widget(Label(text="Currency Pair: ", **self.size_pos(9, 1)))
self.txtPair = TextInput(text=self.pair, multiline=False, **self.size_pos(9, 2, True))
self.txtPair.bind(focus=self.txtPair_focus)
self.layout.add_widget(self.txtPair)
(txtBase, txtCounter) = self.pair.split(":")[-1].upper().split("-")
self.lblCounter = Label(text="{} Amount: ".format(txtCounter), **self.size_pos(8, 1))
self.txtCounter = TextInput(text='', multiline=False, input_filter="float", **self.size_pos(8, 2, True))
self.layout.add_widget(self.lblCounter)
self.layout.add_widget(self.txtCounter)
self.lblBase = Label(text="{} Amount: ".format(txtBase), **self.size_pos(7, 1))
self.txtBase = TextInput(text='', multiline=False, **self.size_pos(7, 2, True))
self.layout.add_widget(self.lblBase)
self.layout.add_widget(self.txtBase)
self.layout.add_widget(Label(text="Spot Price: ", **self.size_pos(6, 1)))
self.lblSpot = Label(text="", **self.size_pos(6, 2))
self.layout.add_widget(self.lblSpot)
self.layout.add_widget(Label(text="Deal Price: ", **self.size_pos(5, 1)))
self.lblDeal = Label(text="", **self.size_pos(5, 2))
self.layout.add_widget(self.lblDeal)
self.layout.add_widget(Label(text="Best Price: ", **self.size_pos(4, 1)))
self.lblBest = Label(text="", **self.size_pos(4, 2))
self.layout.add_widget(self.lblBest)
self.layout.add_widget(Label(text="Buy Price: ", **self.size_pos(3, 1)))
self.lblBuy = Label(text="", **self.size_pos(3, 2))
self.layout.add_widget(self.lblBuy)
self.layout.add_widget(Label(text="Sell Price: ", **self.size_pos(2, 1)))
self.lblSell = Label(text="", **self.size_pos(2, 2))
self.layout.add_widget(self.lblSell)
btn1 = Button(text="Calculate", **self.size_pos(1, 1, False, True))
btn1.bind(on_press=self.btn_press)
self.layout.add_widget(btn1)
return self.layout
CryptCost().run()
#!/usr/bin/env python2
# [rights] Copyright brianddk 2019 https://github.com/brianddk
# [license] Apache 2.0 License https://www.apache.org/licenses/LICENSE-2.0
# [repo] https://gist.github.com/brianddk/6b836fc6421e757d39239bd217d98a2b
# [lntip] BTCLN: https://tippin.me/@dkbriand
# [tipjar] https://gist.github.com/brianddk/3ec16fbf1d008ea290b0
# [refered] https://www.reddit.com/r/CoinBase/comments/ce2f2z/
# [usage] console: python crypt_cost.py counter base [exchange:bas-ctr]
# counter: The counter currency (dollars) transacted in the buy/sell
# base: The base currency (satoshis) transacted in the buy/sell
# exchange: The exchange to use (coinbase, kraken, gemini).
# base-cntr: The "base-counter" currency pair (btc-usd, ltc-btc.. etc)
from __future__ import print_function, division
from sys import argv, version_info
import certifi
import traceback
if version_info[0] < 3:
from urllib2 import Request, urlopen
else:
from urllib.request import Request, urlopen
from json import loads, dumps
def decode_coinbase(jobj):
ask = jobj["asks"][0][0]
bid = jobj["bids"][0][0]
return (float(ask), float(bid))
def decode_gemini(jobj):
ask = jobj["asks"][0]["price"]
bid = jobj["bids"][0]["price"]
return (float(ask), float(bid))
def decode_kraken(jobj):
for i in jobj["result"]:
ask = jobj["result"][i]["asks"][0][0]
bid = jobj["result"][i]["bids"][0][0]
return (float(ask), float(bid))
def calculate(counterCur, baseCur, pair="coinbase:btc-usd"): # base-cntr
jstr = msg = exp = ""
pair = pair.lower()
(exchange, pair) = pair.split(":") if ":" in pair else ("coinbase", pair)
(base, counter) = pair.split("-") if "-" in pair else (pair, pair)
urls = dict(coinbase = "https://api.pro.coinbase.com/products/{}-{}/book",
gemini = "https://api.gemini.com/v1/book/{}{}?limit_bids=1&limit_asks=1",
kraken = "https://api.kraken.com/0/public/Depth?pair={}{}&count=1")
decoders = dict(coinbase = decode_coinbase, gemini = decode_gemini, kraken = decode_kraken)
fees = dict(coinbase = 0.0015, gemini = 0.0025, kraken = 0.0016)
scale = 100000000
if counter == base:
ask = float(1)
bid = float(1)
else:
url = urls[exchange].format(base, counter) # base-cntr
q = Request(url)
q.add_header('User-Agent', 'Python/{}.{}.{}'.format(*version_info))
try:
req = urlopen(q, cafile=certifi.where())
jstr = req.read().decode()
jobj = loads(jstr)
(ask, bid) = decoders[exchange](jobj)
except Exception as e:
print(traceback.format_exc())
rtn = "Failed!"
return (rtn, rtn, rtn, rtn, rtn)
spot = (bid + ask)/2
deal = counterCur / baseCur # cntr / base
if spot / deal > 1000:
baseCur = baseCur / scale
deal = counterCur / baseCur # cntr / base
buy = deal/spot - 1
sell = spot/deal - 1
best = fees[exchange] + (ask - bid)/spot/2
if counter in ["usd", "gbp", "cad", "eur"]:
bfmt = "{:,.2f}"
else:
bfmt = "{:,.8f}"
return (
bfmt.format(spot),
bfmt.format(deal),
"{:,.4f} %".format(best * 100), #best
"{:,.4f} %".format(buy * 100), #buy
"{:,.4f} %".format(sell * 100) #sell
)
fmt = """
spot: {}
deal: {}
best: {}
buy: {}
sell: {}"""
def console():
if len(argv) > 3:
trup = calculate(float(argv[1]), float(argv[2]), str(argv[3]))
else:
trup = calculate(float(argv[1]), float(argv[2]))
print(fmt.format(*trup))
if __name__ == "__main__":
console()

Examples

Order spot deal best premium/discount
Bitrefill BestBuy $100 9,927.81 9,979.54 0.1547 % -0.5211 %
Bitrefill GooglePlay $100 9,884.15 9,751.61 0.1970 % 1.3409 %
CoinCards Amazon $100 9,919.72 9,907.70 0.1556 % 0.1212 %
CoinCards GooglePlay $100 9,946.99 9,925.79 0.1501 % 0.2131 %
Moon Amazon Cart $100 9,864.33 9,864.27 0.1501 % 0.0007 %
CashApp Buy $100 BTC 9,664.19 9,790.02 0.2014 % 1.3020 %
CoinbaseMobile Buy $100 BTC 9,707.41 10,059.26 0.1994 % 3.6245 %
CoinbaseMobile Buy 100 in ETH 211.37 219.55 0.1973 % 3.8720 %
CoinbaseMobile Buy 100 in LTC 89.92 93.23 0.2278 % 3.6725 %
CoinbaseMobile Convert BTC->ETH 0.02182500 0.02205305 0.1958 % 1.0449 %
CoinbaseMobile Convert BTC->LTC 0.00928550 0.00938694 0.3115 % 1.0925 %
#!/usr/bin/env python2
# [rights] Copyright brianddk 2019 https://github.com/brianddk
# [license] Apache 2.0 License https://www.apache.org/licenses/LICENSE-2.0
# [repo] https://gist.github.com/brianddk/6b836fc6421e757d39239bd217d98a2b
# [lntip] BTCLN: https://tippin.me/@dkbriand
# [tipjar] https://gist.github.com/brianddk/3ec16fbf1d008ea290b0
# [refered] https://www.reddit.com/r/CoinBase/comments/ce2f2z/
# [usage] console: python2 main.py counterCur baseCur [bas-ctr]
# KivyGui: python2 main.py
# [android] Launcher: play.google.com/store/apps/details?id=org.kivy.pygame
# [require] pip install kivy_deps.sdl2 kivy_deps.glew kivy_deps.gstreamer
# pip install kivy
from __future__ import print_function, division
from sys import argv
if len(argv) > 1:
from crypt_cost import console
console()
else:
import cc_kivy
#!/usr/bin/env python2
# [rights] Copyright brianddk 2019 https://github.com/brianddk
# [license] Apache 2.0 License https://www.apache.org/licenses/LICENSE-2.0
# [repo] https://gist.github.com/brianddk/6b836fc6421e757d39239bd217d98a2b
# [lntip] BTCLN: https://tippin.me/@dkbriand
# [tipjar] https://gist.github.com/brianddk/3ec16fbf1d008ea290b0
from __future__ import print_function, division
from sys import argv, version_info
if version_info[0] < 3:
from urllib2 import Request, urlopen
else:
from urllib.request import Request, urlopen
from json import loads, dumps
urls = dict(coinbase = "https://api.pro.coinbase.com/products",
gemini = "https://api.gemini.com/v1/symbols",
kraken = "https://api.kraken.com/0/public/AssetPairs")
for exchange in ["coinbase", "gemini", "kraken"]:
url = urls[exchange]
q = Request(url)
q.add_header('User-Agent', 'Python/{}.{}.{}'.format(*version_info))
req = urlopen(q)
jstr = req.read().decode()
jobj = loads(jstr)
if exchange == "kraken":
for i in jobj['result']:
if "wsname" in jobj['result'][i]:
pair = jobj['result'][i]['wsname']
(base, counter) = pair.lower().split('/')
print("kraken:{}-{}".format(base, counter))
else:
continue
if exchange == "gemini":
for i in jobj:
(base, counter) = (i[:3], i[3:])
print("gemini:{}-{}".format(base, counter))
if exchange == "coinbase":
for i in jobj:
pair = i['id']
(base, counter) = pair.lower().split("-")
print("coinbase:{}-{}".format(base, counter))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment