Python {Article130}

ようこそ「Python」へ...

稼げる仮想通貨を見つけるには〔2〕複数の仮想通貨のログリターン・累積ログリターンをグラフに表示して比較する【Matplotlib】

ここでは8回に分けて稼げる仮想通貨を見つける方法について解説します。 第2回目では、複数の仮想通貨(BTC, ETH, LTC, BCH, XRP,...)のログリターン(Log Return: 収益率)と累積ログリターン(Cumulative Log Return)を計算してグラフに表示します。 グラフはPythonのライブラリ「Matplotlib」を使用して表示します。 ちなみに、第3回目ではPythonのライブラリ「Plotly」を使用してグラフに表示します。 ログリターンと累積ログリターンについては第1回目の「記事(Article129)」で解説しています。

ここでは米国のYahoo! Financeから仮想通貨のデータをダウンロードします。 仮想通貨のデータは日付で範囲を指定して日次、週次、月次の単位でダウンロードすることができます。 さらに、秒単位や時間単位でダウンロードすることもできます。 Yahoo! Financeからは、仮想通貨の他にGoogle, Amazon, Apple, Microsoft, MetaなどのGAFAMの株価のデータもダウンロードすることができます。

Yahoo! Financeから日次単位で仮想通貨や株価のデータをダウンロードすると、 デイトレードのパフォーマンスを評価することができます。 さらに、月次単位で仮想通貨や株価のデータをダウンロードすると、 積立投資のパフォーマンスを評価するといったことも可能です。

たとえば、あなたが仮想通貨や株の積立投資を5年前からはじめているとすると、現在のリターン(収益率)がどのくらいになっているかが分かります。 さらに、これから積立投資をはじめようとする人にとっては、どの仮想通貨や株に投資した方がもっともパフォーマンスがよいかを調べることもできます。

一般に仮想通貨に投資するときは、次のようなメトリック(投資のパフォーマンスや効果を測定するための指標や数値)を使用して判断します。
  • 日次リターン (Daily Return)
    仮想通貨の投資において1日あたりの収益率を示します。
  • ログリターン (Log Return)
    仮想通貨の投資においての対数収益率を示します。
  • 累積ログリターン (Cumulative Log Return)
    仮想通貨の投資においての累積的な対数収益率を示します。
  • トレーディングボリューム (Trading Volume)
    仮想通貨の取引量を示します。
  • マーケットキャップ (Market Cap)
    仮想通貨の時価総額を示します。
  • 価格波動性 (Price Volatility)
    仮想通貨の価格の変動率を示します。
さらに、実際にトレードするときは、次のようなテクニカルインジケーターを複数組み合わせて売買のタイミングを判断します。
  • Simple Moving Average (SMA)
    SMAが上昇傾向であれば買い。
  • Exponential Moving Average (EMA)
    EMAが上昇傾向であれば買い。
  • Bollinger Bands (BB)
    Bollinger Bandsで帯域が狭まっている場合は価格変動が大きいと見られ、買い/売りタイミングに注意。
  • Relative Strength Index (RSI)
    RSIが70以上であれば売り、30以下であれば買い。
  • Stochastic Oscillator (STO)
    STOが80以上であれば売り、20以下であれば買い。
説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。
click image to zoom!
図A PIVOT[1]
click image to zoom!
図B PIVOT[2]
click image to zoom!
図C CRYPTO(Daily)
click image to zoom!
図D CRYPTO(Monthly)
click image to zoom!
図E MATIC
click image to zoom!
図F YSCALE(LOG)
click image to zoom!
図G GAFAM

