In [1]:
#Configure the environment
import numpy as np
import pandas as pd
import requests
import logging
%matplotlib notebook
import matplotlib
import matplotlib.pyplot as pyplot
import bitcoin
log = logging.getLogger(bitcoin.__name__)
log.setLevel(logging.ERROR)
print 'Pandas version: %s' % (pd.__version__,)
print 'Numpy version: %s' % (np.__version__,)
print 'Using MatplotLib backend: %s' % (matplotlib.get_backend())
Pandas version: 0.17.0
Numpy version: 1.10.1
Using MatplotLib backend: nbAgg
/home/cayle/.anaconda/envs/scipython/lib/python2.7/site-packages/IPython/kernel/__init__.py:13: ShimWarning: The `IPython.kernel` package has been deprecated. You should import from ipykernel or jupyter_client instead.
  "You should import from ipykernel or jupyter_client instead.", ShimWarning)
In [2]:
btc = bitcoin.BTCMarket()

Bitcoin Miner model


Miners can choose to stockpile, or sell BTC into global circulation. However, it's assumed that a miner must sell enough BTC to cover his costs.

For a given miner, assuming he sells all his BTC, we have

\begin{equation} \pi_i = \int_{t1}^{t2} \frac{m_i \cdot b(t) \cdot p(t)}{H(t)} dt \end{equation}

where

  • 𝜋 is income (\$/s)
  • t is time (s)
  • m is the miner's hash rate (H/s)
  • b is the number of BTC released per block
  • p is the BTC price (\$)
  • H is the number of hashes required to release a block

Since the functions aren't truly continuous, the integral becomes a summation, and a daily income can be derived. Furthermore, the stock of BTC a miner holds is a simple differential equation; the rate of production minus the selling rate. $$ \frac{dS_i}{dt} = \beta_i - s_i $$

Rearranging and assuming that the smallest time period of interest is a day, then the discretised functions look like

\begin{align} \beta_{ij} &= \frac{m_i b_j}{\bar{H}_j} \cdot 3600 \cdot 24 \\ \Delta S_{ij} &= \beta_{ij} - s_{ij} \\ I_{ij} &= s_{ij} \bar{p}_j \end{align}

where

  • j is the jth day of production
  • β is the BTC production rate in BTC/day
  • S is the quantity of BTC in the miner's wallet
  • I is the income of a miner, from selling bitcoin into the market
  • $\bar{p}$ is the average BTC price on the jth day

Let's assume the following:

  • In a bull run, where the price is high, ($\bar{p} > \mathrm{FACC}$), miners sell to achieve a return and stockpile the rest
  • In leaner times, they sell to cover fully absorbed cash cost, without a return on capital, and stockpile the rest ($\mathrm{CC} < \bar{p} < \mathrm{FACC}$)
  • Where the price is depressed, ($\bar{p} < \mathrm{CC}$), miners sell everything they produce
In [3]:
phc = btc.priceHistoryComplete
ph = btc.getPriceHistory()
In [4]:
# Plot the BTC price in USD over its full history with a 95% confidence interal
ci = ph.Stdev*1.96 
hi = ph.price + ci
lo = ph.price - ci
ax = ph.price.plot(figsize=(12,7))
pyplot.fill_between(ci.index, hi, lo, color='b', alpha=0.2)
ax.set_ylabel('BTC Price (USD)')
ax.set_title('Bitcoin price history')
Out[4]:
<matplotlib.text.Text at 0x7fedbd01b390>

Mining Capacity

Let's have a look at the pace of BTC mining capacity addition.

In theory the time per block is 10 minutes. In times of rapid capacity addition, this number will be below ten minutes all the time, with the increase in difficulty lagging somewhat to try and compensate.

A bootstrap plot (below) shows that the mean time per block is generally 9 minutes, indicating that capacity has generally been ahead of the difficulty level.

I've also plotted the monthly mean time (in minutes) for mining a BTC block based on the global net mining rate and the difficulty below that. Also in the plot is the average BTC price for the month as an indication of what's driving demand for increasing capacity.

