📌

Crypto Fear & Greed Indexと戯れる

2022/11/03に公開

きっかけ

https://twitter.com/qash_tit/status/1587482722158907392

を引用したPOTATO教授のツイートで、リアルタイムでCrypto Fear & Greed Indexが取れないので特徴量としての利用を諦めた…というものでした。

でも、こういう指標ってリアルタイムじゃなくても使えることもあるかも? と思ったので、指標チェックの練習も兼ねて手を動かしてみることにしました。

やること

  • https://alternative.me/crypto/fear-and-greed-index/ から過去分のCrypto Fear & Greed Indexをダウンロードする。ご丁寧にAPIの解説まで書いてあるので簡単です。
  • Crypto Fear & Greed IndexがUTC0時に発表された1時間後のUTC1時を起点に、24時間対数リターンを求めます (発表から1時間後にエントリーする想定です。これならリアルタイムじゃなくてもできますね)
  • UTC1時起点の24時間対数リターンと、その日のCrypto Fear & Greed Indexの散布図を書き、相関係数を求め、散布図を書きます
  • 分析対象の期間はBinanceに各銘柄が上場した日から2022/11/2までです。

コード

まずはこんなコードでCrypto Fear & Greed Indexをダウンロードして、pkl.gzファイルに保存しました。

fng_download.py
import requests
import re
import pandas as pd
from io import StringIO
from pathlib import Path

datadir = 'data/alternativeme'

def get_fear_index_csv(datadir:str = None):
    assert datadir is not None
    print('Crypto Fear and Greed Indexの過去データを全てダウンロードします')
    
    _url = 'https://api.alternative.me/fng/?limit=0&format=csv'
    
    _r = requests.get(_url)
    if _r.status_code != requests.codes.ok:
        print(f'response.get({_url})からHTTPステータスコード {_r.status_code} が返されました。')
        return
    
    _csvraw_re = re.compile('^.*\"data\": \[(.*?)\]', re.DOTALL)
    _m = _csvraw_re.match(_r.text)
    _csvraw = _m.group(1)
    
    _df = pd.read_csv(StringIO(_csvraw), names = ['date', 'fng_value', 'fng_classification'], dtype = {0: str, 1: int, 2: str}, header = 0)
    _df['date'] = pd.to_datetime(_df['date'], dayfirst = True)
    _df = _df.sort_values('date')
    _df = _df.set_index('date', drop = True)
    
    Path(f'{datadir}').mkdir(parents = True, exist_ok = True)
    _df.to_pickle(f'{datadir}/FNG-index-86400sec-0000-00-00.pkl.gz')

get_fear_index_csv(datadir)

続けて、このファイルをロードするユーティリティ関数を追加しました。すごくシンプルなものです。

exercise_util.pyの一部
def load_fng_file():
    return pd.read_pickle('data/alternativeme/FNG-index-86400sec-0000-00-00.pkl.gz')

続けて、散布図を描画する関数を準備しました。これは以前UKIさんの記事を読んで区間ごとの平均値を階段状にプロットするようにしたものに、今回近似直線も引くように機能追加したものです。

exercise_util.pyの一部
def show_correlation(series_x, series_y, title = None, xaxis_label = 'x', yaxis_label = 'y'):
    _df = pd.DataFrame({'x': series_x, 'y': series_y}).dropna()
    _corr = np.corrcoef(_df['x'], _df['y'])
    _y_std = _df['y'].std()
    _y_mean = _df['y'].mean()
    _x_std = _df['x'].std()
    _x_mean = _df['x'].mean()
    
    _std_range = 3
    _y_max = _y_mean + _std_range * _y_std
    _y_min = _y_mean - _std_range * _y_std
    _x_max = _x_mean + _std_range * _x_std
    _x_min = _x_mean - _std_range * _x_std
    
    fig, ax = plt.subplots(2, 2, sharex = 'col', sharey = 'row', gridspec_kw = {'width_ratios': [2, 0.5], 'height_ratios': [2, 0.5]}, figsize = (8, 8))
    
    # レンジごとの平均値を階段状にプロット
    _x_sections = []
    _y_means = []
    for i in range(_std_range * 4 + 1):
        __df = _df[(_df['x'] >= _x_min + 0.5 * _x_std * i) & (_df['x'] < _x_min + 0.5 * _x_std * (i + 1))]
        _x_sections.append(_x_min + 0.5 * _x_std * i)
        _y_means.append(__df['y'].mean())

    # 近似直線のプロット
    _ax = ax[0, 0]

    def func(x, a, c):
        return a * x + c
    
    _x_linspace = np.linspace(_x_min, _x_max, 50)
    _popt, _pcov = curve_fit(func, _df['x'], _df['y'])
    _ax.plot(_x_linspace, func(_x_linspace, *_popt), color = 'green', label = '$y = %s x {%s}$' % (f'{_popt[0]:.4f}', f'{_popt[1]:+.4f}'))

    # 散布図
    _ax.scatter(_df['x'], _df['y'], s = 1)
    _ax.step(_x_sections, _y_means, 'red', where = 'post')
    _ax.set_title(title)
    _ax.set_xlabel(xaxis_label)
    _ax.set_ylabel(yaxis_label)
    _ax.set_xlim([_x_min, _x_max])
    _ax.set_ylim([_y_min, _y_max])
    _ax.set_xticks([_x_mean, _x_mean - 2 * _x_std, _x_mean - 4 * _x_std, _x_mean + 2 * _x_std, _x_mean + 4 * _x_std])
    _ax.set_yticks([_y_mean, _y_mean - 2 * _y_std, _y_mean - 4 * _y_std, _y_mean + 2 * _y_std, _y_mean + 4 * _y_std])
    _ax.grid(axis = 'both')
    _ax.axvline(0, color = 'red', linestyle = 'dotted', linewidth = 1)
    _ax.axhline(0, color = 'red', linestyle = 'dotted', linewidth = 1)
    _ax.text(0.01, 0.99, f'IC = {_corr[0][1]:0.4f}', va = 'top', ha = 'left', transform = _ax.transAxes)
    _ax.legend()

    # ヒストグラム
    _ax = ax[1, 0]
    _ax.hist(_df['x'], bins = 50, range = [_x_min, _x_max])
    _ax.grid(axis = 'both')
    _ax.axvline(0, color='red', linestyle = 'dotted', linewidth = 1)
    
    _ax = ax[0, 1]
    _ax.hist(_df['y'], bins = 50, orientation = 'horizontal', range = [_y_min, _y_max])
    _ax.grid(axis = 'both')
    _ax.axhline(0, color = 'red', linestyle = 'dotted', linewidth = 1)
    
    ax[1, 1].remove()
    
    fig.show()

