Backtesting HNS Liquidation Strategies

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')

png

Parametrize and Run with Various Inputs

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)

png

png

png

png

png

png

png

png

png

png


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);

png

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)

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png