Objective of this anlysis is to explore potential ways of liquidating a cryptocurrency called HNS given fixed daily revenues from miner(s) producing more HNS.
import datetime as dt
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams.update({'figure.max_open_warning': 0})
The data is taken from a coincodex.com export of HNS pricing, from here.
Open Raw Data
p = 'backtesting-hns-liquidation-strategies/handshake-token_2020-2-18_2021-11-19.csv'
d = pd.read_csv(p,
skiprows=1)
d.info()
d.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 639 entries, 0 to 638
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 639 non-null object
1 Open 639 non-null float64
2 High 639 non-null float64
3 Low 639 non-null float64
4 Close 639 non-null float64
5 Volume 639 non-null float64
6 Market Cap 639 non-null int64
dtypes: float64(5), int64(1), object(1)
memory usage: 35.1+ KB
|
Date |
Open |
High |
Low |
Close |
Volume |
Market Cap |
0 |
Nov-18-2021 |
0.355220 |
0.359273 |
0.319747 |
0.319747 |
9.536470e+05 |
0 |
1 |
Nov-17-2021 |
0.377583 |
0.380356 |
0.351427 |
0.356039 |
1.113745e+06 |
0 |
2 |
Nov-16-2021 |
0.418333 |
0.420345 |
0.357378 |
0.377583 |
1.922706e+06 |
0 |
3 |
Nov-15-2021 |
0.400493 |
0.435025 |
0.385847 |
0.418333 |
1.434466e+06 |
0 |
4 |
Nov-14-2021 |
0.408115 |
0.416591 |
0.397732 |
0.400493 |
1.063827e+06 |
0 |
Basic Cleansing
d.columns = d.columns.str.lower()
d['date'] = pd.to_datetime(d['date'])
d['yy-mm'] = (d['date'].dt.year.astype(str).str[2:]
+ '-' +
d['date'].dt.month.astype(str).str.zfill(2))
d = (d[['date','yy-mm','close']]
.sort_values(['date'])
.reset_index(drop=True))
d.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 639 entries, 0 to 638
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 639 non-null datetime64[ns]
1 yy-mm 639 non-null object
2 close 639 non-null float64
dtypes: datetime64[ns](1), float64(1), object(1)
memory usage: 15.1+ KB
print('\nDate Range: ' + str(d['date'].min())[:10] + ' - ' + str(d['date'].max())[:10])
d.head()
Date Range: 2020-02-19 - 2021-11-18
|
date |
yy-mm |
close |
0 |
2020-02-19 |
20-02 |
0.171375 |
1 |
2020-02-20 |
20-02 |
0.101944 |
2 |
2020-02-21 |
20-02 |
0.102463 |
3 |
2020-02-22 |
20-02 |
0.082904 |
4 |
2020-02-23 |
20-02 |
0.092982 |
Histograms
monthly_starting_hns = 1000
daily_mined_hns = 7
c = d.drop(columns=['yy-mm'])
summary = pd.DataFrame()
for start_row in np.arange(d.shape[0]-30):
sub = c[start_row:start_row + 30].reset_index(drop=True)
sub['open'] = sub.loc[0]['close']
sub = sub[['date','open','close']].copy()
sub['base_dollars'] = monthly_starting_hns
sub['base_hns'] = 0
sub['base_hns'] = monthly_starting_hns / sub.loc[0]['close']
sub['mined_hns'] = daily_mined_hns
sub['cum_mined_hns'] = sub['mined_hns'].cumsum()
sub = sub.drop(columns=['mined_hns'])
sub['total_hns'] = sub['base_hns'] + sub['cum_mined_hns']
sub['total_dollars'] = sub['total_hns'] * sub['close']
sub['month_end_delta'] = sub['total_dollars'] - monthly_starting_hns
new_summary_row = pd.DataFrame(sub.loc[29]).T
summary = summary.append(new_summary_row)
summary = summary.reset_index(drop=True)
summary.head()
|
date |
open |
close |
base_dollars |
base_hns |
cum_mined_hns |
total_hns |
total_dollars |
month_end_delta |
0 |
2020-03-19 |
0.171375 |
0.177929 |
1000 |
5835.169384 |
210 |
6045.169384 |
1075.612237 |
75.612237 |
1 |
2020-03-20 |
0.101944 |
0.173113 |
1000 |
9809.27551 |
210 |
10019.27551 |
1734.469577 |
734.469577 |
2 |
2020-03-21 |
0.102463 |
0.162866 |
1000 |
9759.587399 |
210 |
9969.587399 |
1623.706373 |
623.706373 |
3 |
2020-03-22 |
0.082904 |
0.14609 |
1000 |
12062.135728 |
210 |
12272.135728 |
1792.831559 |
792.831559 |
4 |
2020-03-23 |
0.092982 |
0.150299 |
1000 |
10754.790676 |
210 |
10964.790676 |
1647.992907 |
647.992907 |
plt.figure(figsize=(16,9))
bins=np.arange(-1000,2000,100)
plt.hist(summary['month_end_delta'],
bins=bins,
alpha=0.8)
plt.axvline(summary['month_end_delta'].quantile(.25), c='C1', alpha=0.8, zorder=0)
plt.axvline(summary['month_end_delta'].quantile(.50), c='C1', alpha=0.8, zorder=0)
plt.axvline(summary['month_end_delta'].quantile(.75), c='C1', alpha=0.8, zorder=0)
plt.gca().set_ylim([0,250])
plt.grid()
for spine in plt.gca().spines.values():
spine.set_visible(False)
plt.gca().tick_params(
axis='x',
bottom=False)
plt.gca().tick_params(
axis='y',
left=False,
right=False)
plt.ylabel('# 30-Day Periods')
plt.xlabel('30-Day Income (Delta From 30-Day Starting Balance: ${})'.format(monthly_starting_hns))
plt.title('''30-Day HNS Income Assuming Each 30-Day Period Starts with HNS Balance worth \${}
Daily HNS Income of {:2.2f} HNS, Backtesting 30-Day Periods over 2020-02-19 - 2021-11-18
{}% ({}/{}) / {}% ({}/{}) Periods with no Income / \$1K+ Income
25th, 50th, 75th Percentile End-of-Period Income = \${}, \${}, \${}'''
.format(monthly_starting_hns, daily_mined_hns,
int(summary[summary['month_end_delta']<0].shape[0]/summary.shape[0]*100.),
summary[summary['month_end_delta']<0].shape[0], summary.shape[0],
int(summary[summary['month_end_delta']>=1000].shape[0]/summary.shape[0]*100.),
summary[summary['month_end_delta']>=1000].shape[0], summary.shape[0],
int(round(summary['month_end_delta'].quantile(.25))),
int(round(summary['month_end_delta'].quantile(.50))),
int(round(summary['month_end_delta'].quantile(.75)))))
rects = plt.gca().patches
for rect in rects:
if rect.get_x() < 0:
rect.set_color('C3')
if rect.get_x() >= 0:
rect.set_color('C0')