そして、このようなコードで各種データの処理と散布図の描画をしました。

fng_index_exercise.ipynb
import exercise_util
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# F&G Indexをロード (一日ごとのデータ)
df_fng = exercise_util.load_fng_file()

for _symbol in exercise_util.target_symbols.keys():
    # 1時間足をロードし、F&G Indexと結合する
    df_price = exercise_util.concat_timebar_files(_symbol, 60 * 60)
    df = pd.concat([df_fng, df_price], axis = 1).sort_index()
    df = df[['close', 'fng_value']]
    df['fng_value'] = df['fng_value'].ffill()
    df.dropna(inplace = True)

    # 24時間後のクローズとの対数リターンを求める
    df['lr'] =  np.log(df['close'].shift(-24)) - np.log(df['close'])

    # 対数リターンが異常値になっている行をドロップする。ICを求める時に異常値に引っ張られてしまうので。
    df.loc[(df['lr'] < df['lr'].quantile(0.01)) | (df['lr'] > df['lr'].quantile(0.99)), 'lr'] = np.nan
    df = df.dropna()

    # F&G Indexが発表された1時間後のデータのみを抜き出す
    df = df[['lr', 'fng_value']]
    df = df.loc[df.index.hour == 1]

    # 散布図の描画
    exercise_util.show_correlation(df['fng_value'], df['lr'], _symbol, 'F&G Index (1時間ラグ)', '24時間logリターン')

結果

BTCUSDT (IC = 0.0498)

ETHUSDT (IC = 0.0811)

XRPUSDT (IC = -0.0065)

BNBUSDT (IC = 0.0440)

ADAUSDT (IC = 0.0463)

SOLUSDT (IC = 0.0796)

DOGEUSDT (IC = 0.0306)

MATICUSDT (IC = 0.0268)

AVAXUSDT (IC = 0.0231)

1000SHIBUSDT (IC = 0.0397)

ATOMUSDT (IC = -0.0003)

おわりに

BTC, ETH, SOLあたりはチャレンジできそうな感じ? MLBotで実際に特徴量を試したことがないのでよくわかりません。

分布図を書くだけではなく、指標を使ってトレードのシミュレーションをするところまでをワンセットでできるように準備しておくべきだということを学びました。

散布図を見ていて気づいた点がいくつかあります。

  • 散布図下側に表示されているFear & Greed Index自体の分布が汚いです。50前後の凪の状態の分布が凹んでいるという謎の分布になっています。上下に大きく動いて話題を振りまく数式になっているのかもしれません。
  • 散布図上にプロットされた階段状の線はきれいではないです。これも嫌な感じです。

その他実験していて気づいた点は以下の通りです。

  • 外れ値をそのままにするとICがぐんと上がります。SOLは0.12前後、その他のものも0.07から0.08くらいまで上がってきます。ICを求めるとき外れ値は外したほうがよいのか、そのままにするのがいいのか、どっちがいいんでしょうね。多分外したほうがいいと思うのですが。
  • 今回は1時間遅れから48時間遅れまでチェックしたのですが、銘柄によって最大のICを出すディレイの数値は結構なばらつきがありました。ただ、ディレイ0でICが最高になった銘柄はゼロだったので、リアルタイムでインデックスの値が取れても、結果がぐんと良くなるというわけではないと思います。

おまけ

Crypto Fear and Greedインデックスは、事実上何が出てくるのか分からないオラクルを見てるのと同じということですね。なるほどー。

この考え方を極端にすると、信じられるのはオンチェーンデータだけかあ。CEXも取引量嘘ついたり、謎の消滅する板があったりすると聞きますし。

Discussion