複数の仮想通貨(BTC, ETH, LTC,...)のログリターン(収益率)と累積ログリターンを計算してグラフに表示する

  1. まずは、Visual Studio Codeを起動してプログラムファイルを作成する

    Visual Studio Code (VS Code)を起動したら新規ファイル(*.py)を作成して行1-188をコピペします。 ここでは、Jupter NotebookのようにPythonのプログラムをセル単位で実行します。 VS Codeの場合は「#%%」から「#%%」の間がセルになります。 セルを選択したら[Ctrl + Enter」でセルのコードを実行します。 IPythonが起動されて「インタラクティブ」ウィンドウが表示されます。 「インタラクティブ」ウィンドウからはPythonのコードを入力して実行させることができます。 たとえば、「df.info()」を入力して[Shift + Enter」で実行します。

    * Article.py:
    # Comparing the Profitability of Multiple Cryptocurrencies Article.py
    # %%
    
    ### Import pandas and matplotlib libraries 
    import os
    import math
    import numpy as np
    
    import pandas as pd
    
    import matplotlib.pyplot as plt 
    import matplotlib.dates as mdates
    
    import datetime as dt
    from datetime import timedelta
    from time import sleep
    import yfinance as yf           
    import warnings
    warnings.simplefilter('ignore')
    plt.style.use('fivethirtyeight')
    pd.set_option('display.max_rows', 10)
    
    
    # %%
    
    ######################################################################################################################################
    def load_data(symbol: str, start_date: dt.datetime , end_date: dt.datetime, period='1d', interval='1d', prepost=True) -> pd.DataFrame:
        # valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
        # fetch data by interval (including intraday if period < 60 days)
        # valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo    
        try:
            end_date = end_date + timedelta(days=1)
            start_date_str = dt.datetime.strftime(start_date, "%Y-%m-%d")
            end_date_str = dt.datetime.strftime(end_date, "%Y-%m-%d")
            print(f"Loading data for {symbol}: start_date={start_date_str}, end_date={end_date_str}, {period=}, {interval=}")
            df = yf.download(symbol, start=start_date_str, end=end_date_str, period=period, interval=interval, prepost=prepost)
            # Date     Open          High           Low         Close     Adj Close       Volume   Symbol : interval=1d,5d,1wk,1mo,3mo
            # Datetime Open          High           Low         Close     Adj Close       Volume   Symbol : interval=1m,2m,5m,15m,30m,60m,90m,1h
    
            # Add symbol
            df['symbol'] = symbol 
    
            # Reset index
            df.reset_index(inplace=True) 
    
            # Rename Date or Datetime column name to Time
            if interval in '1m,2m,5m,15m,30m,60m,90m,1h':
                df.rename(columns={'Datetime': 'Date'}, inplace=True)
            else: # interval=1d,5d,1wk,1mo,3mo
                df.rename(columns={'Date': 'Date'}, inplace=True)    
    
            # Convert column names to lower case    
            df.columns = df.columns.str.lower()
    
            return df
        except:
            print('Error loading data for ' + symbol)
            return pd.DataFrame()
    
    ############################################
    def get_data(csv_file: str) -> pd.DataFrame:
        print(f"Loading data: {csv_file} ")
        df = pd.read_csv(csv_file)       
        # date,open,high,low,close,adj close,volume,symbol
        df['date'] = pd.to_datetime(df['date'])   
        # df['date'] = pd.to_datetime(df['date'], utc=True)          
        df.set_index(['date'], inplace=True)
        return df   
    
    ##############################
    # Main
    ##############################
    
    ### Load the crypto data from yahoo finance
    symbols = ['BTC-JPY', 'ETH-JPY','LTC-JPY']
    # symbols = ['BTC-JPY', 'ETH-JPY', 'LTC-JPY','XRP-JPY','BCH-JPY']  
    # symbols = ['BTC-JPY', 'ETH-JPY','LTC-JPY','ADA-JPY'] 
    # symbols = ['MATIC-JPY'] 
    # symbols = ['BTC-JPY', 'ETH-JPY','LTC-JPY','ADA-JPY','MATIC-JPY']    
    # symbols = ['BTC-JPYD', 'ETH-JPY', 'LTC-JPY','XRP-JPY','BCH-JPY','ADA-JPY','DOGE-JPY','MATIC-JPY']  
    # symbols = tickers = ['GOOGL', 'AAPL', 'MSFT','META','AMZN'] 
    interval = '1d' # 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
    df_list = []
    for symbol in symbols:
        csv_file = f"data/csv/log_return({symbol})_{interval}.csv"  # data/csv/log_return(BTC_USD)_1d.csv
        isFile = os.path.isfile(csv_file)
        if not isFile:    
            if interval in '1m,2m,5m,15m,30m,60m,90m,1h':
                end = dt.datetime.now()          
                start = end - timedelta(days=7)      
            else: # interval=1d,5d,1wk,1mo,3mo  
                start = dt.datetime(2014,1,1)   # 2014,1,1 or 2020,1,1 or 2023,1,1
                end = dt.datetime.now()         
            # load_data(symbol: str, start_date: dt.datetime , end_date: dt.datetime, period='1d', interval={'1m'|'1d'}, prepost=True) -> pd.DataFrame:
            df = load_data(symbol, start, end, period='1d', interval=interval)
            if df.shape[0] > 0:    
                df.to_csv(csv_file, index=False)
        # end of if not isFile:
        df = get_data(csv_file)
        df_list.append(df)
    
    row_df = pd.concat(df_list)
    
    
    # %%
    
    ### Filter close, symbol columns 
    df = row_df.filter(['close','symbol'])
    df.reset_index(inplace=True)
    # df
    
    
    # %%
    
    df.groupby('symbol')['date'].agg(['min', 'max', 'count'])
    
    
    # %%
    
    ### pivot table
    pivot_df = df.pivot_table(index=['date'], columns='symbol', values=['close'])
    pivot_df.dropna(inplace=True)
    pivot_df.isnull().sum() 
    # pivot_df
    
    
    # %%
    
    #### flatten columns multi-index, `date` will become the dataframe index
    
    #                                    col[0]   col[1] 
    # pivot_df.columns.values => array([('close', 'BTC-JPY'), ('close', 'ETH-JPY'), ('close', 'LTC-JPY')])
    pivot_df.columns = [col[1] for col in pivot_df.columns.values]  # ['BTC-JPY', 'ETH-JPY', 'LTC-JPY']
    # pivot_df
    
    
    # %%
    
    ### Calculate log return & cumulative log return
    
    dfx = pivot_df.copy()
    log_col_name_list = []
    cum_col_name_list = []
    
    for symbol in symbols: # BTC-JPY, ETH-JPY, LTC-JPY
        coin = symbol      
        log_col_name = f'log_return_{coin}' # log_return_xxx,...
        log_col_name_list.append(log_col_name)
    
        # Calculate log return
        dfx[log_col_name] = np.log(dfx[symbol] / dfx[symbol].shift(1))
        # dfx['log_return_xxx'] = np.log(dfx['xxx'] / dfx['xxx'].shift(1))
     
        # Calculate cumulative log return
        cum_col_name = f'cum_log_return_{coin}'  # cumulative_log_return_btc
        cum_col_name_list.append(cum_col_name)
    
        dfx[cum_col_name] = np.exp(dfx[log_col_name].cumsum()) - 1
        # dfx['cum_log_return_xxx'] = np.exp(dfx['log_return_xxx'].cumsum()) - 1
    
        # Preview the resulting dataframe
        print(f"Cumulative Log Return ({symbol}) = {dfx.iloc[-1][cum_col_name]:.4f}")  
        # print(f"Cumulative Log Return (xxx) = {dfx.iloc[-1]['cum_log_return_xxx']:.4f}") 
    
    dfx.dropna(inplace=True)
    dfx.isnull().sum() 
    
    
    # %%
    
    ### Plot Cumulative Log Returns
    
    plt.figure(figsize=(10,5))
    
    if interval in '1m,2m,5m,15m,30m,60m,90m,1h':
        plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
        plt.gca().xaxis.set_major_locator(mdates.DayLocator())
        plt.gcf().autofmt_xdate()
    
    plt.title(f'Performance: Cumulative Log Returns - Interval({interval.upper()})', fontsize=18)
    # plt.yscale('log') 
    for i, symbol in enumerate(symbols):
        plt.plot(dfx[cum_col_name_list[i]], label=symbol)
    plt.xlabel('Date')
    plt.ylabel('Cumulative log returns')
    plt.xticks(rotation=45)
    plt.legend(loc='best')
    plt.show() 
    click image to zoom!
    図1
    図1にはVS Codeの画面が表示されています。 次のステップでは「セル」を選択して「セル」単位でPythonのコードを実行します。
  2. Pythonのライブラリを取り込む

    VS Codeから行5-21のセルをクリックして[Ctrl + Enter]で実行します。 IPythonが起動して「インタラクティブ」ウィンドウに実行結果が表示されます。 ここでは、Python 3.10.9とIPython 8.9.0を使用しています。
    click image to zoom!
    図2
    図2ではPythonの各種ライブラリを取り込んでいます。 行19ではPythonの警告メッセージを抑止しています。 行20ではMatplotlibのデフォルトのスタイルを設定しています。 行21では、PandasのDataFrameを表示するときデータ件数を最大10件に制限しています。
  3. 米国のYahoo! Financeから仮想通貨(BTC, ETH, LTC)のデータを取り込む

    VS Codeから行27-102のセルを選択したら[Ctrl + Enter]で実行します。 ここでは米国のYahoo! Financeからビットコイン(BTC-JPY)、イーサリアム(ETH-JPY)、ライトコイン(LTC-JPY)のデータをダウンロードしています。 ダウンロードするデータは「2014/1/1」から当日「2023/2/14」までの範囲を指定しています。

    click image to zoom!
    図3
    図3では、米国のYahoo! Financeから仮想通貨(BTC-JPY, ETH-JPY, LTC-JPY)のデータをダウンロードしてPandasのDataFrameに格納します。 ここではデータの範囲を「2014/1/1」から当日「2023/2/14」に設定しています。 「symbols」には仮想通貨のシンボルを設定します。ここでは「BTC-JPY, ETH-JPY, LTC-JPY」を設定しています。

    「interval」には、「1d」を設定しているので「日次」単位の価格データがダウンロードされます。 「interval」には「1m, 2m, 3m, 15m,1h,...」などが指定できます。 「m」は分単位、「h」は時間単位を意味します。 「1m」は1分単位の価格をダウンロードします。

    Yahoo! FinanceからダウンロードしたデータはCSVファイルに保存します。 次回からはCSVファイルからデータを取り込みます。
  4. PandasのDataFrameから「close, symbol」のカラムを抽出する

    行108-109のセルを選択したら[Ctrl + Enter]で実行します。 ここでは、PandasのDataFrameから「close, symbol」のカラムのみ抽出して変数「df」に格納しています。 行109では、DataFrameのreset_index()メソッドでインデックス(date)を通常のカラムに戻しています。 DataFrameにはデフォルトのインデックスが作成されます。

    click image to zoom!
    図4
    図4にはDataFrameの内容が表示されています。 このDataFrameは「date, close, symbol」のカラムから構成されています。 インデックスには0から始まるデフォルトの値が採番されています。
  5. PandasのDataFrameのagg()メソッドを使用して仮想通貨の「日付」の「min, max, count」を表示する

    行115のセルを選択したら[Ctrl + Enter]で実行します。 ここでは、DataFrameのgroupby()メソッドでDataFrameを「symbol」のカラムでグループ化しています。 さらに、「date」のカラムにagg()メソッドを適用して「min, max, count」の統計情報を計算させています。

    click image to zoom!
    図5
    図5には仮想通貨のシンボルごとの日付(date)の最小値(min)、最大値(max)、件数(count)が表示されています。 たとえば、ビットコイン(BTC-JPY)のデータは日付が「2014-09-17」から「2023-02-13」の範囲で件数が3072件になっています。
  6. Pandasのpivot_table()メソッドで仮想通貨の終値(close)を縦形式から横形式に変換する

    行121-123のセルを選択したら[Ctrl + Enter]で実行します。 ここでは、DataFrameの「pivot_table()」メソッドを実行してDataFrameの形式を縦形から横形に変換しています。 行122では、DataFrameの「dropna()」メソッドで不正な値(Not A Number)のある行をDataFrameから削除しています。 行123では、DataFameの「isnull().sum()」メソッドで不正な値の合計件数を表示させています。

    click image to zoom!
    図6-1
    図6-1にはDataFrameの「symbol」のカラムが縦形から横形に変換されています。 「symbol」のカラムにpivot_table()を適用すると、このカラムはマルチインデックスのオブジェクトに変換されます。 つまり、階層化されたインデックスになります。 階層化されたインデックスは、tuple型で「(close, BTC-JPY), (close, ETH-JPY), (close, LTC-JPY)」のように格納されます。
    click image to zoom!
    図6-2
    図6-2では、「pivot_df.info()」でDataFrameの構造を表示しています。 カラム「BTC-JPY, ETH-JPY, LTC-JPY」は、マルチインデックスの構造になっています。
  7. PandasのDataFrameのカラムをフラットにする

    行133のセルを選択したら[Ctrl + Enter]で実行します。 ここでは、DataFrameの「symbol」のカラムをマルチインデックスからフラットな形式に変換しています。

    click image to zoom!
    図7
    図7にはDataFrameのカラム「BTC-JPY, ETH-JPY, LTC-JPY」がマルチインデックスからフラットな形式に変換された状態で表示されています。

    行133は「リスト内包表記(List Comprehension)」という記述で、このステートメントが実行されると、 「('close', 'BTC-JPY')」というtuple型のマルチインデックスがlist型として格納されます。

    「col[1]」のように記述するとtuple型の2番目のデータ「BTC-JPY」が抽出されます。 ちなみに「col[0]」のように記述すると「close]が抽出されます。

    「['BTC-JPY', 'ETH-JPY', 'LTC-JPY']」のようなlist型のカラム名を、 DataFrameの「columns」属性(プロパティ)に設定すると、DataFrameのカラム名がフラットになります。
  8. 仮想通貨のログリターンと累積ログリターンを計算する

    行141-166のセルを選択したら[Ctrl + Enter]で実行します。 行141では、「pivot_df」に格納されているDataFrameを変数「dfx」に「copy()」メソッドを使用してコピーしています。 ちなみに、「dfx = pivot_df」のように記述するとDataFrameはコピーされません。 dfxはpivot_dfのメモリ領域を共有することになります。 なので「dfx」のDataFrameを変更すると「pivot_df」のDataFrameが変更されてしまいます。 「copy()」メソッドを使うと別のメモリ領域が確保されます。

    行145から162のforループでは、仮想通貨のログリターンと累積ログリターンを計算してDataFrameに格納しています。 行162ではDataFrameの最後の累積ログリターンを表示しています。 行165では不正な値のある行を削除しています。 行166では不正な値の合計件数を表示させています。

    click image to zoom!
    図8-1
    図8-1には仮想通貨の最後の累積ログリターンが表示されています。 さらに、行166の「dfx.isnull().sum() 」の実行結果が表示されています。 件数が0になっているので不正な値「NaN(Not A Number)」が0件ということになります。
    click image to zoom!
    図8-2
    図8-2にはDataFrameの内容を表示しています。 ここでは仮想通貨ごとのログリターンと累積ログリターンが表示されています。
  9. 仮想通貨のログリターン、累積ログリターンをグラフに表示する

    行173-188のセルを選択したら[Ctrl + Enter]で実行します。 行173では図(グラフ)のサイズを設定しています。 行176-178では、グラフのX軸の日付を「mm/dd」の形式でフォーマットしています。 行182-183のforループでは仮想通貨の累積ログリターンを線グラフでプロットしています。 行186では、X軸に表示する日付を45度斜めに表示するように設定しています。 行187では、グラフの凡例が最適な位置に表示されるように設定しています。 行188では、グラフを表示させています。

    click image to zoom!
    図9-1
    図9-1には仮想通貨の累積ログリターンがグラフに表示されています。 「Interval(1D)」が表示されているので「日次」の累積ログリターンになります。 このグラフからは、仮想通貨のパフォーマンスが「ETH, BTC, LTC」の順番になっていることがわかります。 仮にあなたが仮想通貨のデイトレーダーだとしたら、イーサリアム(ETH)に投資するのがもっともパフォーマンスがよいことになります。

    click image to zoom!
    図9-2
    図9-2のグラフには「Interval(1MO)」が表示されているので「月次」のグラフになります。 このグラフからは、仮想通貨の積立投資のパフォーマンスが分かります。 仮にあなたが仮想通貨の積立投資をするとしたら、イーサリアム(ETH)に投資するのがもっともパフォーマンスがよいことになります。
    click image to zoom!
    図9-3
    図9-3では仮想通貨(BTC, ETH, LTC, ADA, MATIC)の日次の累積ログリターンを表示しています。 仮想通貨ポリゴン(MATIC)がダントツでパフォーマンスが良いので、その他の仮想通貨の線はほぼ水平になっています。 このような場合は行181のコメントを外して「plt.yscale('log') 」を有効にします。
    click image to zoom!
    図9-4
    図9-4には「plt.yscale('log') 」を追加したときのグラフが表示されています。 これで仮想通貨ポリゴン(MATIC)以外の仮想通貨のパフォーマンスも把握できます。
    click image to zoom!
    図9-5
    図9-5にはGAFAMの株価のパフォーマンスがグラフに表示されています。 「Interval(1D)」が表示されているので「日次」のパフォーマンスになります。 ここでは「2020/1/1」から今日「2023/2/14」までのデータを使用しています。 このグラフからは、パフォーマンスがApple, Microsoft, Google, Amazon, Metaの順番になっていることが分かります。
    click image to zoom!
    図9-6
    図9-6ではGAFAMの「月次」のパフォーマンスがグラフに表示されています。 このグラフは月次なので積立投資のパフォーマンスとして利用できます。 仮にあなたが積立投資するとしたら、Appleがもっともパフォーマンスがよいということになります。