for monthly_starting_hns in np.arange(100,1100,100):
daily_mined_hns = 7
summary = pd.DataFrame()
for start_row in np.arange(d.shape[0]-30):
sub = c[start_row:start_row + 30].reset_index(drop=True)
sub['open'] = sub.loc[0]['close']
sub = sub[['date','open','close']].copy()
sub['base_dollars'] = monthly_starting_hns
sub['base_hns'] = 0
sub['base_hns'] = monthly_starting_hns / sub.loc[0]['close']
sub['mined_hns'] = daily_mined_hns
sub['cum_mined_hns'] = sub['mined_hns'].cumsum()
sub = sub.drop(columns=['mined_hns'])
sub['total_hns'] = sub['base_hns'] + sub['cum_mined_hns']
sub['total_dollars'] = sub['total_hns'] * sub['close']
sub['month_end_delta'] = sub['total_dollars'] - monthly_starting_hns
new_summary_row = pd.DataFrame(sub.loc[29]).T
summary = summary.append(new_summary_row)
summary = summary.reset_index(drop=True)
plt.figure(figsize=(16,9))
bins=np.arange(-1000,2000,100)
plt.hist(summary['month_end_delta'],
bins=bins,
alpha=0.8)
plt.axvline(summary['month_end_delta'].quantile(.25), c='C1', alpha=0.8, zorder=0)
plt.axvline(summary['month_end_delta'].quantile(.50), c='C1', alpha=0.8, zorder=0)
plt.axvline(summary['month_end_delta'].quantile(.75), c='C1', alpha=0.8, zorder=0)
plt.gca().set_ylim([0,250])
plt.grid()
for spine in plt.gca().spines.values():
spine.set_visible(False)
plt.gca().tick_params(
axis='x',
bottom=False)
plt.gca().tick_params(
axis='y',
left=False,
right=False)
plt.ylabel('# 30-Day Periods')
plt.xlabel('30-Day Income (Delta From 30-Day Starting Balance: ${})'.format(monthly_starting_hns))
plt.title('''30-Day HNS Income Assuming Each 30-Day Period Starts with HNS Balance worth \${}
Daily HNS Income of {:2.2f} HNS, Backtesting 30-Day Periods over 2020-02-19 - 2021-11-18
{}% ({}/{}) / {}% ({}/{}) Periods with no Income / \$1K+ Income
25th, 50th, 75th Percentile End-of-Period Income = \${}, \${}, \${}'''
.format(monthly_starting_hns, daily_mined_hns,
int(summary[summary['month_end_delta']<0].shape[0]/summary.shape[0]*100.),
summary[summary['month_end_delta']<0].shape[0], summary.shape[0],
int(summary[summary['month_end_delta']>=1000].shape[0]/summary.shape[0]*100.),
summary[summary['month_end_delta']>=1000].shape[0], summary.shape[0],
int(round(summary['month_end_delta'].quantile(.25))),
int(round(summary['month_end_delta'].quantile(.50))),
int(round(summary['month_end_delta'].quantile(.75)))))
rects = plt.gca().patches
for rect in rects:
if rect.get_x() < 0:
rect.set_color('C3')
if rect.get_x() >= 0:
rect.set_color('C0')
plt.savefig('backtesting-hns-liquidation-strategies/figures/final_hists_'+str(monthly_starting_hns)+'.pdf',
dpi=450)










