ビットコイン・バブル・インデックス

# 罪滅ぼし
Bitcoin Bubble IndexがツイッターのAPIの変更のせいで使えなくなってるにも関わらずそれに気づかず延々とこのインジケーターを推してしまっていたので大反省してちゃんと使えるインジケーターに自分で修正しました。

https://x.com/daniel_tanaka_/status/1792854806404497417

# 元データ

Chao Maが作ったインジケータでGitHubにて公開されています。今回これを参考にコードを準備しました。

https://github.com/aksnzhy/bitcoin-bubble-index


# バブル・インデックスのコード

データを取得する部分はオリジナルコードとGoogleTrendsを利用しました。

```bash
import requests
from bs4 import BeautifulSoup

url_price = 'https://bitinfocharts.com/comparison/bitcoin-price.html'
url_sentaddr = 'https://bitinfocharts.com/comparison/activeaddresses-btc.html'
url_transaction = 'https://bitinfocharts.com/comparison/bitcoin-transactions.html'
url_difficulty = 'https://bitinfocharts.com/comparison/bitcoin-difficulty.html'

def download(url, var):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    scripts = soup.find_all("script")
    for script in scripts:
        if 'd = new Dygraph(document.getElementById("container")' in str(script.string):
            StrList = str(script.string)
            StrList = '[[' + StrList.split('[[')[-1]
            StrList = StrList.split(']]')[0] + ']]'
    with open(var + '.txt', 'w') as f:
        f.write(StrList)

download(url_price, 'price')
download(url_sentaddr, 'sentaddr')
download(url_transaction, 'transaction')
download(url_difficulty, 'difficulty')
```

https://trends.google.com/trends/explore?date=all&q=bitcoin&hl=en


インジケータデータ生成を生成するコードです。
```python
import collections
import datetime
import json
import math
import pandas as pd
import re

def parse_strlist(sl):
    clean = re.sub(r"[\[\],\s]", "", sl)
    splitted = re.split(r"[\'\"]", clean)
    values_only = [s for s in splitted if s != '']
    return values_only

def read_datafile(filename):
    result = []

    file = open(filename, "r")
    txt_data = file.read()
    txt_data = txt_data[1:][:-1]
    key = ''
    value = ''
    data = txt_data.split(',')
    for i in range(len(data)):
        if i % 2 == 0:
            key = data[i].replace('[new Date("', '').replace('")', '')
            date = datetime.datetime.strptime(key, '%Y/%m/%d')
            if date < datetime.datetime(2010, 7, 17):
                continue
        else:
            if date < datetime.datetime(2010, 7, 17):
                continue
            value = data[i].replace(']', '')
            result.append(key + ":" + value)

    return result

def add_missing_data(start_date, end_date, init_value, scale_factor):
    result = []
    index = 0
    while True:
        date = get_day(start_date, index)
        if date == end_date:
            break
        init_value += init_value * scale_factor
        result.append(date + ':' + str(init_value))
        index += 1

    return result

def get_day(start_date, gap_day):
    tmp = datetime.datetime.strptime(start_date, '%Y/%m/%d')
    target_date = (tmp + datetime.timedelta(days=gap_day)).strftime('%Y/%m/%d')
    return target_date

def get_bubble_index(price, growth_60_day, hot_keywords, difficulty_value, sentaddr_value, transaction_value):
    bubble_index_0 = 5000 * price / (sentaddr_value + difficulty_value / 10000000 + transaction_value)
    bubble_index_1 = growth_60_day * math.pi + hot_keywords / math.pi
    return bubble_index_0 + bubble_index_1 / 10.0 - 30.0

def process_data_with_google_trends():
    google_trends_data = pd.read_csv('mutiltimeline.csv', skiprows=1)
    google_trends_data['Month'] = pd.to_datetime(google_trends_data['Month'])
    google_trends_data.set_index('Month', inplace=True)

    price = read_datafile('price.txt')
    difficulty = read_datafile('difficulty.txt')
    sentaddr = read_datafile('sentaddr.txt')
    transaction = read_datafile('transaction.txt')

    json_data = {}
    json_data['date'] = []
    json_data['price'] = []
    json_data['growth_60_day'] = []
    json_data['hot'] = []
    json_data['bubble'] = []

    last_price = 0.0
    accumulate_60 = 0.0
    my_q = collections.deque()
    for data in price:
        key, value = data.split(':')
        json_data['date'].append(key)
        json_data['price'].append('%0.2f' % float(value))
        if last_price == 0.0:
            last_price = float(value)
        if len(my_q) >= 60:
            accumulate_60 -= my_q.popleft()
        day_growth = (float(value) - last_price) / last_price
        accumulate_60 += day_growth
        my_q.append(day_growth)
        last_price = float(value)
        json_data['growth_60_day'].append(int(accumulate_60 * 100))

    for date in json_data['date']:
        try:
            trend_value = google_trends_data.loc[pd.to_datetime(date), 'bitcoin: (Worldwide)']
            trend_value = 0 if trend_value == '<1' else int(trend_value)
        except KeyError:
            trend_value = 0 
        json_data['hot'].append(trend_value)

    while len(json_data['hot']) < len(json_data['price']):
        json_data['hot'].append(json_data['hot'][-1])
    assert len(json_data['hot']) == len(json_data['price'])

    def extend_data(data, json_data, data_name):
        while len(data) < len(json_data['price']):
            data_value = data[-1].split(':')[1]
            data.append(json_data['date'][len(data)] + ':' + data_value)
        return data

    difficulty = extend_data(difficulty, json_data, 'difficulty')
    sentaddr = extend_data(sentaddr, json_data, 'sentaddr')
    transaction = extend_data(transaction, json_data, 'transaction')

    for i in range(len(price)):
        difficulty_key, difficulty_value = difficulty[i].split(':')
        assert difficulty_key == json_data['date'][i]
        
        sentaddr_key, sentaddr_value = sentaddr[i].split(':')
        assert sentaddr_key == json_data['date'][i]
        
        transaction_key, transaction_value = transaction[i].split(':')
        assert transaction_key == json_data['date'][i]

        difficulty_value = 0.0 if difficulty_value == 'null' else float(difficulty_value)
        sentaddr_value = 0.0 if sentaddr_value == 'null' else float(sentaddr_value)
        transaction_value = 0.0 if transaction_value == 'null' else float(transaction_value)

        bubble_index = get_bubble_index(
            float(json_data['price'][i]),
            float(json_data['growth_60_day'][i]),
            float(json_data['hot'][i]),
            float(difficulty_value),
            float(sentaddr_value),
            float(transaction_value))

        json_data['bubble'].append(int(bubble_index))

    assert len(json_data['bubble']) == len(json_data['price'])

    with open('../data.json', "w") as file:
        file.write("data = '")
        file.write(json.dumps(json_data))
        file.write("'")

if __name__ == '__main__':
    process_data_with_google_trends()
```

