In this notebook I follow the methodology from "Visualising Forecasting Algorithm Performance using Time Series Instance Spaces" to generate time series features and then apply PCA and plot the components in an interactive holoviews graph to try and find interesting cryptocurrencies to look at
Resources:
from rpy2.robjects.packages import importr
#get ts object as python object
from rpy2.robjects import pandas2ri
import rpy2.robjects as robjects
import pandas as pd
import numpy as np
ts=robjects.r('ts')
import numpy as np
import pandas as pd
import holoviews as hv
hv.extension('bokeh', 'matplotlib')
#run this so bokeh plots are outputted when notebook is downlaoded as html
from bokeh.io import output_notebook
output_notebook()
import seaborn as sns
import matplotlib.pyplot as plt
%pylab inline
sns.set(style="whitegrid")
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from datetime import datetime
from coinmarketcap import Market
#install ForeCA package in R
try:
ForeCA=importr('ForeCA')
except:
#if forecast package doesnt load then need to install forecast package first
importr("utils")
utils = importr('utils')
packnames = ('ForeCA','forecast')
from rpy2.robjects.vectors import StrVector
utils.install_packages(StrVector(packnames))
ForeCA=importr('ForeCA')
Getting the Data¶
This function will return a dictionary of the crypto symbols as the keys and the values as the cryptocurrency names for a given amount of symbols
def get_top_x_symbols(n_symbols=500):
"""
small function to return dict of top n symbols from coinmarket API
keys are symbols
values are names of cryptocurrencies
"""
import pandas as pd
import numpy as np
from coinmarketcap import Market
coinmarketcap = Market()
n_iter = math.ceil(n_symbols/100)
start = 0
limit = 100
symbol_name_dic = {}
#paginate through results
for page in range(n_iter):
dic = coinmarketcap.ticker(start=start, limit=limit, convert='USD')
#loop through each json block
for i in dic['data']:
symbol_name_dic[dic['data'][i]['symbol']] = dic['data'][i]['name']
#count up
start += 100
limit += 100
return symbol_name_dic
symbols = get_top_x_symbols()
#symbols
This is our workhorse function for making requests to coinmarketcap.com to get historical data for the coins
def get_historical_price_df(cryptocurrency='bitcoin', start_date = '20160501', end_date = datetime.now().strftime('%Y%m%d')):
"""gets historical prices and market cap of a particular cryptocurrency as a pandas df
Args:
cryptocurrency: full name of the cryptocurrency to pull
Returns:
historical_df: pandas df of historical data for the cryptocurrency
"""
#we'll just take from 2013 up until current datetime
url_string = "https://coinmarketcap.com/currencies/" + cryptocurrency + '/historical-data/?start=' + start_date + '&end=' + end_date
historical_price_df = pd.read_html(url_string,parse_dates=['Date'])[0]
historical_price_df = historical_price_df.set_index('Date')
return historical_price_df
get_historical_price_df().head()
We need to do some manual mapping because of the way the URL's on coinmarketcap work for historical data
#special cases that have different crypto names/symbols than what's found in url of coinmarketcap:
symbols['SXDT'] = 'spectre-dividend'
symbols['GTC'] = 'game'
symbols['IOC'] = 'iocoin'
symbols['ETP'] = 'metaverse'
symbols['MRK'] = 'mark-space'
symbols['TRST'] = 'trust'
symbols['HTML'] = 'html-coin'
symbols['POLY'] = 'polymath-network'
symbols['GBYTE'] = 'byteball'
symbols['DBET'] = 'decent-bet'
symbols['DLT'] = 'agrello-delta'
symbols['RMC'] = 'russian-mining-coin'
symbols['LBC'] = 'library-credit'
symbols['AMB'] = 'amber'
symbols['ECC'] = 'eccoin'
symbols['SAN'] = 'santiment'
symbols['BCN'] = 'bytecoin-bcn'
symbols['GUP'] = 'guppy'
symbols['KICK'] = 'kickico'
symbols['NET'] = 'nimiq'
symbols['ATM'] = 'attention-token-of-media'
symbols['MTN'] = 'medical-chain'
symbols['ELEC'] = 'electrifyasia'
symbols['DDD'] = 'scryinfo'
symbols['BCPT'] = 'blockmason'
symbols['BLT'] = 'bloomtoken'
symbols['NAV'] = 'nav-coin'
symbols['POE'] = 'poet'
symbols['MUSE'] = 'bitshares-music'
symbols['CFI'] = 'cofound-it'
This for loop will return us a dataframe with Sorted datetime index, and crypto ticker symbols as the columns
dfs = {}
for i in symbols.items():
#try by symbol and by name of cryptocurrency
try:
#names with spaces get replace by hyphens
df = get_historical_price_df(cryptocurrency=i[1].lower().replace(' ','-'))
dfs[i] = df['Close'].sort_index()
except:
try:
df = get_historical_price_df(cryptocurrency=i[0].lower())
dfs[i] = df['Close'].sort_index()
except:
print(i,'not found')
#sometime get bad entries from APi, so delete these, could try retry in future for data pull
bad_keys = []
for i in dfs.items():
if 'No data was found for the selected time period.' in i[1]:
#delete dictionary entries with no data
bad_keys.append(i[0])
for i in bad_keys:
del dfs[i]
final_df = pd.DataFrame.from_dict(dfs)
final_df.columns = final_df.columns.droplevel(level=1)
final_df.head()
We'll need to filter out series with less than 2 full year of data or else we can't use STL decomposition
column_keep_list = []
for column in final_df:
if len(final_df[column][final_df[column].notna()].values) > 365*2:
column_keep_list.append(column)
df = final_df[column_keep_list]
df.head()
Extracting the features for Closing Price of each cryptocurrency series¶
The function below taken is a time series and returns the 6 features defined in :
def tsfeatures(time_series, freq=12):
"""Generates features from time series
(https://www.monash.edu/business/econometrics-and-business-statistics/research/publications/ebs/wp10-16.pdf)
Args:
time_series: time series object
freq: frequency of time series (12 is monthly)
Returns:
full_series: time series of fitted and forecasted values
"""
#find the start of the time series
start_ts = time_series[time_series.notna()].index[0]
#find the end of the time series
end_ts = time_series[time_series.notna()].index[-1]
#extract actual time series
time_series = time_series.loc[start_ts:end_ts]
#interpolate any missing values
time_series = time_series.interpolate()
#converts to ts object in R
time_series_R = robjects.FloatVector(time_series)
rdata=ts(time_series_R,frequency=freq)
rstring="""
function(rdata){
library(forecast)
library(ForeCA)
stl_decomp_res = stl(rdata, s.window = "periodic")
trend = stl_decomp_res$time.series[,2]
seasonal = stl_decomp_res$time.series[,1]
remainder = stl_decomp_res$time.series[,3]
detrend_ts = rdata - trend
deseasonal_ts = rdata - seasonal
#Entropy
F1 = spectral_entropy(na.contiguous(rdata))[1L]
#Strength_Trend
F2 = 1- (var(remainder)/(var(deseasonal_ts)))
#Strength_Seasonal
F3 = 1- (var(remainder)/(var(detrend_ts)))
#Period
F4 = frequency(rdata)
#First Order Autocorrelation
F5_temp = Acf(rdata)
F5 = F5_temp$acf[2]
#optimal lambda parameter for transformation
F6 = BoxCox.lambda(rdata,lower=0,upper=1)
return(list(Entropy=F1,Trend=F2,Seasonal=F3,Period=F4,ACF1=F5,lambda=F6))
}
"""
rfunc=robjects.r(rstring)
#gets fitted and predicted series, and lower and upper prediction intervals from R model
Entropy,Trend_stength,Seasonal_strength,Period,ACF1,optim_lambda=rfunc(rdata)
#convert to python objects
Entropy = pandas2ri.ri2py(Entropy)
Trend_stength = pandas2ri.ri2py(Trend_stength)
Seasonal_strength = pandas2ri.ri2py(Seasonal_strength)
Period = pandas2ri.ri2py(Period)
ACF1 = pandas2ri.ri2py(ACF1)
optim_lambda = pandas2ri.ri2py(optim_lambda)
return Entropy[0],Trend_stength[0],Seasonal_strength[0],Period[0],ACF1[0],optim_lambda[0]
Now we'll loop through each column in the dataframe of cryptocurrencies and extract the time series features we want using the function above and append each feature for each series to a list
F1 = []
F2 = []
F3 = []
F4 = []
F5 = []
F6 = []
index_ts = []
for i in df:
try:
Entropy,Trend_stength,Seasonal_strength,Period,ACF1,optim_lambda = tsfeatures(df[i], freq=365)
F1.append(Entropy)
F2.append(Trend_stength)
F3.append(Seasonal_strength)
F4.append(Period)
F5.append(ACF1)
F6.append(optim_lambda)
index_ts.append(i)
except:
print('error: ',i)
Now we can combine all these lists of features into a dataframe which has the features as columns and the coins as the row index
#all time series have same period here, so won't use here, as will become 0 in princomp analysis
df_features = pd.DataFrame(
{'Entropy': F1,
'Trend_stength': F2,
'Seasonal_strength': F3,
#'Period': F4,
'ACF1': F5,
'optim_lambda': F6
},
index=index_ts)
df_features.head()
Below we can use seaborn Pairgrid to plot the scatterplots and density plots of the features over the space of time series
scatter_graph = sns.PairGrid(df_features, diag_sharey=False,size=2)
scatter_graph.map_lower(sns.kdeplot, cmap="Blues")
scatter_graph.map_upper(plt.scatter)
scatter_graph.map_diag(sns.kdeplot, shade=True,lw=3);
We can see that, similar to the research paper, there is correlation between trend strength and Entropy, autocorrelation at lag 1 and entropy, and possibly slight correlation of seasonal strength and entropy. This is all intuitive, as a lower entropy suggests a more forecastable series, and a series with high trend, seasonal, or lag 1 autocorrelation measures suggests a series is easier to forecast as well
Performing PCA on the features¶
Now we can use Principal Components analysis to reduce this feature space into 2 components that can be graphed in 2-D space and viewed in a more sensible way
pca = PCA(n_components=2)
pca.fit(df_features)
explained_ratios = pca.explained_variance_ratio_
print('PC1 variance explained: ', round(explained_ratios[0],4)*100,'%','\n',
'PC2 variance explained: ',round(explained_ratios[1],4)*100,'%')
df_PCA = pca.transform(df_features)
df_PCA[:5]
No we'll add the PCA coefficents for each row back to the features dataframe
df_plotting = df_features.copy()
df_plotting['PC1'] = df_PCA[:,0]
df_plotting['PC2'] = df_PCA[:,1]
#we are going to take the absolute value of the autocorrelation at lag 1,
#because for plotting we don't really are about the direction, only the strength
df_plotting['ACF1'] = abs(df_plotting['ACF1'].values)
df_plotting.head()
This is a plotting detail for holoviews, we'll reset the index so we can select the time series as a column
df_plotting_no_index = df_plotting.reset_index()
df_plotting_no_index.rename(columns={'index':'time_series'}, inplace=True)
df_plotting_no_index.head()
Plotting the features for Closing Price across the instance space¶
Now we create a dictionary where the keys are the feature names, and the values are the individual PCA plots for the features with each time series as a point
scatter_dic = {}
for feature in df_features.columns:
#print(feature)
scatter_plot_x = hv.Scatter(df_plotting_no_index, 'PC1',['PC2','time_series',feature],label=feature)
scatter_plot_x = scatter_plot_x.options(width=300,height=200,tools=['hover'],color_index=feature,cmap='RdYlGn',colorbar=True,size=5,show_grid=True)
scatter_dic[feature] = scatter_plot_x
Finally, we can plot whichever features we want from our dict onto a holoviews interactive chart. These graphs show the distribution of features across the instance space. You can hover over the individual points to see which cryptocurrency you are looking at.
Series towards the bottom left are easier to forecast, with low entropy, high trend, seasonality, and lag1 autocorrelation
feature_scatter = (scatter_dic['ACF1'] + scatter_dic['Entropy'] + scatter_dic['Seasonal_strength'] +
scatter_dic['Trend_stength'] + scatter_dic['optim_lambda']).cols(2)
feature_scatter
It's no surprise to see BTC at the bottom left, since it's the most established coin, with a lot of data on historical price to look at
df[['BTC','BITUSD','LEO']].plot(subplots=True,title='Closing Price of BTC,BITUSD,LEO over time in USD',figsize=(10,8));
Extracting the features for Returns of each cryptocurrency series¶
Now we'll do the same analysis but look at returns, since that's more interesting than being able to predict the price of a coin
returns_df = df.pct_change()
returns_df.head()
F1 = []
F2 = []
F3 = []
F4 = []
F5 = []
F6 = []
index_ts = []
for i in returns_df:
try:
Entropy,Trend_stength,Seasonal_strength,Period,ACF1,optim_lambda = tsfeatures(returns_df[i], freq=365)
F1.append(Entropy)
F2.append(Trend_stength)
F3.append(Seasonal_strength)
F4.append(Period)
F5.append(ACF1)
F6.append(optim_lambda)
index_ts.append(i)
except:
print('error: ',i)
#all time series have same period here, so won't use here, as will become 0 in princomp analysis
df_features = pd.DataFrame(
{'Entropy': F1,
'Trend_stength': F2,
'Seasonal_strength': F3,
#'Period': F4,
'ACF1': F5,
'optim_lambda': F6
},
index=index_ts)
df_features.head()
scatter_graph = sns.PairGrid(df_features, diag_sharey=False,size=2)
scatter_graph.map_lower(sns.kdeplot, cmap="Blues")
scatter_graph.map_upper(plt.scatter)
scatter_graph.map_diag(sns.kdeplot, shade=True,lw=3);
pca = PCA(n_components=2)
pca.fit(df_features)
df_PCA = pca.transform(df_features)
explained_ratios = pca.explained_variance_ratio_
print('PC1 variance explained: ', round(explained_ratios[0],4)*100,'%','\n',
'PC2 variance explained: ',round(explained_ratios[1],3)*100,'%')
df_plotting = df_features.copy()
df_plotting['PC1'] = df_PCA[:,0]
df_plotting['PC2'] = df_PCA[:,1]
#we are going to take the absolute value of the autocorrelation at lag 1,
#because for plotting we don't really are about the direction, only the strength
df_plotting['ACF1'] = abs(df_plotting['ACF1'].values)
df_plotting_no_index = df_plotting.reset_index()
df_plotting_no_index.rename(columns={'index':'time_series'}, inplace=True)
df_plotting_no_index.head()
scatter_dic = {}
for feature in df_features.columns:
#print(feature)
scatter_plot_x = hv.Scatter(df_plotting_no_index, 'PC1',['PC2','time_series',feature],label=feature)
scatter_plot_x = scatter_plot_x.options(width=300,height=200,tools=['hover'],color_index=feature,cmap='RdYlGn',colorbar=True,size=5,show_grid=True)
scatter_dic[feature] = scatter_plot_x
feature_scatter = (scatter_dic['ACF1'] + scatter_dic['Entropy'] + scatter_dic['Seasonal_strength'] +
scatter_dic['Trend_stength'] + scatter_dic['optim_lambda']).cols(2)
feature_scatter
As you might expect the forecastibility of returns of a cryptocurrency is extremely difficult with none having spectral entropy value less than .9, and you can see the lag 1 correlations and trend strengths are much lower than the raw closing price data as well
One interesting note is you can see DimeCoin is an anomaly in this data here with one of the highest relative trend strengths and lowest entropy. I'm not sure exactly why this is the case, but taking a quick glance at the coin's website, the coin does not have a max supply. In fact it is inflationary so perhaps this affects the trend of the returns. let's take a look below
print('summed inter-day percent returns for BTC: ',returns_df['BTC'].sum())
print('summed inter-day percent returns for ETH: ',returns_df['ETH'].sum())
print('summed inter-day percent returns for XRP: ',returns_df['XRP'].sum())
print('summed inter-day percent returns for SAFEX: ',returns_df['SAFEX'].sum())
print('summed inter-day percent returns for BITUSD: ',returns_df['BITUSD'].sum())
print('summed inter-day percent returns for DIME: ',returns_df['DIME'].sum())
returns_df[['DIME','BTC','ETH','XRP','SAFEX','BITUSD']].plot(subplots=True,title='Percent Returns for DIME and other cryptocurrencies',figsize=(10,12));
returns_df[['DIME','BTC','ETH','XRP','SAFEX','BITUSD']].plot(kind="hist",bins=100,subplots=True,title='Percent Returns Distribution for DIME and other cryptocurrencies',figsize=(10,12));
The returns are not normally distributed around 0 it seems, with early 2018 seeing a large increase, and some outliers in the right tail of the distribution
I think this may be an artifact of small market cap coins that recently had a big pop in late 2017 and early 2018. We can see this if we take the 5 coins with highest summer returns. It's possible these coins haven't seen a long enough time scale to have normally distributed returns. We tried to filter these coins out earlier but some coins have long left tails with numbers very close to 0, and it's hard to know if this is their actual price or just an anomaly in the data collection process. Without diving individually into each coin and when it was officially launched, it's hard to know.
top_5_return_coins = np.array(returns_df.sum().sort_values(ascending=False)[:5].index)
top_5_return_coins
df[top_5_return_coins].plot(subplots=True,title='Top 5 coins by percent return: Closing price over time in USD',figsize=(10,10));
Analyzing Closing Price in Bitcoin terms¶
All the analysis we've done up to this point has been it terms of USD, but as you may know a lot of these coin prices can look very different on a BTC price scale. Since many altcoins still follow movements in BTC relatively closely it would be interesting to do the same analysis I just did but instead of having prices in USD, we look at prices in Bitcoin value. We can do this by using the .div method and divide by the BTC column along the rows of our dataframe
df_BTC = df.div(df['BTC'].values,axis='rows')
df_BTC.head()
What we'd be interested in seeing here is if any coin's price has been rising relative to BTC price over time.
To see this let's take the top 10 coins by summed price over the last 30 days and then plot theme against Bitcoin value
top_10_by_sum_price = np.array(df_BTC.iloc[-30:,:].sum().sort_values(ascending=False)[:11].index)
top_10_by_sum_price
df_BTC[top_10_by_sum_price].plot(title='top 10 Coins: Closing Price relative to BTC price over time',figsize=(8,5));
df_BTC[top_10_by_sum_price[1:]].plot(title='top 10 Coins: Closing Price relative to BTC price over time',figsize=(8,5));
You can see at some points in time, coins like Etehereum have gained value in Bitcoin terms, but generally over time coins decrease or remain constant over time in BTC value. However the trend around April 2017 is interesting, because ETH and DASH jumped a level in BTC value and have remained relatively constant around that level over time. Time will tell if it stays that way given the increasing ease of buying other coins directly with USD
Feature Analysis with Returns in BTC¶
We're now going to do the same analysis as before, but in terms of BTC return now
returns_df_BTC = df_BTC.pct_change()
#remove first row
returns_df_BTC = returns_df_BTC.iloc[1:,:]
returns_df_BTC.head()
F1 = []
F2 = []
F3 = []
F4 = []
F5 = []
F6 = []
index_ts = []
for i in returns_df_BTC:
try:
Entropy,Trend_stength,Seasonal_strength,Period,ACF1,optim_lambda = tsfeatures(returns_df_BTC[i], freq=365)
F1.append(Entropy)
F2.append(Trend_stength)
F3.append(Seasonal_strength)
F4.append(Period)
F5.append(ACF1)
F6.append(optim_lambda)
index_ts.append(i)
except:
print('error: ',i)
#all time series have same period here, so won't use here, as will become 0 in princomp analysis
df_features_BTC = pd.DataFrame(
{'Entropy': F1,
'Trend_stength': F2,
'Seasonal_strength': F3,
#'Period': F4,
'ACF1': F5,
'optim_lambda': F6
},
index=index_ts)
df_features_BTC.head()
scatter_graph = sns.PairGrid(df_features_BTC, diag_sharey=False,size=2)
scatter_graph.map_lower(sns.kdeplot, cmap="Blues")
scatter_graph.map_upper(plt.scatter)
scatter_graph.map_diag(sns.kdeplot, shade=True,lw=3);
pca = PCA(n_components=2)
pca.fit(df_features_BTC)
df_PCA = pca.transform(df_features_BTC)
explained_ratios = pca.explained_variance_ratio_
print('PC1 variance explained: ', round(explained_ratios[0],4)*100,'%','\n',
'PC2 variance explained: ',round(explained_ratios[1],4)*100,'%')
df_plotting = df_features_BTC.copy()
df_plotting['PC1'] = df_PCA[:,0]
df_plotting['PC2'] = df_PCA[:,1]
#we are going to take the absolute value of the autocorrelation at lag 1,
#because for plotting we don't really are about the direction, only the strength
df_plotting['ACF1'] = abs(df_plotting['ACF1'].values)
df_plotting_no_index = df_plotting.reset_index()
df_plotting_no_index.rename(columns={'index':'time_series'}, inplace=True)
df_plotting_no_index.head()
scatter_dic = {}
for feature in df_features_BTC.columns:
#print(feature)
scatter_plot_x = hv.Scatter(df_plotting_no_index, 'PC1',['PC2','time_series',feature],label=feature)
scatter_plot_x = scatter_plot_x.options(width=300,height=200,tools=['hover'],color_index=feature,cmap='RdYlGn',colorbar=True,size=5,show_grid=True)
scatter_dic[feature] = scatter_plot_x
feature_scatter = (scatter_dic['ACF1'] + scatter_dic['Entropy'] + scatter_dic['Seasonal_strength'] +
scatter_dic['Trend_stength'] + scatter_dic['optim_lambda']).cols(2)
feature_scatter
print('summed inter-day percent returns for BTC: ',returns_df_BTC['BTC'].sum())
print('summed inter-day percent returns for ETH: ',returns_df_BTC['ETH'].sum())
print('summed inter-day percent returns for XRP: ',returns_df_BTC['XRP'].sum())
print('summed inter-day percent returns for SAFEX: ',returns_df_BTC['SAFEX'].sum())
print('summed inter-day percent returns for BITUSD: ',returns_df_BTC['BITUSD'].sum())
print('summed inter-day percent returns for DIME: ',returns_df_BTC['DIME'].sum())
returns_df_BTC[['DIME','BTC','ETH','XRP','SAFEX','BITUSD']].plot(subplots=True,title='Percent Returns for DIME and other cryptocurrencies',figsize=(10,12));
returns_df_BTC[['DIME','BTC','ETH','XRP','SAFEX','BITUSD']].plot(kind="hist",bins=100,subplots=True,title='Percent Returns Distribution for DIME and other cryptocurrencies',figsize=(10,12));
top_5_return_coins = np.array(returns_df_BTC.sum().sort_values(ascending=False)[:5].index)
top_5_return_coins
df_BTC[top_5_return_coins].plot(subplots=True,title='Top 5 coins by percent return: Closing price over time in BTC',figsize=(10,10));
top_5_return_coins = np.append(top_5_return_coins,'BTC')
df_BTC[top_5_return_coins].plot(title='top 5 Coins by returns: Closing Price relative to BTC price over time',figsize=(8,5));
df_BTC[top_5_return_coins[:-1]].plot(title='top 5 Coins by returns: Closing Price relative to BTC price over time',figsize=(8,5));
We see from the preceding graphs that there is not much difference between this analysis in terms of BTC instead of USD. I think the only reason PacCoin is so high is it used to be one of the lowest market cap coins which got pushed by some youtubers as something to buy for the return multiple because it was the lowest price coin at the time, which turned out to be true if you were lucky enough to buy before around Jan 2018 when it had a big spike in price.
This may be the case for many of these low market cap coins over a short time horizon where you see large returns because of pump and dumps shooting the price up, and then the residual value from those left holding the bags hoping the price will go back up, but over time like you see with PACcoin the price drops back down dramatically
Conclusion¶
In this post, I used time series feature extraction and then principal components analysis as a data exploration tool to try and find interesting cryptocurrencies to look at in terms of price or returns. We saw that there are some coins on a shorter time horizon that have seen their price grow dramatically, possibly due to pump and dump schemes, and the aftermath of the residual value that is rapidly declining.
We also saw what others have found which is that almost all cryptocurrencies do not increase in terms of BTC price over time, since BTC is still the main cryptocurrency gateway. However, there are a few cryptocurrencies which have recently become marginally more valuable in relative BTC terms. This is also reflected here at the bottom of the page which shows Total Market Cap dominance percentage of BTC. These 2 facts represent the increasing ease of getting into other coins directly with USD or other national currencies, thereby eliminating BTC as the gateway into cryptocurrencies
Lastly, we did find one anomaly that we can't fully explain: DIME This coin has a long right tail in its returns distribution. The only differentiating factor from other coins I could find was that the coin has no capped supply so it is designed to be inflationary, unlike other coins. However, it's still unclear why this affects its returns distribution, if anything you would think an inflationary coin should be worth less as continually increasing supply decreases value