Line Graphs
Math on Purchases
monthly_starting_hns = 500
daily_mined_hns = 7
stack = pd.DataFrame()
for yymm in d['yy-mm'].unique():
sub = d[d['yy-mm']==yymm].reset_index(drop=True)
sub.loc[sub['date'].dt.day==1, 'start/end'] = 'Start'
sub.loc[sub.shape[0]-1, 'start/end'] = 'End'
sub.loc[sub['start/end']=='Start', 'start_hns'] = monthly_starting_hns / sub['close']
sub['daily_hns'] = daily_mined_hns + sub['start_hns'].fillna(0)
sub['cum_hns'] = sub['daily_hns'].cumsum()
sub['start_dollars'] = sub['start_hns'].fillna(method='ffill') * sub['close']
sub['cum_dollars'] = sub['cum_hns'] * sub['close']
sub.loc[sub['start/end']=='End', 'month_end_delta'] = monthly_starting_hns
sub['month_end_delta'] = sub['cum_dollars'] - sub['month_end_delta']
stack = (stack.append(sub).reset_index(drop=True))
stack.tail(32)
|
date |
yy-mm |
close |
start/end |
start_hns |
daily_hns |
cum_hns |
start_dollars |
cum_dollars |
month_end_delta |
607 |
2021-10-18 |
21-10 |
0.249315 |
NaN |
NaN |
7.000000 |
2702.051042 |
642.247609 |
673.661272 |
NaN |
608 |
2021-10-19 |
21-10 |
0.226768 |
NaN |
NaN |
7.000000 |
2709.051042 |
584.166378 |
614.326544 |
NaN |
609 |
2021-10-20 |
21-10 |
0.224618 |
NaN |
NaN |
7.000000 |
2716.051042 |
578.628402 |
610.074974 |
NaN |
610 |
2021-10-21 |
21-10 |
0.216088 |
NaN |
NaN |
7.000000 |
2723.051042 |
556.653159 |
588.418063 |
NaN |
611 |
2021-10-22 |
21-10 |
0.218547 |
NaN |
NaN |
7.000000 |
2730.051042 |
562.987333 |
596.643518 |
NaN |
612 |
2021-10-23 |
21-10 |
0.219946 |
NaN |
NaN |
7.000000 |
2737.051042 |
566.592398 |
602.003721 |
NaN |
613 |
2021-10-24 |
21-10 |
0.220292 |
NaN |
NaN |
7.000000 |
2744.051042 |
567.483220 |
604.492262 |
NaN |
614 |
2021-10-25 |
21-10 |
0.296563 |
NaN |
NaN |
7.000000 |
2751.051042 |
763.962468 |
815.861064 |
NaN |
615 |
2021-10-26 |
21-10 |
0.314082 |
NaN |
NaN |
7.000000 |
2758.051042 |
809.090820 |
866.253713 |
NaN |
616 |
2021-10-27 |
21-10 |
0.276268 |
NaN |
NaN |
7.000000 |
2765.051042 |
711.681108 |
763.895807 |
NaN |
617 |
2021-10-28 |
21-10 |
0.294556 |
NaN |
NaN |
7.000000 |
2772.051042 |
758.790330 |
816.523233 |
NaN |
618 |
2021-10-29 |
21-10 |
0.281368 |
NaN |
NaN |
7.000000 |
2779.051042 |
724.819450 |
781.937242 |
NaN |
619 |
2021-10-30 |
21-10 |
0.267215 |
NaN |
NaN |
7.000000 |
2786.051042 |
688.358977 |
744.474086 |
NaN |
620 |
2021-10-31 |
21-10 |
0.334605 |
End |
NaN |
7.000000 |
2793.051042 |
861.960381 |
934.569735 |
434.569735 |
621 |
2021-11-01 |
21-11 |
0.314541 |
Start |
1589.618383 |
1596.618383 |
1596.618383 |
500.000000 |
502.201786 |
NaN |
622 |
2021-11-02 |
21-11 |
0.345178 |
NaN |
NaN |
7.000000 |
1603.618383 |
548.701968 |
553.534466 |
NaN |
623 |
2021-11-03 |
21-11 |
0.379187 |
NaN |
NaN |
7.000000 |
1610.618383 |
602.763173 |
610.726107 |
NaN |
624 |
2021-11-04 |
21-11 |
0.463349 |
NaN |
NaN |
7.000000 |
1617.618383 |
736.547715 |
749.521480 |
NaN |
625 |
2021-11-05 |
21-11 |
0.424900 |
NaN |
NaN |
7.000000 |
1624.618383 |
675.428091 |
690.299574 |
NaN |
626 |
2021-11-06 |
21-11 |
0.442935 |
NaN |
NaN |
7.000000 |
1631.618383 |
704.097615 |
722.700885 |
NaN |
627 |
2021-11-07 |
21-11 |
0.448025 |
NaN |
NaN |
7.000000 |
1638.618383 |
712.188347 |
734.141559 |
NaN |
628 |
2021-11-08 |
21-11 |
0.498679 |
NaN |
NaN |
7.000000 |
1645.618383 |
792.709628 |
820.635664 |
NaN |
629 |
2021-11-09 |
21-11 |
0.441374 |
NaN |
NaN |
7.000000 |
1652.618383 |
701.616183 |
729.422743 |
NaN |
630 |
2021-11-10 |
21-11 |
0.367072 |
NaN |
NaN |
7.000000 |
1659.618383 |
583.504184 |
609.199215 |
NaN |
631 |
2021-11-11 |
21-11 |
0.362876 |
NaN |
NaN |
7.000000 |
1666.618383 |
576.833580 |
604.774994 |
NaN |
632 |
2021-11-12 |
21-11 |
0.380015 |
NaN |
NaN |
7.000000 |
1673.618383 |
604.079496 |
636.000791 |
NaN |
633 |
2021-11-13 |
21-11 |
0.407216 |
NaN |
NaN |
7.000000 |
1680.618383 |
647.318710 |
684.375405 |
NaN |
634 |
2021-11-14 |
21-11 |
0.400493 |
NaN |
NaN |
7.000000 |
1687.618383 |
636.631695 |
675.880049 |
NaN |
635 |
2021-11-15 |
21-11 |
0.418333 |
NaN |
NaN |
7.000000 |
1694.618383 |
664.990259 |
708.915253 |
NaN |
636 |
2021-11-16 |
21-11 |
0.377583 |
NaN |
NaN |
7.000000 |
1701.618383 |
600.213623 |
642.502972 |
NaN |
637 |
2021-11-17 |
21-11 |
0.356039 |
NaN |
NaN |
7.000000 |
1708.618383 |
565.965891 |
608.334514 |
NaN |
638 |
2021-11-18 |
21-11 |
0.319747 |
End |
NaN |
7.000000 |
1715.618383 |
508.274935 |
548.562996 |
48.562996 |
Add Data for Price Bands
stack['c_low_price'] = stack['close'].rolling(30).min()
stack['c_high_price'] = stack['close'].rolling(30).max()
stack['c_low_usd'] = stack['cum_hns'] * stack['c_low_price']
stack['c_high_usd'] = stack['cum_hns'] * stack['c_high_price']
sub = stack[stack['yy-mm']=='20-03'].reset_index(drop=True)
plt.figure(figsize=(16,9))
ax1 = plt.gca()
lns1 = ax1.plot(sub['date'], sub['cum_dollars'],
label='Value of Total (Starting + Daily Purchased) hns (Dollars)')
lns2 = ax1.plot(sub['date'], sub['start_hns'].fillna(method='ffill')*sub['close'],
label='Value of Starting hns (Dollars)')
ax1.set_title('Daily Dollar Value of Purchases and Retained hns')
ax1.set_xlabel('Date')
ax1.set_ylabel('Accumulated Dollar Value')
ax1.grid()
ax1.set_ylim([-100,2000])
ax2 = ax1.twinx()
lns3 = ax2.bar(sub['date'], sub['close'], color='C2', alpha=0.125,
label='Daily HNS Price')
ax2.set_ylim(bottom = ax1.get_ylim()[0]*0.001,
top = ax1.get_ylim()[1]*0.001)
ax2.set_ylabel('Daily HNS Price')
lns4 = ax1.fill_between(sub['date'], sub['c_low_usd'], sub['c_high_usd'], alpha=0.1,
label='30-Day High/Low Price Band')
for ax in [ax1, ax2]:
for spine in ax.spines.values():
spine.set_visible(False);
ax.tick_params(
axis='y',
left=False,
right=False)
ax.tick_params(
axis='x',
bottom=False)
lines = lns1+lns2+[lns3]+[lns4]
labels = [l.get_label() for l in lines]
ax.legend(lines, labels, loc=2);

Parametrize and Run Over All Months in Dataset
for yymm in stack['yy-mm'].unique()[1:]:
sub = stack[stack['yy-mm']==yymm].reset_index(drop=True)
plt.figure(figsize=(16,9))
ax1 = plt.gca()
lns1 = ax1.plot(sub['date'], sub['cum_dollars'],
label='Value of Total (Starting + Daily Purchased) HNS (Dollars)')
lns2 = ax1.plot(sub['date'], sub['start_hns'].fillna(method='ffill')*sub['close'],
label='Value of Starting HNS (Dollars)')
ax1.set_title('''Daily Dollar Value of Purchases and Retained HNS During {}
Assuming Each Month Starts with hns Balance worth \${}
Daily HNS Income of {:2.2f} HNS
Starting / Ending HNS Price \${:1.3f} / \${:1.3f}'''.format(yymm,
monthly_starting_hns,
daily_mined_hns,
sub.iloc[0]['close'],
sub.iloc[-1]['close']))
ax1.set_xlabel('Date')
ax1.set_ylabel('Accumulated Dollar Value')
ax1.grid()
ax1.set_ylim([-100,1800])
ax2 = ax1.twinx()
lns3 = ax2.bar(sub['date'], sub['close'], color='C2', alpha=0.125,
label='Daily HNS Price')
ax2.set_ylim(bottom = ax1.get_ylim()[0]*0.001,
top = ax1.get_ylim()[1]*0.001)
ax2.set_ylabel('Daily HNS Price')
lns4 = ax1.fill_between(sub['date'], sub['c_low_usd'], sub['c_high_usd'], alpha=0.1,
label='30-Day High/Low Price Band')
for ax in [ax1, ax2]:
for spine in ax.spines.values():
spine.set_visible(False);
ax.tick_params(
axis='y',
left=False,
right=False)
ax.tick_params(
axis='x',
bottom=False)
lines = lns1+lns2+[lns3]+[lns4]
labels = [l.get_label() for l in lines]
ax.legend(lines, labels, loc=2);
plt.savefig('backtesting-hns-liquidation-strategies/figures/lines_' + yymm + '.pdf',
dpi=450)




