データを元にチャート生成するコードです。
```python
import json
import matplotlib.pyplot as plt

with open('../data.json', 'r') as file:
    data = file.read()
    json_data = json.loads(data.lstrip("data = '").rstrip("'"))

dates = json_data['date']
bubble_indices = json_data['bubble']

plt.plot(dates, bubble_indices)
plt.xlabel('Date')
plt.ylabel('Bubble Index')
plt.title('Bitcoin Bubble Index Over Time')
plt.xticks(dates[::len(dates)//10], rotation=45)  
plt.tight_layout()
plt.show()
```

# 実行結果
実行結果を見る限り、まだまだバブル水準とはいえませんね。やったね。

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670541/1b4d16d6-385f-49cc-3cd2-e74407809736.png)


# 改善版
ビットコイン価格と同時表記の方がいいと思って改良しました。

```python
import json
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.ticker import MaxNLocator
from datetime import datetime

with open('../data.json', 'r') as file:
    data = file.read()
    json_data = json.loads(data.lstrip("data = '").rstrip("'"))
    
dates = json_data['date']
bubble_indices = json_data['bubble']
prices = json_data['price']
dates = [datetime.strptime(date, "%Y/%m/%d") for date in dates]
prices = [float(price) for price in prices]

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True)

ax1.set_ylabel('Bitcoin Price (USD)', color='tab:blue')
ax1.plot(dates, prices, color='tab:blue', label='Bitcoin Price', linewidth=1.5)
ax1.tick_params(axis='y', labelcolor='tab:blue')
ax1.set_title('Bitcoin Price')

ax2.set_xlabel('Date')
ax2.set_ylabel('Bubble Index', color='tab:red')
ax2.plot(dates, bubble_indices, color='tab:red', label='Bubble Index', linewidth=1.5, linestyle='--')
ax2.tick_params(axis='y', labelcolor='tab:red')
ax2.set_title('Bubble Index')
ax2.xaxis.set_major_locator(MaxNLocator(nbins=10))
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))

fig.autofmt_xdate(rotation=45)

ax1.grid(True)
ax2.grid(True)
ax1.legend(loc='upper left')
ax2.legend(loc='upper left')

fig.tight_layout()

plt.show()
```

![BBI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670541/10e4ec8f-f302-9acc-4627-eced582402b4.png)