The two periods of greatest price increase, between 2010-06 and 2011-6 and in 2013 we see the average block mining time at values well below 10 minutes consistently.

In [5]:
dhexp = btc.difficulty_history.drop('blockNumber',1).resample('1d',fill_method='ffill')
#timeForBlock = 1e-9 * 4295032833 * dhexp.difficulty  / btc.hashrate_history / 60.0
timeForBlock = 1e-9 * 4295032833 / 60 * dhexp.difficulty.div(btc.hashrate_history.Value)
timeForBlock = timeForBlock.dropna()

blocktime = timeForBlock.resample('M')
aveMonthlyPrice = ph.price.resample('M')


fig, ax1 = pyplot.subplots()
ax2 = ax1.twinx()
ax2.set_yscale('log')
ax1.plot(blocktime.index, blocktime, 'r')
ax2.plot(aveMonthlyPrice.index, aveMonthlyPrice, 'b')

ax1.set_xlabel('')
ax1.set_ylabel("time per block (min)", color='r')
ax2.set_ylabel("ave BTC price (\$)", color='b')

ax1.axvspan('2010-07-01','2011-07-01', facecolor='g', alpha=0.5)
ax1.axvspan('2013-02-01','2013-12-31', facecolor='g', alpha=0.5)
Out[5]:
<matplotlib.patches.Polygon at 0x7fedbcdf0e10>
In [6]:
# Plot the net hash rate in GH/s over history, on a log scale
aveNetHash = btc.hashrate_history
aveNetHash.sort_index(inplace=True)
aveNetHash.plot(logy=True)
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fedbcc27910>
In [7]:
# Example simple global mining pool calculation with manual capacity addition
reload(bitcoin)
start_date = '2013-03-01'
end_date = '2015-10-01'


supply = bitcoin.MiningSupplyGenerator(start_date, end_date, None, overheads=0.15)
supply.generateSupplyFromHistory()
ERROR:bitcoin.supply:Could not load supply history. [data/supply_history.pickle] does not exist

Hardware distribution history

The folowing plot displays the hardware distribution over time as the hashing difficulty improves, and the hardware (cost and energy efficiency) improves.

The y-axis tracks total hash rate on a log scale, and the x-axis indicates time. The left-hand plot shows total global mining capacity, while the right-hand plot shows only hardware that should be operating, i.e. where cash cost < income from mining. The dots are the actual network hash rate as reported by Quandl's network index.

In [8]:
# How to mix pyplot commands with Pandas plotting features
#hwdata = hwdist.xs('totalHashRate', level='category')
drange = supply.date_range
hrData = supply.data['hashRateDistribution'].loc[drange]
mask = supply.data['canOperate'].loc[drange]
hrDataAll = hrData.sum(axis=1, level='Product')
hrDataRunning = hrData.mul(mask).sum(axis=1, level='Product')
# Plot configuation
netHash = btc.hashrate_history.loc[drange,:] 
fig, axes = pyplot.subplots(nrows=1, ncols=2, figsize=(16, 8))
#axes[0].plot(hrDataAll.index, hrDataAll)
axes[0].set_yscale('log')
axes[0].set_ylabel('Hash rate capacity(GH/s)')
axes[0].plot(netHash.index, netHash, 'k:', label='Actual historical')
hrDataAll.plot(stacked=True, kind='area', ax=axes[0])

axes[1].set_yscale('log')
axes[1].set_ylabel('Hash rate capacity(GH/s)')
axes[1].plot(netHash.index, netHash, 'k:', label='Actual historical')
hrDataRunning.plot(stacked=True, kind='area', ax=axes[1])
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fedb84c4350>

Most miners are running at an absolute loss in 2015

The cash cost curves on the bottom row of the following figures are quite interesting. The red lines show the cash cost curve (assuming a 15% overheads rate -- which is almost certainly on the low side) for the given dates. The entire global BTC mining capacity is graphed, but the y-axis range is capped, clipping most expensive hardware (FGPAs and early ASICs) out of the picture. The fully absorbed cash cost (FACC), which includes depreciation (assuming a 2 year lifetime on hardware) and a desired return on capital (assumed as 15%) is also shown in blue.

