Backtesting LBC Liquidation Strategies

Objective of this anlysis is to explore potential ways of liquidating a cryptocurrency called lbc given fixed daily revenues from miner(s) producing more lbc.

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 LBC pricing, from here.

Open Raw Data

p = 'backtesting-lbc-liquidation-strategies/library-credit_2016-7-7_2021-11-19.csv'
d = pd.read_csv(p,
                skiprows=1)
d.info()
d.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1961 entries, 0 to 1960
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   Date        1961 non-null   object 
 1   Open        1961 non-null   float64
 2   High        1961 non-null   float64
 3   Low         1961 non-null   float64
 4   Close       1961 non-null   float64
 5   Volume      1961 non-null   float64
 6   Market Cap  1961 non-null   float64
dtypes: float64(6), object(1)
memory usage: 107.4+ KB

Date Open High Low Close Volume Market Cap
0 Nov-18-2021 0.050198 0.051660 0.048909 0.049932 257920.5176 26056054.90
1 Nov-17-2021 0.050936 0.051249 0.048736 0.049994 304353.9689 26020284.21
2 Nov-16-2021 0.053904 0.053904 0.049713 0.050827 309789.9993 26472981.03
3 Nov-15-2021 0.056154 0.058078 0.053374 0.053904 302880.5002 28826773.23
4 Nov-14-2021 0.058781 0.060198 0.055138 0.056258 485465.8088 29879819.18

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: 1961 entries, 0 to 1960
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype         
---  ------  --------------  -----         
 0   date    1961 non-null   datetime64[ns]
 1   yy-mm   1961 non-null   object        
 2   close   1961 non-null   float64       
dtypes: datetime64[ns](1), float64(1), object(1)
memory usage: 46.1+ KB
print('\nDate Range: ' + str(d['date'].min())[:10] + ' - ' + str(d['date'].max())[:10])
d.head()
Date Range: 2016-07-07 - 2021-11-18

date yy-mm close
0 2016-07-07 16-07 0.441370
1 2016-07-08 16-07 0.193952
2 2016-07-09 16-07 0.258600
3 2016-07-10 16-07 0.242374
4 2016-07-11 16-07 0.239641

Histograms

monthly_starting_lbc = 100
daily_mined_lbc = 35
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_lbc
    sub['base_lbc'] = 0
    sub['base_lbc'] = monthly_starting_lbc / sub.loc[0]['close']
    sub['mined_lbc'] = daily_mined_lbc
    sub['cum_mined_lbc'] = sub['mined_lbc'].cumsum()
    sub = sub.drop(columns=['mined_lbc'])
    sub['total_lbc'] = sub['base_lbc'] + sub['cum_mined_lbc']
    sub['total_dollars'] = sub['total_lbc'] * sub['close']
    sub['month_end_delta'] = sub['total_dollars'] - monthly_starting_lbc
    
    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_lbc cum_mined_lbc total_lbc total_dollars month_end_delta
0 2016-08-05 0.44137 0.338385 100 226.567279 1050 1276.567279 431.971219 331.971219
1 2016-08-06 0.193952 0.322175 100 515.591487 1050 1565.591487 504.394437 404.394437
2 2016-08-07 0.2586 0.307922 100 386.697602 1050 1436.697602 442.390799 342.390799
3 2016-08-08 0.242374 0.289051 100 412.585508 1050 1462.585508 422.761804 322.761804
4 2016-08-09 0.239641 0.284821 100 417.290864 1050 1467.290864 417.915251 317.915251
plt.figure(figsize=(16,9))
bins=np.arange(-1000,4000,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,800])
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_lbc))
plt.title('''30-Day LBC Income Assuming Each 30-Day Period Starts with LBC Balance worth \${}
Daily LBC Income of {:2.2f} LBC, Backtesting 30-Day Periods over 2016-07-07 - 2021-11-18
{}% ({}/{}) / {}% ({}/{}) Periods with no Income / \$1K+ Income
25th, 50th, 75th Percentile End-of-Period Income = \${}, \${}, \${}'''
              .format(monthly_starting_lbc, daily_mined_lbc,
                      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_lbc in np.arange(100,600,100):
    daily_mined_lbc = 35
    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_lbc
        sub['base_lbc'] = 0
        sub['base_lbc'] = monthly_starting_lbc / sub.loc[0]['close']
        sub['mined_lbc'] = daily_mined_lbc
        sub['cum_mined_lbc'] = sub['mined_lbc'].cumsum()
        sub = sub.drop(columns=['mined_lbc'])
        sub['total_lbc'] = sub['base_lbc'] + sub['cum_mined_lbc']
        sub['total_dollars'] = sub['total_lbc'] * sub['close']
        sub['month_end_delta'] = sub['total_dollars'] - monthly_starting_lbc

        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,4000,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,800])
    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_lbc))
    plt.title('''30-Day lbc Income Assuming Each 30-Day Period Starts with lbc Balance worth \${}
    Daily lbc Income of {:2.2f} lbc, 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_lbc, daily_mined_lbc,
                          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-lbc-liquidation-strategies/figures/final_hists_'+str(monthly_starting_lbc)+'.pdf',
                dpi=450)

png

png

png

png

png


Line Graphs

Math on Purchases

monthly_starting_lbc = 100
daily_mined_lbc = 35

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_lbc'] = monthly_starting_lbc / sub['close']
    sub['daily_lbc'] = daily_mined_lbc + sub['start_lbc'].fillna(0)
    sub['cum_lbc'] = sub['daily_lbc'].cumsum()
    sub['start_dollars'] = sub['start_lbc'].fillna(method='ffill') * sub['close']
    sub['cum_dollars'] = sub['cum_lbc'] * sub['close']
    
    sub.loc[sub['start/end']=='End', 'month_end_delta'] = monthly_starting_lbc
    sub['month_end_delta'] = sub['cum_dollars'] - sub['month_end_delta']
    
    stack = (stack.append(sub).reset_index(drop=True))
stack.head(32)

date yy-mm close start/end start_lbc daily_lbc cum_lbc start_dollars cum_dollars month_end_delta
0 2016-07-07 16-07 0.441370 NaN NaN 35.000000 35.000000 NaN 15.447950 NaN
1 2016-07-08 16-07 0.193952 NaN NaN 35.000000 70.000000 NaN 13.576640 NaN
2 2016-07-09 16-07 0.258600 NaN NaN 35.000000 105.000000 NaN 27.153000 NaN
3 2016-07-10 16-07 0.242374 NaN NaN 35.000000 140.000000 NaN 33.932360 NaN
4 2016-07-11 16-07 0.239641 NaN NaN 35.000000 175.000000 NaN 41.937175 NaN
5 2016-07-12 16-07 0.598059 NaN NaN 35.000000 210.000000 NaN 125.592390 NaN
6 2016-07-13 16-07 1.311340 NaN NaN 35.000000 245.000000 NaN 321.278300 NaN
7 2016-07-14 16-07 1.546460 NaN NaN 35.000000 280.000000 NaN 433.008800 NaN
8 2016-07-15 16-07 1.194920 NaN NaN 35.000000 315.000000 NaN 376.399800 NaN
9 2016-07-16 16-07 0.911908 NaN NaN 35.000000 350.000000 NaN 319.167800 NaN
10 2016-07-17 16-07 0.809600 NaN NaN 35.000000 385.000000 NaN 311.696000 NaN
11 2016-07-18 16-07 0.649293 NaN NaN 35.000000 420.000000 NaN 272.703060 NaN
12 2016-07-19 16-07 0.810544 NaN NaN 35.000000 455.000000 NaN 368.797520 NaN
13 2016-07-20 16-07 0.706712 NaN NaN 35.000000 490.000000 NaN 346.288880 NaN
14 2016-07-21 16-07 0.635370 NaN NaN 35.000000 525.000000 NaN 333.569250 NaN
15 2016-07-22 16-07 0.586170 NaN NaN 35.000000 560.000000 NaN 328.255200 NaN
16 2016-07-23 16-07 0.558915 NaN NaN 35.000000 595.000000 NaN 332.554425 NaN
17 2016-07-24 16-07 0.484527 NaN NaN 35.000000 630.000000 NaN 305.252010 NaN
18 2016-07-25 16-07 0.452199 NaN NaN 35.000000 665.000000 NaN 300.712335 NaN
19 2016-07-26 16-07 0.365556 NaN NaN 35.000000 700.000000 NaN 255.889200 NaN
20 2016-07-27 16-07 0.602031 NaN NaN 35.000000 735.000000 NaN 442.492785 NaN
21 2016-07-28 16-07 0.490585 NaN NaN 35.000000 770.000000 NaN 377.750450 NaN
22 2016-07-29 16-07 0.441357 NaN NaN 35.000000 805.000000 NaN 355.292385 NaN
23 2016-07-30 16-07 0.411769 NaN NaN 35.000000 840.000000 NaN 345.885960 NaN
24 2016-07-31 16-07 0.391433 End NaN 35.000000 875.000000 NaN 342.503875 242.503875
25 2016-08-01 16-08 0.373216 Start 267.941353 302.941353 302.941353 100.000000 113.062560 NaN
26 2016-08-02 16-08 0.286560 NaN NaN 35.000000 337.941353 76.781274 96.840474 NaN
27 2016-08-03 16-08 0.322015 NaN NaN 35.000000 372.941353 86.281135 120.092710 NaN
28 2016-08-04 16-08 0.333293 NaN NaN 35.000000 407.941353 89.302977 135.963997 NaN
29 2016-08-05 16-08 0.338385 NaN NaN 35.000000 442.941353 90.667335 149.884710 NaN
30 2016-08-06 16-08 0.322175 NaN NaN 35.000000 477.941353 86.324005 153.980755 NaN
31 2016-08-07 16-08 0.307922 NaN NaN 35.000000 512.941353 82.505037 157.945927 NaN

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_lbc'] * stack['c_low_price']
stack['c_high_usd'] = stack['cum_lbc'] * 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) lbc (Dollars)')
lns2 = ax1.plot(sub['date'], sub['start_lbc'].fillna(method='ffill')*sub['close'],
                label='Value of Starting lbc (Dollars)')

ax1.set_title('Daily Dollar Value of Purchases and Retained lbc')
ax1.set_xlabel('Date')
ax1.set_ylabel('Dollars')
ax1.grid()
ax1.set_ylim([-100,3000])

ax2 = ax1.twinx()
lns3 = ax2.bar(sub['date'], sub['close'], color='C2', alpha=0.125,
               label='Daily LBC Price')
ax2.set_ylim(bottom = ax1.get_ylim()[0]*0.001,
             top = ax1.get_ylim()[1]*0.001)
ax2.set_ylabel('Daily LBC 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) lbc (Dollars)')
    lns2 = ax1.plot(sub['date'], sub['start_lbc'].fillna(method='ffill')*sub['close'],
                    label='Value of Starting lbc (Dollars)')

    ax1.set_title('''Daily Dollar Value of Purchases and Retained lbc During {}
Assuming Each Month Starts with lbc Balance worth \${}
Daily lbc Income of {:2.2f} lbc
Starting / Ending lbc Price \${:1.3f} / \${:1.3f}'''.format(yymm,
                                                            monthly_starting_lbc,
                                                            daily_mined_lbc,
                                                            sub.iloc[0]['close'],
                                                            sub.iloc[-1]['close']))
    
    ax1.set_xlabel('Date')
    ax1.set_ylabel('Dollars')
    ax1.grid()
    ax1.set_ylim([-100,1500])

    ax2 = ax1.twinx()
    lns3 = ax2.bar(sub['date'], sub['close'], color='C2', alpha=0.125,
                   label='Daily LBC Price')
    ax2.set_ylim(bottom = ax1.get_ylim()[0]*0.001,
                 top = ax1.get_ylim()[1]*0.001)
    ax2.set_ylabel('Daily LBC 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-lbc-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

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png

png