Hardware is sorted by cash cost and the lowest threshold of each type of hardware is annoted on the plot for illustration purposes.

The 2015 curve indicates that most operaters are running at an absolute loss at present; a fact that has been going on since the beginning of 2015, as evidenced in the 'rational predicted' and 'actual' hash rates of the right-hand plot above.

As the 2015 cash cost curve below indicates, only the most efficient miners are recovering the capital costs of their equipment, but it also shows that even with a BTC price under $300, there are rigs available that make sense for investment. One therefore expects the hashing difficulty to remain roughly flat until all the unprofitable operators are forced out -- though they seem to be displaying some resiliance; perhaps by selling off BTC accumulated pre-2014 -- and then marginal capacity increasing at the rate factories can produce these efficient rigs.

In [9]:
dates = ['2013-10-01', '2013-11-30','2015-01-01','2015-10-01']
titles = ['before bubble', 'peak of bubble', 'after crash', 'today']
topY = [20, 100, 400, 500]

n = len(dates)

fig, axes = pyplot.subplots(nrows=2, ncols=n, figsize=(n*8,15))
for col, date in enumerate(dates):
    hw = supply.getHardwareDistribution(date)
    cc = supply.getCostCurve(date)
    price = ph.price[date]
    labels = cc.index.drop_duplicates()
    axPie = axes[0, col]
    axCC = axes[1, col]
    axPie.set_title('Hardware distribution %s\n %s - $%3.0f/BTC' % (titles[col], str(date), price))
    hw.plot(kind='pie', ax=axPie)
    axCC.set_title('Cash cost curve %s' % (titles[col],))
    axCC.set_ylim([0,topY[col]])
    cc.plot(x='cumHash', y=['cashCost', 'facc'], ax=axCC, legend=False)
    axCC.set_xlabel('Cumulative hash (GH/s)')
    axCC.axhline(price, color='b')
    #Annotate curve with hardware
    maxH = cc['cumHash'].max()
    for label in labels:
        row = cc.loc[label].iloc[0]
        lx = row.cumHash + 0.1*maxH if row.cumHash < 0.8*maxH else row.cumHash - 0.1*maxH
        axCC.annotate(label, xy=(row.cumHash, row.cashCost), 
                     xytext=(lx, row.cashCost - 0.1*topY[col]),
                    arrowprops={'facecolor': 'k',
                                'shrink': 0.05,
                                'width': 1})

Capital investment in Bitcoin mining hardware

The following chart presents a minimum capital investment into bitcoin mining hardware.

It is a minimum because

  • For each day in the calculation, only the most efficient available hardware (acording to BitCoin wiki -- the hardware may not even have been shipping yet) is used to account for the entire marginal increase in hashing capacity.
  • Any other hardware, but especially GPU or CPU cycles committed to BTC mining (whether knowingly or not -- e.g. illegal zombie networks) therefore necessarily represents a higher capital investment than what we've allocated.

Notice the remarkable addition of hardware over Q1 2014, where at least $1.3billion was added to bitcoin mining operations in 3 months. Clearly, ASIC manufacturers could never have supplied this demand, so it really begs the question as to where this incredible amount of capacity came form.

In [10]:
numMachines = supply.data['numMachines']
hw = btc.hardware[['Product','Price']]
df = pd.merge(numMachines, hw, on='Product')
cols = numMachines.columns
cols = cols.drop('Product')
df = df[cols].mul(df.Price, axis=0).sum(axis=1).mul(1e-6)
capex = df.cumsum()
capex.index = numMachines.index
#Plot
fid, ax = pyplot.subplots()
ax.plot(capex.index, capex)
ax.set_ylabel('Capital investment ($mil)')
ax.set_xlabel('Date')
Out[10]:
<matplotlib.text.Text at 0x7fedb1476510>
In [11]:
pyplot.close('all')
#supply.save()
In [ ]: