Python {Article116}

ようこそ「Python」へ...

ITエンジニアが仮想通貨の自作自動売買システム(bot)で月収7万円のパッシブインカムを得るには [8] BOTのApiクラスを作成する

ここではITエンジニアがPythonで仮想通貨の自動売買システム(BOT)を作成してパッシブインカム(Passive Income)を得る方法を解説します。 「仮想通貨のボット(BOT)でパッシブインカムを得る」シリースは以下の11回に分けて解説します。 第8回目ではこのシリーズで作成するBOTのApiクラスを作成する方法を解説します。 なお、このシリーズで作成するBOTは日本の取引所「GMOコイン」を利用することを前提にしています。 bitFlyer、Binanceなどの取引所を利用するBOTについては後日、別記事にて解説します。

予め断っておきますが、BOTを作成したからといってすぐには稼げるようにはなりません。 3ヶ月から6ヶ月かけて仮想取引とリアル取引をしてBOTのアルゴリズムとパラメータを調整する必要があります。 そうすれば半年後には投資額にもよりますが月5万円から10万円くらいは稼げるようになっているはずです。

半年経っても月10万円稼げないときは次の「座右の銘」を思い出してください。 きっと稼げるようになるはずです。

「上がり始めたら買え(下がっている間は買うな)。 下がり始めたなら売れ(上がっている間は売るな)。 また、そもそも当てられるわけのない天井や底で売買しようとするな。 一番安いところで買ったり、 一番高いところで売れるものだと思うな。」 このシリーズではMicrosoftのVisual Studio Code(VS Code)を使用しますが、Jupyter NotebookなどのツールでもOKです。 説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。

【免責事項】
当記事や当サイトの情報を参考に仮想通貨のBOT(自動トレードプログラム)を動かす場合は、すべて自己責任でお願いいたします。 当サイトの情報を参考にして発生したいかなる損害も責任を負いません。

BOTのApiクラスを作成する前に、予め以下の「オブジェクト指向プログラミング」のシリーズを読んでクラスの基本を理解してください。

BOTのApiクラスを作成する

  1. GMOコインに口座を開設してAPIキーを取得する

    Apiクラスを作成する前に、 GMOコインの「会員登録」 サイトに移動して口座を開設します。 なお、口座を開設するときは「現物取引」と「レバレッジ取引」の双方をチェックしてください。 ちなみにBOTはレバレッジ取引のみ行います。

    次にGMOコインに 「ログイン」したら、 左側のメニューから「API」を選択して「APIキー」と「APIシークレット」を取得します。 なお、「APIキー」と「APIシークレット」はPythonのソースコードに直接記述しないでWindows / MacOSの環境変数に登録します。

    click image to zoom!
    図1-1
    図1-1ではGMOコインのサイトから「API」を選択して「APIキー」と「APIシークレット」を表示させています。


    click image to zoom!
    図1-2
    図1-2はWindows 11の「ユーザー環境変数」に「APIキー」と「APIシークレット」を登録した画面です。 変数名は「GMO_API_KEY」、「GMO_SECRET_KEY」で登録してください。


  2. Apiクラスを作成して__init__(), __str__()メソッドを追加する

    ここではApiクラスを作成して__init__(), __str__()メソッドを追加します。 なお、Apiクラスの各種メソッドはGMOコインのAPIを使用します。

    __init__()メソッドはApiクラスのインスタンスを生成したときに自動的に呼ばれます。 __str__()メソッドはprint()等でApiクラスを表示したときに呼ばれます。 __init__()、__str__()の詳しいことは、 「記事(Article085)」、 「記事(Article087)」で詳しく解説しています。

    Apiクラスのインスタントを生成するときは、 引数1にGvarクラス、引数2にCoinクラスのオブジェクトを指定します。 Apiクラスの各種メソッドはGvar, Coinクラスの各種プロパティを参照して処理を行います。

    どの仮想通貨に対して処理を行うかは、 Coinクラスのsymbolプロパティに仮想通貨のシンボル「BTC_JPY」を設定します。 仮想通貨のシンボルはレバレッジ取引用のシンボルのみサポートします。

    Apiクラスの各種メソッドはシミュレーションモード(仮想モード)とリアルモードをサポートしています。 シミュレーションモードで実行させたいときは、 Coinクラスのreal_modeプロパティに「False」を設定します。 リアルモードで実行させたいときは、real_modeプロパティに「True」を設定します。

    GMOコインのAPIでタイムアウト等のエラーが発生したときは、 Coinクラスのapi_retry_limit_countプロパティに設定した回数分再試行します。 デフォルトでは5回再試行します。

    __init__()メソッドの行68-69では、Windows/MacOSの環境変数からGMOコインの「APIキー」と「APIシークレット」を取得しています。 __str__()メソッドの行73ではApiクラスのプロパティを取得して返しています。

    api.py:
    # api.py
    
    """
    Api class
    status = 0, 4, 5, 8, 9, 999
        0: normal return
        4: invalid request (requests are too many)
        5: maintenance return
        8: retry return
        9: empty dataframe return
        999: misc error return
    """
    
    # import the libraries
    import os
    import os.path
    
    import time
    from time import sleep  
    import datetime as dt
    
    import numpy as np
    import pandas as pd
    
    # api
    import hmac
    import hashlib
    
    import requests
    import json
    
    from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe  
    from lib.debug import Debug
    from lib.trade import Trade
    
    import warnings
    warnings.simplefilter('ignore')
    
    ########## Api class
    class Api:
        """
        __init__(self, gvar, coin)  
        load_master_driver() => load_master_data() => call get_crypto_try() => get_crypto()   
        get_assets() => exe_assets()  
        get_crypto_try() => get_crypto() 
        get_orderbooks_try() => get_orderbooks()
        get_order_status_try() => get_order_status(), get_price_try() => get_price() 
        exe_buy_order_market_wait()  => exe_buy_order_market(),  exe_buy_order_limit()
        exe_sell_order_market_wait() => exe_sell_order_market(), exe_sell_order_limit()
        exe_buy_close_order_market_wait()  => exe_buy_close_order_market(),  exe_buy_close_order_limit()
        exe_sell_close_order_market_wait() => exe_sell_close_order_market(), exe_sell_close_order_limit()
        exe_cancel_order()     
        """     
          
        #######################################################
        def __init__(self, gvar: object, coin: object) -> None:
            self.gvar = gvar                      
            self.coin = coin                
            self.debug = Debug(gvar)                  
            self.trade = Trade(gvar, coin)   
          
            self.folder_gmo =  f'{self.gvar.domain}/{self.gvar.exchange}/'  # desktop-pc/bot/ 
            self.folder_trading_type =  f'{self.gvar.domain}/{self.gvar.exchange}/{self.coin.trade_type}/'  
            # desktop-pc/bot/buy_sell/          
            # desktop-pc/bot/sell_buy/    
    
            ### GET GMO API-key / Secret Key
            self.api_key = os.environ.get('GMO_API_KEY')
            self.api_secret = os.environ.get('GMO_SECRET_KEY')
    
        #########################
        def __str__(self) -> str:
            return f"Api({self.folder_gmo=}, {self.folder_trading_type=}, self.api_key=●●●, self.api_secret=●●●)"

    click image to zoom!
    図2
    図2はデモプログラムの実行結果です。 Apiクラスの各種プロパティを表示しています。 「api_key」と「api_secret」プロパティは「●●●」で表示されます。


  3. Apiクラスにget_crypto_try(), get_crypto()メソッドを追加する

    ここではApiクラスにget_crypto_try(), get_crypto()メソッドを追加します。 get_crypto_try()は親メソッドでget_crypto()は子メソッドです。

    get_crypto_try()からget_crypto()メソッドを呼び出してタイムアウト等のエラーが発生したときは、 Coinクラスに設定した回数分再試行します。

    get_crypto()メソッドはGMOコインから「取引履歴」のデータを取得して返します。 このメソッドの引数1(page)には、1-100のページ番号を指定します。 引数2(count)には、1ページ当たりの取得件数を指定します。 1~100まで指定できます。

    api.py:
        #########################################################
        def get_crypto_try(self, page: int, count: int) -> tuple:   # return (status, df)
            retry_count = 0
            # retry N times if invalid or connection error or time out errors
            while True:
                status, df = self.get_crypto(page, count)  # => return (status, df)             
                # invalid request (requests are too many), connection error, time out error ?  
                if status == 4 or status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~60 seconds    
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_crypto({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ")                   
                        sleep(sec)  # sleep 3~60 seconds                
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (status, df)
    
        ##################################################### 
        def get_crypto(self, page: int, count: int) -> tuple:   # return (status, df)          
            symbol = self.coin.symbol
            page_str = str(page)
            count_str = str(count)
    
            endPoint = 'https://api.coin.z.com/public'
            path = f'/v1/trades?symbol={symbol}&page={page_str}&count={count_str}'        
            url = endPoint + path  
    
            status = 999    # misc error return
    
            while True:   
    
                try:
                    res_dict = requests.get(url, timeout=3) # timeout=3 sec
               
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)                        
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 999            # misc error
                    break   # error return                
    
                except requests.exceptions.ConnectionError as errc:                       
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {errc}  ")
                    self.debug.sound_alert(3) 
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 8              # retry error
                    break   # error return 
    
                except requests.exceptions.Timeout as errt:       
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                      
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 8              # retry error
                    break   # error return                  
    
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)                                             
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 999            # misc error
                    break   # error return                   
    
                except:     #  block lets you handle the error
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_crypto() ▶ return ...")
                    self.debug.sound_alert(3)                    
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 999            # misc error
                    break   # error return                   
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try and except block
                    pass
    
                # end of try except else finally        
    
                dict = res_dict.json()
    
                # invalid url
                # {'message': 'Not Found', 'statusCode': 404}            
    
                statusCode = dict.get('statusCode')
    
                if statusCode == 404:
                    message = dict.get('message')
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}URL NOT FOUND ERROR:{Bcolors.ENDC} get_crypto() ▶  requests.get({url=}) : {statusCode=}, {message=} ")
                    self.debug.sound_alert(3)  
                    quit()
    
                ### Normal Case 
    
                # {'status': 0, 'data': {'list': [{'price': '4480940', 'side': 'BUY', 'size': '0.0109', 'timestamp': '2022-02-27T08:15:09.241Z'},...        
    
                ################################################### debug info
                # print('-'*100, 'dump response from get_crypto()')
                # print(dict) 
                # print('-'*100, 'dump response from get_crypto()')    
                ################################################### debug info               
    
                status = dict.get('status')
    
                # no error ?
                if status == 0:
                    data_dict = dict.get('data')
                    data_list = data_dict.get('list')
                    data_pagination = data_dict.get('pagination')
                    df = pd.DataFrame(data_list)
                    if df.shape[0] > 0:
                        # convert data types
                        df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (ware) ★  errors='coerce' => NaT or errors='ignore'
                        df = df.astype({'price': 'float', 'size': 'float'})  
    
                    break   # normal return   
                    # ----------------------------------------------------------------
                    #  #   Column     Dtype              
                    # ----------------------------------------------------------------              
                    #  0   price      float64            
                    #  1   side       object    'BUY' or 'SELL'             
                    #  2   size       float64            
                    #  3   timestamp  datetime64[ns, UTC] ★ UTC descending order  
                    # ----------------------------------------------------------------                  
    
                else:   # error return : status == 5 or 4 or 1 or ?
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')
                    msg_str = msg_list[0].get('message_string')	            
    
                    if status == 5:   # Maintenance
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_crypto({symbol}, {page}, {count}) ▶ {status=}, {msg_code} - {msg_str} quit python...")
                        self.debug.sound_alert(3)  
                        quit()
    
                    elif status == 4 and msg_code == 'ERR-5003':   # Invalid Request: ERR-5003 Requests are too many              
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}Invalid Request():{Bcolors.ENDC} get_crypto({symbol}, {page}, {count}) ▶ {status=}, {msg_code} - {msg_str} wait 1 minute and retry...") 
                        df = pd.DataFrame()    # return empty dataframe 
                        break   # error return                     
    
                    else:   # status == ?
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}Invalid Request:{Bcolors.ENDC} get_crypto({symbol}, {page}, {count}): {status=}, {msg_code} - {msg_str} error return with empty dataframe... ") 
                        ################################################# debug info
                        print('-'*100, 'dump response from get_crypto()')
                        print(dict) 
                        print('-'*100, 'dump response from get_crypto()')    
                        ################################################# debug info            
                        df = pd.DataFrame()    # return empty dataframe 
                        break   # error return
    
                # end of if status == 0:
            # end of while True:
    
            #trace_write(f".... get_crpto('{symbol}', page='{page}', count='{count}'): ▶ {df.shape[0]} ")  
            return (status, df)

    click image to zoom!
    図3
    図3はデモプログラムの実行結果です。 get_crypto_try()メソッドからの戻り値(PandasのDataFrame)を表示しています。 DataFrameにはGMOコインの取引履歴が100件格納されています。 ここでは、DataFrameのhead()とtail()メソッドで先頭3件と最後から3件の取引履歴を表示しています。


  4. Apiクラスにget_orderbooks_try(), get_orderbooks()メソッドを追加する

    ここではApiクラスにget_orderbooks_try(), get_orderbooks()メソッドを追加します。 get_orderbooks_try()は親メソッドで、get_orderbooks()は子メソッドです。

    get_orderbooks_try()では、get_orderbooks()でタイムアウト等のエラーが発生するとCoinクラスに設定されている回数分再試行します。

    get_orderbooks_try()メソッドではGMOコインから「板情報」を取得して返します。

    api.py:
        ###################################### 
        def get_orderbooks_try(self) -> tuple:  # return (sell_df, buy_df)   
            retry_count = 0
            # retry N times if invalid or connection or time out errors
            while True:
                status, sell_df, buy_df =  self.get_orderbooks()  # => return (status, sell_df, buy_df)           
                # invalid request (requests are too many), connection error, time out error ?  
                if status == 4 or status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1                    
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~60 seconds                       
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_orderbooks({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ") 
                        sleep(sec)    # sleep 3~60 seconds                                                    
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (sell_df, buy_df)
    
        ################################## 
        def get_orderbooks(self) -> tuple:  # return (status, sell_df, buy_df)  
            symbol = self.coin.symbol
    
            endPoint = 'https://api.coin.z.com/public'
            path = f'/v1/orderbooks?symbol={symbol}'
            url = endPoint + path
    
            status = 999    # misc error
    
            while True:            
                try:
                    res_dict = requests.get(url, timeout=3) # 3 seconds  
                
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)                          
                    status = 999                # misc error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe
    
                except requests.exceptions.ConnectionError as errc:                      
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {errc}  ")
                    self.debug.sound_alert(3)  
                    status = 8                  # retry error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                   
    
                except requests.exceptions.Timeout as errt:       
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                      
                    status = 8                  # retry error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                                    
    
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)   
                    status = 999                # misc error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                                                       
    
                except:     #  block lets you handle the error
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_orderbooks() ▶ return...")
                    self.debug.sound_alert(3)  
                    status = 999                # misc error    
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                 
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try and except block
                    pass
    
                # end of try except else finally        
    
                dict = res_dict.json()
    
                # {'status': 0,
                #  'data': {
                #   'asks': [{'price': '4042000', 'size': '0.0005'},
                #    {'price': '4045000', 'size': '0.0028'},
                #    {'price': '4045018', 'size': '0.0001'},
                #    {'price': '4049000', 'size': '0.001'},
                #    {'price': '4049310', 'size': '0.0329'},
                #    {'price': '4049520', 'size': '0.0125'},
                #    {'price': 5381822.0, 'size': '0.001'}],
                #   'bids': [{'price': 5013050.0, 'size': '0.0749'},
                #    {'price': 5013000.0, 'size': '0.0015'},
                #    {'price': 5012425.0, 'size': '0.05'},
                #    {'price': 5012420.0, 'size': '0.05'},
                #    {'price': 4740000.0, 'size': '0.2135'}],
                #   'symbol': 'BTC'},
    
                ### Normal Case      
    
                ####################################################### debug info
                # print('-'*100, 'dump response from get_orderbooks()')
                # print(dict) 
                # print('-'*100, 'dump response from get_orderbooks()')    
                ####################################################### debug info               
    
                status = dict.get('status')
    
                if status == 0:
                    data_dict = dict.get('data')
    
                    asks = data_dict.get('asks') # sell order info (ascending order) 
                    bids = data_dict.get('bids') # buy order info (descending order)       
    
                    sell_df = pd.DataFrame(asks)      
                    buy_df = pd.DataFrame(bids)
    
                    # {'asks': [{'price': '4064331', 'size': '0.0001'},
                    #   {'price': '4069888', 'size': '0.0309'},
                    #   {'price': '4070000', 'size': '0.0073'},
                    #   {'price': '4070620', 'size': '0.001'},
                    #   {'price': 5381822.0, 'size': '0.001'}],
    
                    #  'bids': [{'price': 5013050.0, 'size': '0.0749'},
                    #    {'price': 5013000.0, 'size': '0.0015'},
                    #    {'price': 5012425.0, 'size': '0.05'},
                    #    {'price': 5012420.0, 'size': '0.05'},
                    #    {'price': 4740000.0, 'size': '0.2135'}],            
    
                    if sell_df.shape[0] > 0:
                        # convert data types
                        sell_df = sell_df.astype({'price': 'float', 'size': 'float'})  
                        
                    if buy_df.shape[0] > 0:
                        # convert data types
                        buy_df = buy_df.astype({'price': 'float', 'size': 'float'})                    
                
                else:   # error return
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')
                    msg_str = msg_list[0].get('message_string')	            
    
                    if status == 5:   # Maintenance
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_orderbooks({symbol}) ▶ {status=}, {msg_code} - {msg_str} quit python...")
                        self.debug.sound_alert(3)  
                        quit()
    
                    else:   # status == ?
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}Invalid Request:{Bcolors.ENDC} get_orderbooks({symbol}) ▶ {status=}, {msg_code} - {msg_str} error return with empty dataframe... ") 
                        ###################################################### debug info
                        print('-'*100, 'dump response from get_orderbooks()')
                        print(dict) 
                        print('-'*100, 'dump response from get_orderbooks()')    
                        ##################################################### debug info            
                        sell_df = pd.DataFrame()    # return empty dataframe  
                        buy_df = pd.DataFrame()     # return empty dataframe                
                
                # end if if status == 0:        
                break
            # end of while true:
      
            #trace_write(f".... get_orderbooks('{symbol}'): ▶ {sell_df.shape[0]}, {buy_df.shape[0]} ")  
            return (status, sell_df, buy_df)    # status, sell_df(ascending order), buy_df(descending order)

    click image to zoom!
    図4
    図4はデモプログラムの実行結果です。 ここではget_orderbooks_try()メソッドからの戻り値(PandasのDataFrame)を表示しています。 get_orderbooks_try()からは「売り」と「買い」の板情報がDataFrameに格納されて返されます。

    ここではDataFrameの板情報の先頭5件を表示しています。 ちなみに「売り」の板情報は472件、「買い」の板情報は461件格納されています。

    DataFrameには「price, size」が格納されています。 「売り」の板情報は価格(レート)の昇順、「買い」の板情報は価格(レート)の降順に格納されています。 Coinクラスのsymbolプロパティに「XRP_JPY」が設定されているので、 リップルのレバレッジ取引の板情報が返されています。


  5. Apiクラスにget_assets(), exe_assets()メソッドを追加する

    ここではApiクラスにget_assets(), exe_assets()メソッドを追加します。 get_assets()は親メソッドで、exe_assets()は子メソッドです。

    get_assets()では、exe_assets()から返されたデータを、 Coinクラスのsymbolプロパティに設定されている仮想通貨で絞り込んで返します。 このメソッドからは仮想通貨の保有数が返されます。 なお、このメソッドは現物取引の仮想通貨にのみ適用されます。 なのでBOTでは使用していません。

    api.py:
        ############################## 
        def get_assets(self) -> tuple:  # ▶ (status, asset)
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
           
            status, df = self.exe_assets()
    
            # --------------------------------------------------
            #  #   Column          Dtype 
            # -------------------------------------------------- 
            #  0   amount          object
            #  1   available       object
            #  2   conversionRate  object
            #  3   symbol          object
            # --------------------------------------------------
    
            asset = 0.0 
    
            if status != 0:     
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_assets{Bcolors.ENDC}(): ▶ {status=} ")        
                # error return
                return status, asset
    
            ############################################ debug info
            # print('-'*100, 'Begin: get_assets()')
            # print('\n', df.head()) 
            # print('-'*80)
            # print('\n', df.info())
            # print('-'*100, 'End  : getassets()')    
            ############################################ debug info
    
            # -----------------------------------------------------
            #          amount    available  conversionRate symbol
            # -----------------------------------------------------
            # 0  185822.0000  185822.0000             1.0    JPY
            # 1       0.0001       0.0001       4564624.0    BTC
            # 2       0.0000       0.0000        302500.0    ETH
            # 3       0.0100       0.0100         33100.0    BCH
            # 4       0.1000       0.1000         11992.0    LTC
            # ----------------------------------------------------
    
            filter_mask = df['symbol'] == symbol    
            dfx = df[filter_mask]
            if dfx.shape[0] > 0:            
                asset = dfx.amount.values[0]    
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_assets{Bcolors.ENDC}(): ▶ {status=}, {symbol=}, {asset=:,.{_q_}f} ")
            # normal return
            return status, asset    # 0(int), 1.123(float)
    
        ############################## execute account/assets ▶ status, df(dataframe)
        def exe_assets(self) -> tuple:   # ▶ status, df(dataframe)
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'GET'
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/account/assets'
    
            url = timestamp + method + path
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.get(endPoint + path, headers=headers, timeout=3)    
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}JCOM Maintenance:{Bcolors.ENDC} get_assets() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()                 
            else:       # block lets you execute code when there is no error
                pass
            finally:    # block lets you execute code, regardless of the result of the try and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            msg_code = ''
            msg_str = ''     
    
            if status == 0:
                data_list = dict.get('data') 
                df = pd.DataFrame(data_list)
    
                # -----------------------------------------------------------
                #  #   Column          Dtype 
                # -----------------------------------------------------------
                #  0   amount          object => float
                #  1   available       object => float
                #  2   conversionRate  object => float
                #  3   symbol          object
                # -----------------------------------------------------------
    
                # convert data types
                df = df.astype({'amount': 'float', 'available': 'float', 'conversionRate': 'float'})         
                # normal return     
                return (status, df)
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_assets(): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()   
    
            else:   # status=1 or ?
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # ERR-201
                msg_str = msg_list[0].get('message_string')     # Trading margin is insufficient
    
                ############################# debug info  
                print('-'*100, 'Begin: dump response from exe_assets()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_assets()')    
                ############################# debug info
    
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_assets{Bcolors.ENDC}(): ▶ {status=}, {msg_code} - {msg_str} ")
                # error return
                return (status, pd.DataFrame())

    click image to zoom!
    図5
    図5はデモプログラムの実行結果です。 ここでは現物取引のリップル(XRP)の保有数を取得して表示しています。 保有数10が表示されています。


  6. Apiクラスにexe_buy_order_market_wait(), exe_buy_order_market(), exe_sell_close_order_limit()メソッドを追加する

    ここでは、Coinクラスのtran_typeプロパティに「buy_sell」を設定したときにBOTで使用されるApiのメソッドを追加します。 「買い▶売り」の注文を行う場合、BOTは買いの成行き注文を行って約定したら売りの注文を指値で行います。

    exe_buy_order_market_wait()は親メソッドで、exe_buy_order_market()は子メソッドです。 親メソッドでは買いの成行き注文が約定するまで一定時間待ちます。 そして約定したら約定情報をPandasのDataFrameに格納して返します。

    DatFrameには約定したときの価格(レート)、数量、注文ID、ポジションIDなどが格納されています。 なお、数量は分割されることがあります。 たとえば、リップル(XRP_JPY)の数量「100」を買いで注文をしたとき、 「20」「30」「50」のように分割されることがあります。

    さらに、成行き注文のときは「FAK: Fill and Kill」が適用されて全数量約定しない場合があります。 前出の例で言えば、「100」買い注文したのに「60」しか約定しないといったことがあります。 この場合、BOTは残数の「40」を再度注文します。 つまり、全数約定するように内部的に処理します。

    BOTは売りのタイミングを検知したら、 exe_sell_close_order_limit()メソッドを実行して指値の売り注文を出します。 ただし、成行きの買い注文の数量が「20」「30」「50」にように分割されているときは、 売りのポジションIDが3個生成されるのでexe_sell_close_order_limit()メソッドを3回発行します。

    指値の売り注文が約定しない状態で午前6を経過するとレバレッジ手数料が発生します。 なので、BOTは午前5~午前6まではレバレッジの取引を保留にします。 レバレッジの手数料を発生させたくないとき、 BOTに午前5時から6時の間に売りポジションを強制的に執行させることも可能です。 この場合、BOTは成行きの売り注文を出します。

    指値のときは「FAS: Fill and Store」が適用されるので全数量約定します。 ただし、成行き注文と同様数量が分割されて約定することがあります。 前出の例で言えば、「20」「30」「50」の売り注文したとき、 「50」が「20」と「30」に分割されて約定されることがあります。

    これまでの経験だと、リップルで数量を「1000」くらいにすると4~7くらいに数量が分割されて約定されることがあります。

    api.py:
        ###############################################################################################  
        def exe_buy_order_market_wait(self, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)  
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                if latest_close_price == 0.0: latest_close_price = price              
                status = 0
                execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                order_id     = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                position_id  = 'P' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PMMDD-HHMMSS-999999(milliseconds) 
                position_list = [                         
                                    {
                                        'executionId': execution_id,    # E9999-999999-999999
                                        'fee': '0',                     # 1 => 0 (leverage trading)
                                        'lossGain': '0', 
                                        'orderId': order_id,            # B9999-999999-999999 
                                        'positionId': position_id,      # P9999-999999-999999 
                                        'price': latest_close_price,    # ★
                                        'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                        'side': 'BUY',                  # 'SELL' or 'BUY'
                                        'size': qty, 
                                        'symbol': symbol, 
                                        'timestamp': dt.datetime.utcnow()
                                    }
                                ]
                                                        
                df = pd.DataFrame(position_list)
                # convert data types
                df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                df['executionId'] = df['executionId'].astype(str)   # int64 => string
                df['orderId'] = df['orderId'].astype(str)           # int64 => string
                df['positionId'] = df['positionId'].astype(str)     # int64 => string
                df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                msg_code = 'OK'
                msg_str = 'Normal Return'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")             
                return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')
            # end of if not self.coin.real_mode:
            
            ### real mode
    
            df = pd.DataFrame()
    
            # exe_buy_order_market(qty: float) -> tuple:    return (status, res_order_id, msg_code, msg_str)
            status, order_id, msg_code, msg_str = self.exe_buy_order_market(qty)    # ★ FAK (Fill and Kill) 
            # status: 0(ok), 1(Trading margin is insufficient), ?(misc)            
            
            # error ?
            if status != 0:
                return (status, df, msg_code, msg_str)    # (1, df, 'msg_code', 'msg_str') 
    
            i = 0
    
            while True:
                i += 1
    
                if i > self.coin.api_market_timeout_limit:     # time out ?
                    status = 8  # time out
                    msg_code = 'ERR-888'
                    msg_str = f'get_price() time out error: loop count={i} '
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return =>  (8, df, 'ERR-888', 'get_price() time out error:...')              
    
                # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:   return (status, order_status)     
                status, order_status = self.get_order_status_try(order_id, qty, price)  
                # status: # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED, ERROR
    
                if status != 0:
                    msg_code = 'ERR-999'
                    msg_str = f"get_order_status({order_id=}) error ▶ {status=}, {order_status=}  "       
                    return (status, df, msg_code, msg_str)    # error return 
                
                ### status == 0 : normal return              
    
                # exe_buy_order_market() is pending status
                if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_buy_order_market_wait(): get_order_status({i}) ▶ {status=}, {order_status=} ")
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_order_status_try()
    
                ### order_status : EXECUTED or EXPIRED or CANCELED                 
    
                ### get_order_status() ▶ status==0 & order_status in 'EXECUTED(qty==real_qty), EXPIRED(qty > real_qty), CANCELED(real_qty==0)'         
                sleep(1)            # sleep 1 sec 
    
                # get_price_try(order_id: str, qty: float, price: float, latest_close_price=0.0, debug1=False, debug2=False) -> tuple:  return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
                status, df, msg_code, msg_str = self.get_price_try(order_id, qty, price)  # qty and price are used for fake mode         
                # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
                self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_buy_order_market_wait(): get_price({i}) ▶ {status=}, {df.shape=} ")
    
                # error ?
                if status != 0:     
                    # empty dataframe ?
                    if status == 9: 
                        sleep(1)        # sleep 1 sec 
                        continue        # continue get_price()
                    
                    # misc error 
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return (status, df, msg_code, msg_str)    # error return                    
                          
                if df.shape[0] == 0:
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_price()                                             
    
                ### Normal Return    
    
                # -------------------------------------------------------------
                #  #   Column       Dtype         
                # -------------------------------------------------------------       
                #  0   executionId  int64         
                #  1   fee          float64       
                #  2   lossGain     float64                               
                #  3   orderId      object(string)       
                #  4   positionId   object(string)              
                #  5   price        float64  
                #  6   settleType   object('OPEN' or 'CLOSE')  
                #  7   side         object('BUY' or 'SELL')                  
                #  8   size         float64           
                #  9   symbol       object('BTC')                        
                # 10   timestamp    datetime64[ns]   
                # ------------------------------------------------------------          
    
                # real_buy_qty = df.iloc[0]['size']
                real_buy_price = df.iloc[0]['price']
                # real_buy_fee = df.iloc[0]['fee']
    
                if real_buy_price > 0:    
                    msg_code = 'OK'
                    msg_str = 'Normal Return'
                    break   # got a real price ? => normal return
                                       
                sleep(1)    # sleep 1 sec
                # continue to get_price()
            # end of while True:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
            # normal return
            return (status, df, msg_code, msg_str)    # (0, df, 'OK', 'Normal Return')     
        
        #################################################### 
        def exe_buy_order_market(self, qty: float) -> tuple:    # return (status, res_order_id, msg_code, msg_str) 
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}' 
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # Buy MMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC} ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake/Test buy order') 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'MARKET',  # Required (MARKET, LIMIT, STOP) 
                'timeInForce': 'FAK',       # FAK (Fill and Kill)
                'size': qty_str             # Required (1.1234 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_order_market() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
            finally:    # block lets you execute code, regardless of the result of the try and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''    
            msg_code = ''
            msg_str = ''
        
            if status == 0:
                res_order_id = dict.get('data')  # string type 
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_order_market({qty=:,.{_q_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()        
    
            else:   # status == ?        
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-201'
                msg_str = msg_list[0].get('message_string')     # 'Trading margin is insufficient'
    
                if msg_code == 'ERR-201':    # Trading margin is insufficient
                    pass
                else:   # misc error
                    ################################################################### debug info
                    print('-'*100, 'Begin: dump response from exe_buy_order_market()')
                    print(dict) 
                    print('-'*100, 'End  : dump response from exe_buy_order_market()')    
                    ################################################################### debug info    
                                
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str} ")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '99999', 'OK', 'Normal Return') or (1, '',  'ERR-201', 'Trading margin is insufficient')  
    
        ########################################################################################## 
        def exe_sell_close_order_limit(self, position_id: str, qty: float, price: float) -> tuple:   # return (status, res_order_id, msg_code, msg_str)   
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell close order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")            
                return (status, res_order_id, msg_code, msg_str)  # (0, 'S9999-999999-999999', 'OK', 'Fake/Test sell close order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/closeOrder'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC_JPY, ETH_JPY, LTC_JPY,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAS (Fill and Store)
                'price': price_str,         # Required (9999999 YEN)        
                'settlePosition': [
                    {
                        'positionId': position_id,      # 162401271 
                        'size': qty_str                 # 0.01
                    }
                ]
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_close_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_close_order_limit({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?         
    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-???'
                msg_str = msg_list[0].get('message_string')     # 'Error Message...'     
               
                ########################################################################## debug info
                print('-'*100, 'Begin: dump response from exe_sell_close_order_limit()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_sell_close_order_limit()')    
                ########################################################################## debug info                 
            
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-???', 'Error Message')

    click image to zoom!
    図6-1
    図6-1はデモプログラムの実行結果です。 ここではApiクラスのexe_buy_order_market_wait()メソッドで成行きの買い注文(XRP_JPY, 注文数=10)を出しています。 成行き注文が約定すると価格(レート)、数量、注文ID、ポジションID等が返されます。 ここでは数量を最小値の「10」にしているので分割されることはありません。

    約定した価格に「1.0035」を掛けて売値を計算したら、 exe_sell_close_order_limit()メソッドを発行して指値で売りポジションを決済します。 次にget_order_status_try()メソッドで指値の売り注文のステータスをチェックします。 売り注文が約定したときは「EXECUTED」が返されます。 売り注文が約定したことを確認したらget_price_try()メソッドを発行して約定した価格(レート)、数量、注文ID等を取得します。

    ここではまだ約定していないので1分間待って再試行しています。


    click image to zoom!
    図6-2
    図6-2は指値の売り注文が約定したときの画面です。 このときは10分くらいで約定しました。


    click image to zoom!
    図6-3
    図6-3はGMOコインの画面です。 2円の利益になっています。 数量が「10」なので金額は無視してください。



    こちらがデモプログラムのソースコードです。 このデモプログラムはBOTの「買い▶売り」の処理を単純化したものです。 BOTを使用すると、このデモプログラムのような処理を自動的に行います。

    api_class_buy_sell_demo.py:
    # buy_sell api demo
    from time import sleep    # import time
    import numpy as np
    import pandas as pd
    
    from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe
    from lib.debug import Debug                 # dependencies Gvar
    from lib.api import Api                     # dependencies Gvar, Coin
    
    ### [1] instantiate Gvar, Coin, Api classes
    gvar = Gvar()
    gvar.real_mode = True           # ★
    gvar.callers_method_name = ''
    print('-'*210)
    print(gvar)
    print('-'*210)
    
    coin = Coin()
    coin.symbol = 'XRP_JPY'         # ★
    coin.real_mode = True           # ★
    coin.trade_type = 'buy_sell'    # ★
    coin.qty = 10                   # ★
    coin.qty_decimal = 0
    coin.price_decimal = 3    
    print(coin)
    print('-'*210)
    
    api = Api(gvar, coin)
    print(api)
    print('-'*210)
    
    ### [2] get latest market data from GMO Coin
    status, mas_df = api.get_crypto_try(page=1, count=100)
    mas_df = pd.DataFrame(mas_df)   # cast(mas_df)
    if status != 0:
        print(f"api.get_crypto_try(page=1, count=100) ▶ {status=}, quit... ")
        quit()
    
    if mas_df.shape[0] == 0:
        print(f"api.get_crypto_try(page=1, count=100) ▶ {mas_df.shape[0]=}, quit... ")
        quit()
    
    filter = mas_df['side'] == 'BUY'
    buy_mas_df = mas_df[filter]
    print(f'{Bcolors.OKGREEN}gmo buy master dataframe: buy_df.tail(1)')
    print(buy_mas_df.tail(1))
    print(f'{Bcolors.ENDC}', end='')
    print('-'*210)
    
    #  buy_mas_df:
    # ------------------------------------------------------------------------------------
    #  #   Column     Dtype              
    #  -----------------------------------------------------------------------------------        
    #  0   price      float64   buy/sell price           
    #  1   side       object    'BUY' or 'SELL'             
    #  2   size       float64            
    #  3   timestamp  datetime64[ns, UTC] ★ UTC descending order
    #  -----------------------------------------------------------------------------------
    
    ### [3] execute buy order market
    
    buy_qty = coin.qty
    last_buy_price = buy_mas_df.iloc[-1]['price']
    latest_close_price = last_buy_price
    
    # exe_buy_order_market_wait(qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)
    (status, res_df, msg_code, msg_str) = api.exe_buy_order_market_wait(buy_qty, last_buy_price, latest_close_price)    # last_buy_price, latest_close_price are used for fake mode only
    res_df = pd.DataFrame(res_df)   # cast(df)
    if status != 0:
        print(f"exe_buy_order_market_wait({buy_qty=:,.0f}, {last_buy_price=:,.3f}, {latest_close_price=:,.3f}) ▶ {status=}, quit... ")
        quit()
    
    ### print buy order [marker] response dataframe
    
    print('-'*210)
    print(f'{Bcolors.OKGREEN}buy order response dataframe: res_df')
    print(res_df)
    print(f'{Bcolors.ENDC}', end='')
    print('-'*210)
    
    ### print buy order (market) info
    
    buy_order_id = res_df.iloc[0]['orderId']
    position_id = res_df.iloc[0]['positionId']
    buy_real_price = res_df.iloc[0]['price']
    buy_real_qty = res_df.iloc[0]['size']
    
    print(f"....  {Bcolors.HEADER}{buy_order_id=}, {position_id=}, {buy_real_qty=:,.0f}, {buy_real_price=:,.3f}{Bcolors.ENDC} ")
    print('-'*210)
    
    ### [4] execute sell close order (limit) 
    
    sell_position_id = position_id
    sell_qty = buy_real_qty
    sell_price = buy_real_price * 1.0035    # calculate a sell price
    
    # exe_sell_close_order_limit(position_id: str, qty: float, price: float) -> tuple:   # return (status, res_order_id, msg_code, msg_str)
    (status, sell_order_id, msg_code, msg_str) = api.exe_sell_close_order_limit(sell_position_id, sell_qty, sell_price)
    if status != 0:
        print(f"exe_sell_close_order_limit({position_id=}, {sell_qty=:,.0f}, {sell_price=:,.3f}) ▶ {status=}, quit... ")
        quit()
    
    ### [5] get sell close order status 
    i = 0
    while True:
        i += 1
        # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status) 
        (status, order_status) = api.get_order_status_try(sell_order_id, sell_qty, sell_price)  # sell_qty, sell_price are used for fake mode only 
        # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
        # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED    
    
        if status != 0:
            print(f"get_order_status_try({sell_order_id=}, {sell_qty=:,.0f}, {sell_price=:,.3f}) ▶ {status=}, quit... ")
            quit()
    
        if order_status in 'EXECUTED,EXPIRED,CANCELED': break 
    
        # if i > 5: quit()
    
        print(f"....  get_order_status_try({sell_order_id=}) ▶ {order_status=}, sleep 1 minute and retry({i=})... ")
        sleep(60)    # sleep 1 minute  
    
        # continue to get order status
    # end of while True:
    
    if order_status in 'EXPIRED,CANCELED':
        print(f"....  get_order_status_try({sell_order_id=}) ▶ {order_status=}, quit... ") 
        quit()
    
    ### [6] get a sell price : sell close order has been executed 
    i = 0
    while True:
        i += 1
        ### get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
        (sell_bk_df, buy_bk_df) = api.get_orderbooks_try()
        sell_bk_df = pd.DataFrame(sell_bk_df)   # 100, 110, 120, 130, 140, 150 (low to high) ascending order of sell price
        buy_bk_df = pd.DataFrame(buy_bk_df)     # 150, 140, 130, 120, 110, 100 (high to low) descending order of buy price
    
        if sell_bk_df.shape[0] == 0 or buy_bk_df.shape[0] == 0:
            print(f"get_orderbooks_try() ▶ {sell_bk_df.shape[0]=}, {buy_bk_df.shape[0]=}, quit... ")
            quit()
    
        ### get the latest buy price from orderbooks
        latest_buy_bk_price = buy_bk_df.iloc[0]['price']    # 150
    
        ### get a sell position number
        filter_mask = sell_bk_df['price'] < sell_price      # < 120
        dfx = sell_bk_df[filter_mask]   # 100, 110
        sell_position = dfx.shape[0]    # 2
        coin.sell_position_list.append(sell_position)   # append sell position [2]
        reverse_sell_position_list = coin.sell_position_list[::-1]
    
        print(f"....  {reverse_sell_position_list=} ")
    
        ### get a sell price
        # get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)     
        (status, res_df, msg_code, msg_str) = api.get_price_try(sell_order_id, sell_qty, sell_price, latest_buy_bk_price)    # sell_qty, sell_price, latest_buy_bk_price are used for fake mode only    
        # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
        # msg_code: OK, ERR-888, ERR-999  
    
        # empty dataframe ?    
        if status == 9:
            print(f"....  get_price_try({sell_order_id=}, {sell_qty=:,.0f}, {sell_price=:,.3f}, {latest_buy_bk_price=:,.3f}) ▶ {status=}, sleep 1 minute & retry({i=}) ... ")
            print(f"....  exe_sell_close_order_limit() close condition [{Bcolors.WARNING}{sell_price=:,.3f} <= {latest_buy_bk_price=:,.3f}{Bcolors.ENDC}] ")        
            sleep(60)   # sleep 1 minute
            continue   
    
        if status != 0: # 8 or 9 or ?
            print(f"....  get_price_try({sell_order_id=}, {sell_qty=:,.0f}, {sell_price=:,.3f}, {latest_buy_bk_price=:,.3f}) ▶ {status=}, quit... ")
            quit()      
    
        break   # if normal return then exit
    # end of while True:
    
    ### [7] get a sell price and print it 
    res_df = pd.DataFrame(res_df)   # cast res_df
    print('-'*210)    
    print(f'{Bcolors.OKGREEN}get price response dataframe: res_df')
    print(res_df)    
    print(f'{Bcolors.ENDC}', end='')
    print('-'*210)
    
    sell_order_id = res_df.iloc[0]['orderId']
    sell_real_qty = res_df.iloc[0]['size']
    sell_real_price = res_df.iloc[0]['price']
    
    print(f"....  {Bcolors.HEADER}{sell_order_id=}, {sell_real_qty=:,.0f}, {sell_real_price=:,.3f}{Bcolors.ENDC} ")
    print('-'*210)

  7. Apiクラスにexe_sell_order_market_wait(), exe_sell_order_market(), exe_buy_close_order_limit()メソッドを追加する

    ここでは、Coinクラスのtran_typeプロパティに「sell_buy」を設定したときに使用されるApiのメソッドを追加します。 「空売り▶買戻し」の注文を行う場合、BOTは空売りの成行き注文を行って約定したら買い戻しの注文を指値で行います。

    exe_sell_order_market_wait()は親メソッドで、exe_sell_order_market()は子メソッドです。 親メソッドでは空売り成行き注文が約定するまで一定時間待ちます。 そして約定したら約定情報をPandasのDataFrameに格納して返します。

    BOTは買い戻しのタイミングを検知したら、 exe_buy_close_order_limit()メソッドを実行して指値の買い注文を出します。

    api.py:
        ################################################################################################ 
        def exe_sell_order_market_wait(self, qty: float, price: float, latest_close_price=0.0) -> tuple: # return (status, df, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                if latest_close_price == 0.0: latest_close_price = price
                status = 0
                execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                order_id     = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)  
                position_id  = 'P' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PMMDD-HHMMSS-999999(milliseconds) ★
                position_list = [                         
                                    {
                                        'executionId': execution_id,    # E9999-999999-999999
                                        'fee': '0',                     # 1 => 0 (leverage traing) 
                                        'lossGain': '0', 
                                        'orderId': order_id,            # S9999-999999-999999
                                        'positionId': position_id,      # P9999-999999-999999 ★
                                        'price': latest_close_price,    # ★
                                        'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                        'side': 'SELL',                 # 'SELL' or 'BUY'
                                        'size': qty, 
                                        'symbol': symbol, 
                                        'timestamp': dt.datetime.utcnow()
                                    }
                                ]
                                                        
                df = pd.DataFrame(position_list)
                # convert data types
                df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                df['executionId'] = df['executionId'].astype(str)   # int64 => string
                df['orderId'] = df['orderId'].astype(str)           # int64 => string
                df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                msg_code = 'OK'
                msg_str = 'Normal Return'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC} ")                       
                return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')                 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            df = pd.DataFrame()   
    
            status, order_id, msg_code, msg_str = self.exe_sell_order_market(qty)   # ★ FAK (Fill and Kill)   
    
            # error ?
            if status != 0: 
                return status, df, msg_code, msg_str    # (1, df, 'msg_code', 'msg_str') 
    
            i = 0
    
            while True:
                i += 1
         
                if i > self.coin.api_market_timeout_limit:  # time out ?
                    status = 8      # time out
                    msg_code = 'ERR-888'
                    msg_str = f'get_price() time out error: loop count={i} '
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return =>  (8, df, 'ERR-888', 'get_price() time out error:...')  
    
                # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)    
                status, order_status = self.get_order_status_try(order_id, qty, price)  
                # status: # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED, ERROR            
                if status != 0:
                    msg_code = 'ERR-999'
                    msg_str = f"get_order_status({order_id=}) error ▶ {status=}, {order_status=} "       
                    return status, df, msg_code, msg_str    # error return 
                
                ### status == 0    
    
                # exe_sell_order_market() is pending status
                if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_sell_order_market_wait(): get_order_status({i}) ▶ {status=}, {order_status=} ")
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_order_status()
    
                ### order_staus : EXECUTED or EXPIRED or CANCELED
                
                ### get_order_status() ▶ status==0 & order_status in 'EXECUTED,EXPIRED,CANCELED'  
                sleep(1)            # sleep 1 sec            
    
                # get_price_try(order_id: str, qty: float, price: float, latest_close_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
                status, df, msg_code, msg_str = self.get_price_try(order_id, qty, price)  # qty and price are used for fake mode    
                # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)    
                self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_sell_order_market_wait(): get_price({i}) ▶ {status=}, {df.shape=} ")                                           
               
                # error ?
                if status != 0: 
                    # DataFrame is empty ?
                    if status == 9: 
                        sleep(1)        # sleep 1 sec 
                        continue        # continue get_price()
                    
                    # misc error                
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return            
    
                if df.shape[0] == 0:
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_price()
    
                ### Normal Return           
    
                # -----------------------------------------------------
                #  #   Column       Dtype         
                # -----------------------------------------------------         
                #  0   executionId  int64         
                #  1   fee          float64       
                #  2   lossGain     float64                               
                #  3   orderId      object(string)       
                #  4   positionId   object(string)               
                #  5   price        float64  
                #  6   settleType   object('OPEN' or 'CLOSE')  
                #  7   side         object('BUY' or 'SELL')                  
                #  8   size         float64           
                #  9   symbol       object('BTC_JPY')                        
                # 10   timestamp    datetime64[ns]          
                # ---------------------------------------------------- 
    
                # real_sell_qty = df.iloc[0]['size']
                real_sell_price = df.iloc[0]['price']
                # real_sell_fee = df.iloc[0]['fee']            
     
                if real_sell_price > 0:   
                    msg_code = 'OK'
                    msg_str = 'Normal Return'                
                    break   # got a real price ?     
      
                sleep(1)        # sleep 1 sec
                # continue to get_order_status_try()
            # end of while True:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str}")
            # normal or time out error return
            return (status, df, msg_code, msg_str)    # (0, df, 'OK', 'Normal Return') 
    
        ##################################################### 
        def exe_sell_order_market(self, qty: float) -> tuple:    # return (status, res_order_id, msg_code, msg_str)     
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC}  ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'S9999-999999-999999', 'OK', 'Fake\Test sell order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode 
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'MARKET',  # Required (MARKET, LIMIT, STOP) 
                'timeInForce': 'FAK',       # FAK (Fill and Kill)
                'size': qty_str             # Required (1 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_order_market() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
    
            status = dict.get('status')     # int type    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_order_market({qty=:,.{_q_}f}): ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # ERR-208
                msg_str = msg_list[0].get('message_string')     # Exceeds the available balance
    
                if msg_code == 'ERR-208':   # Exceeds the available balance
                    pass
                else:        
                    ##################################################################### debug info
                    print('-'*100, 'Begin: dump response from exe_sell_order_market()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_sell_order_market()')    
                    ##################################################################### debug info                     
    
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-201', 'Exceeds the available balance')     
    
        ######################################################################################## 
        def exe_buy_close_order_limit(self, position_id: str, qty: float, price: float) ->tuple:    # return (status, res_order_id, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy close order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")            
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake/Test close order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/closeOrder'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC_JPY, ETH_JPY, LTC_JPY,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999 YEN)        
                'settlePosition': [
                    {
                        'positionId': position_id,      # 162401271 
                        'size': qty_str                 # 0.01
                    }
                ]
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_close_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_close_order_limit({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?         
    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-???'
                msg_str = msg_list[0].get('message_string')     # 'Error Message...'     
               
                ######################################################################### debug info
                print('-'*100, 'Begin: dump response from exe_buy_close_order_limit()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_buy_close_order_limit()')    
                ######################################################################### debug info                 
            
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-???', 'Error Message')

    click image to zoom!
    図7-1
    図7-1はデモプログラムの実行結果です。 ここではApiクラスのexe_sell_order_market_wait()メソッドで成行きの空売り注文(XRP_JPY, 注文数=10)を出しています。 成行き注文が約定すると価格(レート)、数量、注文ID、ポジションID等が返されます。 ここでは数量を最小値の「10」にしているので分割されることはありません。

    約定した価格に「0.0065」を掛けて買値を計算したら、 exe_buy_close_order_limit()メソッドを発行して指値で買いポジションを決済します。 次にget_order_status_try()メソッドで指値の買い注文のステータスをチェックします。 買い注文が約定したときは「EXECUTED」が返されます。 買い注文が約定したことを確認したらget_price_try()メソッドを発行して約定した価格(レート)、数量、注文ID等を取得します。

    ここではまだ約定していないので1分間待って再試行しています。


    click image to zoom!
    図7-2
    図7-2は指値の売り注文が約定したときの画面です。 このときは15分くらいで約定しました。


    click image to zoom!
    図7-3
    図7-3はGMOコインの画面です。 2円の利益になっています。 注文数量が「10」なので金額は無視してください。



    こちらがデモプログラムのソースコードです。 このデモプログラムはBOTの「空売り▶買戻し」の処理を単純化したものです。 BOTを使用すると、このデモプログラムのような処理を自動的に行います。

    api_class_sell_buy_demo.py:
    # sell_buy api demo 
    from time import sleep    # import time
    import numpy as np
    import pandas as pd
    
    from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe
    from lib.debug import Debug                 # dependencies Gvar
    from lib.api import Api                     # dependencies Gvar, Coin
    
    ### [1] instantiate Gvar, Coin, Api classes
    gvar = Gvar()
    gvar.real_mode = True           # ★
    gvar.callers_method_name = ''
    print('-'*210)
    print(gvar)
    print('-'*210)
    
    coin = Coin()
    coin.symbol = 'XRP_JPY'         # ★
    coin.real_mode = True           # ★
    coin.trade_type = 'sell_buy'    # ★
    coin.qty = 10                   # ★
    coin.qty_decimal = 0
    coin.price_decimal = 3    
    print(coin)
    print('-'*210)
    
    api = Api(gvar, coin)
    print(api)
    print('-'*210)
    
    ### [2] get latest market data from GMO Coin
    status, mas_df = api.get_crypto_try(page=1, count=100)
    mas_df = pd.DataFrame(mas_df)   # cast(mas_df)
    if status != 0:
        print(f"api.get_crypto_try(page=1, count=100) ▶ {status=}, quit... ")
        quit()
    
    if mas_df.shape[0] == 0:
        print(f"api.get_crypto_try(page=1, count=100) ▶ {mas_df.shape[0]=}, quit... ")
        quit()
    
    filter = mas_df['side'] == 'SELL'
    sell_mas_df = mas_df[filter]
    print(f'{Bcolors.OKGREEN}gmo sell master dataframe: sell_df.tail(1)')
    print(sell_mas_df.tail(1))
    print(f'{Bcolors.ENDC}', end='')
    print('-'*210)
    
    #  sell_mas_df:
    # ------------------------------------------------------------------------------------
    #  #   Column     Dtype              
    #  -----------------------------------------------------------------------------------        
    #  0   price      float64   buy/sell price           
    #  1   side       object    'BUY' or 'SELL'             
    #  2   size       float64            
    #  3   timestamp  datetime64[ns, UTC] ★ UTC descending order
    #  -----------------------------------------------------------------------------------
    
    ### [3] execute sell order (market)
    
    sell_qty = coin.qty
    last_sell_price = sell_mas_df.iloc[-1]['price']
    latest_close_price = last_sell_price
    
    # exe_sell_order_market_wait(qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)
    (status, res_df, msg_code, msg_str) = api.exe_sell_order_market_wait(sell_qty, last_sell_price, latest_close_price)    # last_sell_price, latest_close_price are used for fake mode only
    res_df = pd.DataFrame(res_df)   # cast(df)
    if status != 0:
        print(f"exe_sell_order_market_wait({sell_qty=:,.0f}, {last_sell_price=:,.3f}, {latest_close_price=:,.3f}) ▶ {status=}, quit... ")
        quit()
    
    ### print executed sell order response dataframe
    
    print('-'*210)
    print(f'{Bcolors.OKGREEN}sell order response dataframe: res_df')
    print(res_df)
    print(f'{Bcolors.ENDC}', end='')
    print('-'*210)
    
    ###  print sell order (market) info
    sell_order_id = res_df.iloc[0]['orderId']
    position_id = res_df.iloc[0]['positionId']
    sell_real_price = res_df.iloc[0]['price']
    sell_real_qty = res_df.iloc[0]['size']
    
    print(f"....  {Bcolors.HEADER}{sell_order_id=}, {position_id=}, {sell_real_qty=:,.0f}, {sell_real_price=:,.3f}{Bcolors.ENDC} ")
    print('-'*210)
    
    ### [4] execute buy close order (limit) 
    
    buy_position_id = position_id
    buy_qty = sell_real_qty
    buy_price = sell_real_price -  (sell_real_price * 0.0035)   # calculate buy price
    
    # exe_buy_close_order_limit(position_id: str, qty: float, price: float) -> tuple:   # return (status, res_order_id, msg_code, msg_str)
    (status, buy_order_id, msg_code, msg_str) = api.exe_buy_close_order_limit(buy_position_id, buy_qty, buy_price)
    if status != 0:
        print(f"exe_buy_close_order_limit({position_id=}, {buy_qty=:,.0f}, {buy_price=:,.3f}) ▶ {status=}, quit... ")
        quit()
    
    ### [5] get buy close order status 
    i = 0
    while True:
        i += 1
        # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status) 
        (status, order_status) = api.get_order_status_try(buy_order_id, buy_qty, buy_price) 
        # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
        # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED    
    
        if status != 0:
            print(f"get_order_status_try({buy_order_id=}, {buy_qty=:,.0f}, {buy_price=:,.3f}) ▶ {status=}, quit... ")
            quit()
    
        if order_status in 'EXECUTED,EXPIRED,CANCELED': break 
    
        print(f"....  get_order_status_try({buy_order_id=}) ▶ {order_status=}, sleep 1 minute and retry({i=}) ... ")
        sleep(60)    # sleep 1 minute  
        
        # if i > 5: quit()
    
        # continue to get order status
    # end of while True:
    
    if order_status in 'EXPIRED,CANCELED':
        print(f"....  get_order_status_try({buy_order_id=}) ▶ {order_status=}, quit... ") 
        quit()
    
    ### [6] get a buy price : buy order has been executed 
    i = 0
    while True:
        ### get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
        (sell_bk_df, buy_bk_df) = api.get_orderbooks_try()
        sell_bk_df = pd.DataFrame(sell_bk_df)   # 100, 110, 120, 130, 140, 150 (low to high) ascending order of sell price
        buy_bk_df = pd.DataFrame(buy_bk_df)     # 150, 140, 130, 120, 110, 100 (high to low) descending order of buy price
    
        if sell_bk_df.shape[0] == 0 or buy_bk_df.shape[0] == 0:
            print(f"get_orderbooks_try() ▶ {sell_bk_df.shape[0]=}, {buy_bk_df.shape[0]=}, quit... ")
            quit()
    
        ### get the latest sell price from orderbooks
        latest_sell_bk_price = sell_bk_df.iloc[-1]['price']     # 150
    
        ### get a buy position number
        buy_bk_df.sort_values('price', ascending=True, inplace=True)    # sort in ascending order of price   
        filter_mask = buy_bk_df['price'] > buy_price            # > 120
        dfx = buy_bk_df[filter_mask]   # 130, 140, 150
        buy_position = dfx.shape[0]    # 3
        coin.buy_position_list.append(buy_position)   # append buy position [3]
        reverse_buy_position_list = coin.buy_position_list[::-1]
    
        print(f"....  {reverse_buy_position_list=} ")
    
        # get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str) 
        (status, res_df, msg_code, msg_str) = api.get_price_try(buy_order_id, buy_qty, buy_price, latest_sell_bk_price)    
        # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
        # msg_code: OK, ERR-888, ERR-999  
    
        # empty dataframe ?    
        if status == 9:
            print(f"....  get_price_try({buy_order_id=}, {buy_qty=:,.0f}, {buy_price=:,.3f}, {latest_sell_bk_price=:,.3f}) ▶ {status=}, sleep 1 minute & retry({i=}) ... ")
            print(f"....  exe_buy_close_order_limit() close condition [{Bcolors.WARNING}{buy_price=:,.3f} >= {latest_sell_bk_price=:,.3f}{Bcolors.ENDC}] ")        
            sleep(60)   # sleep 1 minute
            continue   
    
    
        if status != 0: # 8 or 9 or ?
            print(f"....  get_price_try({buy_order_id=}, {buy_qty=:,.0f}, {buy_price=:,.3f}, {latest_sell_bk_price=:,.3f}) ▶ {status=}, quit... ")
            quit()      
    
        break   # if normal return then exit
    # end of while True:
    
    ### [7] get a buy price and print it
    res_df = pd.DataFrame(res_df)   # cast res_df
    print('-'*210)    
    print(f'{Bcolors.OKGREEN}get price response dataframe: res_df')
    print(res_df)    
    print(f'{Bcolors.ENDC}', end='')
    print('-'*210)
    
    buy_order_id = res_df.iloc[0]['orderId']
    buy_real_qty = res_df.iloc[0]['size']
    buy_real_price = res_df.iloc[0]['price']
    
    print(f"....  {Bcolors.HEADER}{buy_order_id=}, {buy_real_qty=:,.0f}, {buy_real_price=:,.3f}{Bcolors.ENDC} ")
    print('-'*210)
  8. Apiクラスにexe_buy_order_limit(), exe_sell_order_limit()メソッドを追加する

    ここではApiクラスにexe_buy_order_limit(), exe_sell_order_limit()メソッドを追加します。 これらのメソッドは仮想通貨の新規注文を指値で行うときに使います。 これらのメッソは現物取引、レバレッジ取引とも可能です。 BOTではこれらのメソッドは使用していません。

    api.py:
        ################################################################## 
        def exe_buy_order_limit(self,  qty: float, price: float) -> tuple:       # return (status, res_order_id, msg_code, msg_str)   
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC} ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake/Test Buy order') 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999)        
                'size': qty_str             # Required (1.1234 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string 
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type 
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_order_limit({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()        
    
            else:   # status == ? 
                ### {'status': 1, 'messages': [{'message_code': 'ERR-5120', 'message_string': 'Too high price.'}], 'responsetime': '2022-09-26T05:00:58.198Z'}
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')
                msg_str = msg_list[0].get('message_string')
    
                if msg_code == 'ERR-201':   # Trading margin is insufficient
                    pass
                else:
                    ################################################################# debug info
                    print('-'*100, 'Begin: dump response from exe_buy_order_limit()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_buy_order_limit()')    
                    ################################################################# debug info
    
            # end of if status == 0:                        
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str} ")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '99999', 'OK', 'Normal Return') or (1, '', 'ERR-201', 'Trading margin is insufficient')
    
        ################################################################## 
        def exe_sell_order_limit(self, qty: float, price: float) -> tuple:       # return (status, res_order_id, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}  ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'S9999-999999-999999', 'OK', 'Fake/Test sell order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999 YEN)        
                'size': qty_str             # Required (1 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_order_limit({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?         
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-208'
                msg_str = msg_list[0].get('message_string')     # 'Exceeds the available balance'     
    
                if msg_code == 'ERR-208':   # Exceeds the available balance  
                    pass
                else:
                    #################################################################### debug info
                    print('-'*100, 'Begin: dump response from exe_sell_order_limit()')
                    print(dict) 
                    print('-'*100, 'End  : dump response from exe_sell_order_limit()')    
                    #################################################################### debug info                 
            
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-5120', 'Too high price')

    click image to zoom!
    図8-1
    図8-1ではexe_buy_order_limit()メソッドを実行して指値の買い注文を行っています。 ここではCoinクラスのsymbolプロパティに「XRP_JPY」を設定しているのでレバレッジ取引の買い注文になります。


    click image to zoom!
    図8-2
    図8-2ではexe_sell_order_limit()メソッドを実行して指値の売り注文を行っています。 ここではCoinクラスのsymbolプロパティに「XRP_JPY」を設定しているのでレバレッジ取引の売り注文になります。


  9. Apiクラスにget_order_status_try(), get_order_status()メソッドを追加する

    ここではApiクラスにget_order_status_try(), get_order_status()メソッドを追加します。 get_order_status_try()は親メソッドで、get_order_status()は子メソッドです。 get_order_status()でタイムアウト等のエラーが発生するとCoinクラスに設定した回数分再試行します。

    get_order_status_try()メソッドからは、 戻り値として「WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED」のいずれかが返ります。

    「EXECUTED」は注文が約定したことを意味します。 「EXPIRED」は成行き注文のとき「FAK: Fill and Kill」が適用されて、 数量が全数量約定しなかったことを意味します。 「CANCELED」は注文がキャンセルされたことを意味します。

    api.py:
        ################################################################################ 
        def get_order_status_try(self, order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)     
    
            retry_count = 0
    
            # retry N times if invalid or connection or time out errors
            while True:
                status, order_status = self.get_order_status(order_id, qty, price)   # return (status, order_status)      
                # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED
    
                # invalid request (requests are too many), connection error, time out error ?  
                if status == 4 or status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~61 seconds  
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_order_status({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ")                       
                        sleep(sec)    # sleep 3~61 seconds                                   
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (status, order_status)   # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)    
        
        ############################################################################    
        def get_order_status(self, order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)            
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            if not self.coin.real_mode: 
                status = 0
                order_status = 'EXECUTED'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_status=} {Bcolors.FAIL}FAKE{Bcolors.ENDC} ") 
                return (status, order_status)
            # end of if not self.coin.real_mode: 
    
            ### real mode
    
            status = 9  # misc rrror
    
            while True:            
                timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
                method    = 'GET'
                endPoint  = 'https://api.coin.z.com/private'
                path      = '/v1/orders'
    
                url = timestamp + method + path
                sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
                parameters = {
                    'orderId': order_id # '2478774888' : DO NOT USE multiple orderId  '2478774888','2478774999'  max 10 orderId     
                }
    
                headers = {
                    'API-KEY': self.api_key,
                    'API-TIMESTAMP': timestamp,
                    'API-SIGN': sign
                }
    
                try:
                    res_dict = requests.get(endPoint + path, headers=headers, params=parameters, timeout=3)
    
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)               
                    status = 9              # misc error
                    order_status = 'ERROR'  # http error   
                    break   # error return                     
    
                except requests.exceptions.ConnectionError as errc:    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {errc}  ")  
                    status = 8              # retry error  
                    order_status = 'ERROR'  # connection error   
                    break   # error return                                            
    
                except requests.exceptions.Timeout as errt:                    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                     
                    status = 8              # retry error
                    order_status = 'ERROR'  # timeout error   
                    break   # error return                
      
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)               
                    status = 9              # misc error
                    order_status = 'ERROR'  # exception error   
                    break   # error return                  
    
                except:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ return...")
                    self.debug.sound_alert(3)                          
                    status = 9              # misc error
                    order_status = 'ERROR'  # misc exception error   
                    break   # error return                 
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try- and except block
                    pass
    
                dict = res_dict.json()
                status = dict.get('status')    # 0 or 1
    
                # {
                #   "status": 0,
                #   "data": {
                #     "list": [
                #       {
                #         "executedSize": "1000",
                #         "executionType": "MARKET",
                #         "losscutPrice": "0",
                #         "orderId": 2478774888,
                #         "orderType": "NORMAL",
                #         "price": "0",
                #         "rootOrderId": 2478774888,
                #         "settleType": "OPEN",
                #         "side": "BUY",
                #         "size": "1000",
                #         "status": "EXECUTED", # WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED
                #         "symbol": "XRP_JPY",
                #         "timeInForce": "FAK",
                #         "timestamp": "2022-05-13T05:27:46.042Z"
                #       }
                #     ]
                #   },
                #   "responsetime": "2022-05-26T00:23:48.767Z"
                # }
    
                # normal return
                if status == 0:
                    data = dict.get('data')
    
                    if len(data) == 0:
                        df = pd.DataFrame()            
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}): ▶ {status=}, {df.shape[0]=} ") 
                        order_status = 'EXPIRED'
                        break   # return (status, order_status)  
    
                    data_list = data.get('list')
                    
                    df = pd.DataFrame(data_list)
                    # convert data types
                    df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True)   # UTC (aware)
    
                    # 'executedSize': "1000" => float  
                    # 'losscutPrice': "0" => float
                    # 'orderId": 2478774888 => object        
                    # 'price': "0" => float
                    # 'rootOrderId': 2478774888 => object  
                    # 'size': "1000" => float  
                
                    df = df.astype(
                            {
                                'executedSize': 'float', 'losscutPrice': 'float', 
                                'orderId': 'object', 'price': 'float',
                                'rootOrderId': 'object', 'size': 'float'
                            }
                        )  
    
                    # Data columns (total 14 columns):
                    # --------------------------------------------------------------
                    #  #   Column         Dtype
                    # ---------------------------------------------------------------
                    #  0   executedSize   float64
                    #  1   executionType  object
                    #  2   losscutPrice   float64
                    #  3   orderId        object
                    #  4   orderType      object
                    #  5   price          float64
                    #  6   rootOrderId    object
                    #  7   settleType     object
                    #  8   side           object
                    #  9   size           float64
                    #  10  status         object
                    #  11  symbol         object
                    #  12  timeInForce    object
                    #  13  timestamp      datetime64[ns, UTC]
                    # ------------------------------------------------------------------
    
                    if df.shape[0] == 0:
                        order_status = 'EXPIRED'
                    else:
                        order_status = df.loc[0, 'status'] # WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED
    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_status=} ")               
    
                elif status == 5:   # Maintenance
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_order_status({order_id=}): ▶ {status=}, quit python...")
                    self.debug.sound_alert(3)  
                    quit()
                
                else:   # status == ?              
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')
                    msg_str = msg_list[0].get('message_string')
    
                    ################################################################## debug info
                    print('-'*100, 'Begin: dump response from get_order_status()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from get_order_status()')    
                    ################################################################## debug info         
    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}): ▶ {status=}, {msg_code} - {msg_str} ")            
                    order_status = 'ERROR'   
                # end of if status == 0: 
                break
            # end of while true:    
            
            return (status, order_status)   # 0(ok), 4(invalid request), 8(retry error), 9(misc error)

    click image to zoom!
    図9
    図9はデモプログラムの実行結果です。 ここではget_order_status_try()メソッドから「EXECUTED」が返されています。


  10. Apiクラスにget_price_try(), get_price()メソッドを追加する

    ここではApiクラスにget_price_try(), get_price()メソッドを追加します。 get_price_try()は親メソッドで、get_price()は子メソッドです。 get_price()でタイムアウト等のエラーが発生したときはCoinクラスに設定した回数分再試行します。

    これらのメソッドは、注文が約定したときに約定価格(レート)、約定数量、注文ID、ポジションID等を取得するために使用します。

    get_price()メソッドの行29-99ではシミュレーションモードの処理を行っています。 ここでは可能な限りリアル取引に近い状況をシミュレーションしています。 行40-45では「buy_sell」の処理をシミュレーションしています。 「買い▶売り」のときは、 自分の売値が売りの板情報の最安値(1番目)で、 かつ買いの板情報の最高値(1番目)より安い(≦)ときに約定させています。

    行51-56では「sell_buy」の処理をシミュレーションしています。 「売り▶買い」のときは、 自分の買値が買いの板情報の最高値(1番目)で、 かつ売りの板情報の最安値(1番目)より高い(≧)ときに約定させています。

    api.py:
        #############################################################################################################################   
        def get_price_try(self, order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
            
            retry_count = 0
    
            # retry N times if invalid or connection or time out errors
            while True:
                status, df, msg_code, msg_str = self.get_price(order_id, qty, price, latest_book_price, debug1, debug2)   # return (status, order_status)      
                # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
                # msg_code: OK, ERR-888, ERR-999           
    
                # connection error, time out error ?  
                if status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~61 seconds       
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_price({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ")                    
                        sleep(sec)    # sleep 3~61 seconds                                   
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (status, df, msg_code, msg_str)  # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
    
        #########################################################################################################################    
        def get_price(self, order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            if not self.coin.real_mode:   
                if latest_book_price == 0.0: latest_book_price = price
                buy_sell = True if self.coin.trade_type == 'buy_sell' else False
                
                return_empty_df = True
    
                if buy_sell:
                    if debug1 or debug2:
                        return_empty_df = False
                    else: # neither debug1 nor debug2
                        # skip empty position list
                        if len(self.coin.sell_position_list) > 0:   
                            # first sell position ?
                            if self.coin.sell_position_list[-1] == 0:
                                # sell_price is less than equal to latest buy orderbooks price ?
                                if price <= latest_book_price:
                                    return_empty_df = False                
                else: # sell_buy
                    if debug1 or debug2:
                        return_empty_df = False
                    else: # neither debug1 nor debug2
                        # skip empty position list
                        if len(self.coin.buy_position_list) > 0:        
                            # first buy position ?
                            if self.coin.buy_position_list[-1] == 0:
                                # buy_price is greater than or equal to latest sell orderbooks price ?
                                if price >= latest_book_price:  
                                    return_empty_df = False                
                # end of if buy_sell:
    
                if not return_empty_df:                 
                    status = 0
                    execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                    position_id  = 'P' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PMMDD-HHMMSS-999999(milliseconds) ★
                    position_list = [                         
                                        {
                                            'executionId': execution_id, 
                                            'fee': '0',                     # 1 => 0 (leverage trading) ★
                                            'lossGain': '0', 
                                            'orderId': order_id, 
                                            'positionId': position_id,      # ★
                                            'price': latest_book_price,     # ★
                                            'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                            'side': 'SELL',                 # 'SELL' or 'BUY'
                                            'size': qty, 
                                            'symbol': symbol, 
                                            'timestamp': dt.datetime.utcnow()
                                        }
                                    ]
                                                            
                    df = pd.DataFrame(position_list)
                    # convert data types
                    df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                    df['executionId'] = df['executionId'].astype(str)   # int64 => string
                    df['orderId'] = df['orderId'].astype(str)           # int64 => string
                    df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                    df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                    msg_code = 'OK'
                    msg_str = 'Normal Return'
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")               
                    return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')         
                else: # return empty dataframe                    
                    df = pd.DataFrame()
                    status = 9                          # empty dataframe
                    msg_code = 'ERR-999'                # null => ERR-999
                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")               
                    return (status, df, msg_code, msg_str)  # (9, df, 'ERR-999', 'DataFrame is empty') 
                # end of if not return_empty_df: 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            status = 9    # internal error or misc error
    
            while True:            
                timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
                method    = 'GET'
                endPoint  = 'https://api.coin.z.com/private'
                path      = '/v1/executions'
    
                url = timestamp + method + path
                sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
                parameters = {'orderId': order_id}         # buy or sell orderId
    
                headers = {
                    "API-KEY": self.api_key,
                    "API-TIMESTAMP": timestamp,
                    "API-SIGN": sign
                }
    
                try:
                    res = requests.get(endPoint + path, headers=headers, params=parameters)
                    # res = requests.get(endPoint + path, headers=headers, params=parameters, timeout=3)
    
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)    
                    df = pd.DataFrame()
                    status = 9    # misc error
                    msg_code = 'ERR-999'    
                    msg_str = 'HTTP ERROR'  
                    break                 
    
                except requests.exceptions.ConnectionError as errc:   
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {errc}  ")
                    self.debug.sound_alert(3)  
                    df = pd.DataFrame()
                    status = 8      # retry error  
                    msg_code = 'ERR-888'    
                    msg_str = 'HTTP ERROR'  
                    break              
    
                except requests.exceptions.Timeout as errt: 
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                     
                    df = pd.DataFrame()
                    status = 8      # retry error  
                    msg_code = 'ERR-888'    
                    msg_str = 'TIMEOUT ERROR'  
                    break              
                        
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)                                        
                    df = pd.DataFrame()
                    status = 9    # misc error
                    msg_code = 'ERR-999'    
                    msg_str = 'EXCEPTION ERROR'  
                    break                 
                
                except:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_price() ▶ quit python...")
                    self.debug.sound_alert(3)  
                    df = pd.DataFrame()
                    status = 9    # misc error
                    msg_code = 'ERR-999'    
                    msg_str = 'MISC EXCEPTION ERROR'  
                    break   
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try- and except block
                    pass
    
                dict = res.json()
    
                # BTC_JPY
                # -----------------------------------------------------
                # {
                #     'status': 0, 
                #     'data': {
                #         'list': [
                #             {'executionId': 492454252, 
                #             'fee': '0', 
                #             'lossGain': '0', 
                #             'orderId': 2350188580, 
                #             'positionId': 162375068, ★
                #             'price': '92.967', 
                #             'settleType': 'OPEN', 
                #             'side': 'SELL', 
                #             'size': '10', 
                #             'symbol': 'XRP_JPY', 
                #             'timestamp': '2022-03-17T10:12:33.135Z'
                #             }
                #           ]
                #     }, 
                #     'responsetime': '2022-03-19T00:06:00.681Z'
                # }           
                # -----------------------------------------------------
    
                status = dict.get('status')   
    
                # nornal return ?
                if status == 0:
                    data_dict = dict.get('data')
                    if len(data_dict) == 0:
                        df = pd.DataFrame()
                        status = 9                          # empy dataframe
                        msg_code = 'ERR-999'                # null => ERR-999
                        msg_str = 'DataFrame is empty'      # null => DataFrame is empty                                        
                    else: 
                        data_list = data_dict.get('list')
                        df = pd.DataFrame(data_list)
    
                        # BTC_JPY
                        # Data columns (total 11 columns):
                        # -----------------------------------------------------------
                        #  #   Column       Dtype 
                        # ----------------------------------------------------------- 
                        #  0   executionId  int64   => string
                        #  1   fee          object  => float
                        #  2   lossGain     object  => float
                        #  3   orderId      int64   => string
                        #  4   positionId   int64   => string
                        #  5   price        object  => float
                        #  6   settleType   object  'OPEN' or 'CLOSE'
                        #  7   side         object  'SELL' or 'BUY'
                        #  8   size         object  => float
                        #  9   symbol       object  => 'BTC_JPY'
                        # 10   timestamp    object  => time(utc)      
                        # -----------------------------------------------------------          
    
                        if df.shape[0] > 0:
                            # convert data types
                            df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                            df['executionId'] = df['executionId'].astype(str)       # int64 => string
                            df['orderId'] = df['orderId'].astype(str)               # int64 => string                    
                            df['positionId'] = df['positionId'].astype(str)         # int64 => string ★
                            df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})                                        
           
                            # Data columns (total 11 columns):
                            # ---------------------------------------------------------
                            #  #   Column       Dtype              
                            # ---------------------------------------------------------              
                            #  0   executionId  object             
                            #  1   fee          float64            
                            #  2   lossGain     float64            
                            #  3   orderId      object             
                            #  4   positionId   object  ★           
                            #  5   price        float64            
                            #  6   settleType   object  (OPEN or CLOSE)             
                            #  7   side         object  (BUY or SELL)           
                            #  8   size         float64            
                            #  9   symbol       object  (BTC_JPY)           
                            #  10  timestamp    datetime64[ns, UTC]
                            # ---------------------------------------------------------                    
    
                        msg_code = 'OK'
                        msg_str = 'Normal Return'                           # 'Normal Return'
                    # end of if len(data_dict) == 0:
    
                elif status == 5:   # Maintenance
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_price({order_id=}) ▶ {status=} quit python...")
                    self.debug.sound_alert(3)  
                    quit()    
    
                else:   # status == ?        
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')  # ERR-???
                    msg_str = msg_list[0].get('message_string') # error message  
    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {msg_code} - {msg_str} ")            
                    self.debug.sound_alert(3)
    
                    ######################################################## debug info
                    print('-'*100, 'Begin: dump response from get_price()')
                    print(dict) 
                    print('-'*100, 'End  : dump response from get_price()')    
                    ####################################################### debug info   
                    
                    ### change original error status(?) to empty status(9) ★
                    
                    df = pd.DataFrame()
                    status = 9                          # empty dataframe
                    msg_code = 'ERR-999'                # null => ERR-999
                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty                                      
            
                # end of if status == 0:
    
                if status == 0:
                    side_pos = df.columns.get_loc('side')   
                    side = df.iloc[0, side_pos] # get side ('BUY' or 'SELL')
                    if side == 'BUY':
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}get_price_buy{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    else:  #  'SELL':
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}get_price_sell{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                elif status == 9:   # dataframe is empty
                    pass    # do not print message
                else:   # error
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                # end of if status == 0:
                break
            # end of while loop
    
            # normal or error return
            return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return') or (8, df, 'ERR-888', 'Error Message') or (9, df, 'ERR-999', 'DataFrame is mepty')

    click image to zoom!
    図10
    図10はデモプログラムの実行結果です。 ここではget_price_try()メソッドで注文の約定情報を取得して表示しています。 get_price_try()メソッドの戻り値には約定情報が格納されているPandasのDataFrameが返されます。

    ここではDataFrameの内容を表示しています。 DataFrameに「orderId, positionId, price, size, fee, ...」が格納されているのが分かります。


  11. Apiクラスにexe_cancel_order()メソッドを追加する

    ここではApiクラスにexe_cancel_order()メソッドを追加します。 このメソッドは指値の買い注文、売り注文などが約定しないで保留状態になっているときに使用します。 exe_cancel_order()メソッドの引数には、キャンセルする「注文ID」を指定します。

    api.py:
        ################################################### 
        def exe_cancel_order(self, order_id: str) -> tuple:      # return (status, msg_code, msg_str) 
    
            if not self.coin.real_mode:
                status = 0    
                msg_code = 'OK'
                msg_str = 'Cancelled...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_cancel_order{Bcolors.ENDC}({order_id=}): {Bcolors.FAIL}FAKE{Bcolors.ENDC} ")
                return (status, msg_code, msg_str)  # (0, 'OK', 'Cancelled...') 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/cancelOrder'
            reqBody = {'orderId': order_id}
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_cancel_order() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')       
    
            msg_code = ''
            msg_str = ''
        
            # normal return 
            if status == 0:
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_cancel_order({order_id=}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()        
    
            else:   # status == ?     
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')  # ERR-5122
                msg_str = msg_list[0].get('message_string') #  The request is invalid due to the status of the specified order.
    
                if msg_code == 'ERR-5122':  # The request is invalid due to the status of the specified order
                    pass
                else:        
                    ################################################################# debug info
                    print('-'*100, 'Begin: dump response from exe_cancel_order()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_cancel_order()')    
                    ################################################################# debug info
    
            # end of if status == 0:                    
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_cancel_order{Bcolors.ENDC}({order_id=}): ▶ {status=}, {msg_code} - {msg_str} ")
            # normal or error return
            return (status, msg_code, msg_str)  # (0, 'OK, 'Normal Return') or (1, 'ERR-5122', 'The request is invalid due to the status of the specified order')

    click image to zoom!
    図11-1
    図11-1では指値の買い注文をキャンセルしています。


    click image to zoom!
    図11-2
    図11-2では指値の売り注文をキャンセルしています。


  12. Apiクラスの全てを掲載

    ここではApiクラスの全てのソースコードを掲載しています。

    api.py:
    
    # api.py
    
    """
    Api class
    status = 0, 4, 5, 8, 9, 999
        0: normal return
        4: invalid request (requests are too many)
        5: maintenance return
        8: retry return
        9: empty dataframe return
        999: misc error return
    """
    
    # import the libraries
    import os
    import os.path
    
    import time
    from time import sleep  
    import datetime as dt
    
    import numpy as np
    import pandas as pd
    
    # api
    import hmac
    import hashlib
    
    import requests
    import json
    
    from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe  
    from lib.debug import Debug
    from lib.trade import Trade
    
    import warnings
    warnings.simplefilter('ignore')
    
    ########## Api class
    class Api:
        """
        __init__(self), __init__(self, gvar, coin)  
        load_master_driver() => load_master_data() => call get_crypto_try() => get_crypto()   
        get_assets() => exe_assets()  
        get_crypto_try() => get_crypto() 
        get_orderbooks_try() => get_orderbooks()
        get_order_status_try() => get_order_status(), get_price_try() => get_price() 
        exe_buy_order_market_wait()  => exe_buy_order_market(),  exe_buy_order_limit()
        exe_sell_order_market_wait() => exe_sell_order_market(), exe_sell_order_limit()
        exe_buy_close_order_market_wait()  => exe_buy_close_order_market(),  exe_buy_close_order_limit()
        exe_sell_close_order_market_wait() => exe_sell_close_order_market(), exe_sell_close_order_limit()
        exe_cancel_order()     
        """
    
        ###########################    
        def __init__(self) -> None:
            self.gvar = Gvar()                          # Gvar          
            self.coin = Coin()                          # Coin 
            self.debug = Debug(self.gvar)               # dependent class 
            self.trade = Trade(self.gvar, self.coin)    # dependent class              
          
            self.folder_gmo =  f'{self.gvar.domain}/{self.gvar.exchange}/'  # desktop-pc/bot/         
            self.folder_trading_type =  f'{self.gvar.domain}/{self.gvar.exchange}/{self.coin.trade_type}/'  
            # desktop-pc/bot/buy_sell/           
            # desktop-pc/bot/sell_buy/ 
    
            ### GET GMO API-key / Secret Key
            self.api_key = os.environ.get('GMO_API_KEY')
            self.api_secret = os.environ.get('GMO_SECRET_KEY')        
          
        #######################################################
        def __init__(self, gvar: object, coin: object) -> None:
            self.gvar = gvar                # Gvar      
            self.coin = coin                # Coin
            self.debug = Debug(gvar)        # dependent class          
            self.trade = Trade(gvar, coin)  # dependent class 
          
            self.folder_gmo =  f'{self.gvar.domain}/{self.gvar.exchange}/'  # desktop-pc/bot/ 
            self.folder_trading_type =  f'{self.gvar.domain}/{self.gvar.exchange}/{self.coin.trade_type}/'  
            # desktop-pc/bot/buy_sell/          
            # desktop-pc/bot/sell_buy/    
    
            ### GET GMO API-key / Secret Key
            self.api_key = os.environ.get('GMO_API_KEY')
            self.api_secret = os.environ.get('GMO_SECRET_KEY')
    
        #########################
        def __str__(self) -> str:
            return f"Api({self.folder_gmo=}, {self.folder_trading_type=}, self.api_key=●●●, self.api_secret=●●●)"      
        
        ######################## 
        def x_get_api_key(self):
            return self.api_key
    
        ###########################  
        def x_get_api_secret(self):
            return self.api_secret           
      
        #########################################################
        def get_crypto_try(self, page: int, count: int) -> tuple:   # return (status, df)
            retry_count = 0
            # retry N times if invalid or connection error or time out errors
            while True:
                status, df = self.get_crypto(page, count)  # => return (status, df)             
                # invalid request (requests are too many), connection error, time out error ?  
                if status == 4 or status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~61 seconds    
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_crypto({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ")                   
                        sleep(sec)  # sleep 3~61 seconds                
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (status, df)
    
        ##################################################### 
        def get_crypto(self, page: int, count: int) -> tuple:   # return (status, df)          
            symbol = self.coin.symbol
            page_str = str(page)
            count_str = str(count)
    
            endPoint = 'https://api.coin.z.com/public'
            path = f'/v1/trades?symbol={symbol}&page={page_str}&count={count_str}'        
            url = endPoint + path  
    
            status = 999    # misc error return
    
            while True:   
    
                try:
                    res_dict = requests.get(url, timeout=3) # timeout=3 sec
               
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)                        
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 999            # misc error
                    break   # error return                
    
                except requests.exceptions.ConnectionError as errc:                       
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {errc}  ")
                    self.debug.sound_alert(3) 
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 8              # retry error
                    break   # error return 
    
                except requests.exceptions.Timeout as errt:       
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                      
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 8              # retry error
                    break   # error return                  
    
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_crypto() ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)                                             
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 999            # misc error
                    break   # error return                   
    
                except:     #  block lets you handle the error
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_crypto() ▶ return ...")
                    self.debug.sound_alert(3)                    
                    df = pd.DataFrame()     # return empty dataframe 
                    status = 999            # misc error
                    break   # error return                   
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try and except block
                    pass
    
                # end of try except else finally        
    
                dict = res_dict.json()
    
                # invalid url
                # {'message': 'Not Found', 'statusCode': 404}            
    
                statusCode = dict.get('statusCode')
    
                if statusCode == 404:
                    message = dict.get('message')
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}URL NOT FOUND ERROR:{Bcolors.ENDC} get_crypto() ▶  requests.get({url=}) : {statusCode=}, {message=} ")
                    self.debug.sound_alert(3)  
                    quit()
    
                ### Normal Case 
    
                # {'status': 0, 'data': {'list': [{'price': '4480940', 'side': 'BUY', 'size': '0.0109', 'timestamp': '2022-02-27T08:15:09.241Z'},...        
    
                ################################################### debug info
                # print('-'*100, 'dump response from get_crypto()')
                # print(dict) 
                # print('-'*100, 'dump response from get_crypto()')    
                ################################################### debug info               
    
                status = dict.get('status')
    
                # no error ?
                if status == 0:
                    data_dict = dict.get('data')
                    data_list = data_dict.get('list')
                    data_pagination = data_dict.get('pagination')
                    df = pd.DataFrame(data_list)
                    if df.shape[0] > 0:
                        # convert data types
                        df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (ware) ★  errors='coerce' => NaT or errors='ignore'
                        df = df.astype({'price': 'float', 'size': 'float'})  
    
                    break   # normal return   
    
                    #  #   Column     Dtype              
                    # ----------------------------------------------------------------              
                    #  0   price      float64            
                    #  1   side       object    'BUY' or 'SELL'             
                    #  2   size       float64            
                    #  3   timestamp  datetime64[ns, UTC] ★ UTC descending order  
                    # ----------------------------------------------------------------                  
    
                else:   # error return : status == 5 or 4 or 1 or ?
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')
                    msg_str = msg_list[0].get('message_string')	            
    
                    if status == 5:   # Maintenance
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_crypto({symbol}, {page}, {count}) ▶ {status=}, {msg_code} - {msg_str} quit python...")
                        self.debug.sound_alert(3)  
                        quit()
    
                    elif status == 4 and msg_code == 'ERR-5003':   # Invalid Request: ERR-5003 Requests are too many              
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}Invalid Request():{Bcolors.ENDC} get_crypto({symbol}, {page}, {count}) ▶ {status=}, {msg_code} - {msg_str} wait 1 minute and retry...") 
                        df = pd.DataFrame()    # return empty dataframe 
                        break   # error return                     
    
                    else:   # status == ?
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}Invalid Request:{Bcolors.ENDC} get_crypto({symbol}, {page}, {count}): {status=}, {msg_code} - {msg_str} error return with empty dataframe... ") 
                        ################################################# debug info
                        print('-'*100, 'dump response from get_crypto()')
                        print(dict) 
                        print('-'*100, 'dump response from get_crypto()')    
                        ################################################# debug info            
                        df = pd.DataFrame()    # return empty dataframe 
                        break   # error return
    
                # end of if status == 0:
            # end of while True:
    
            #trace_write(f".... get_crpto('{symbol}', page='{page}', count='{count}'): ▶ {df.shape[0]} ")  
            return (status, df)     
    
        ###################################### 
        def get_orderbooks_try(self) -> tuple:  # return (sell_df, buy_df)   
            retry_count = 0
            # retry N times if invalid or connection or time out errors
            while True:
                status, sell_df, buy_df =  self.get_orderbooks()  # => return (status, sell_df, buy_df)           
                # invalid request (requests are too many), connection error, time out error ?  
                if status == 4 or status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1                    
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~61 seconds                       
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_orderbooks({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ") 
                        sleep(sec)    # sleep 3~61 seconds                                                    
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (sell_df, buy_df)
    
        ################################## 
        def get_orderbooks(self) -> tuple:  # return (status, sell_df, buy_df)  
            symbol = self.coin.symbol
    
            endPoint = 'https://api.coin.z.com/public'
            path = f'/v1/orderbooks?symbol={symbol}'
            url = endPoint + path
    
            status = 999    # misc error
    
            while True:            
                try:
                    res_dict = requests.get(url, timeout=3) # 3 seconds  # timeout=0.001  sec
                
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)                          
                    status = 999                # misc error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe
    
                except requests.exceptions.ConnectionError as errc:                      
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {errc}  ")
                    self.debug.sound_alert(3)  
                    status = 8                  # retry error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                   
    
                except requests.exceptions.Timeout as errt:       
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                      
                    status = 8                  # retry error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                                    
    
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_orderbooks() ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)   
                    status = 999                # misc error
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                                                       
    
                except:     #  block lets you handle the error
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_orderbooks() ▶ return...")
                    self.debug.sound_alert(3)  
                    status = 999                # misc error    
                    sell_df = pd.DataFrame()    # return empty dataframe  
                    buy_df = pd.DataFrame()     # return empty dataframe                           
                    break   # return with empty dataframe                 
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try- and except block
                    pass
    
                # end of try except else finally        
    
                dict = res_dict.json()
    
                # {'status': 0,
                #  'data': {
                #   'asks': [{'price': '4042000', 'size': '0.0005'},
                #    {'price': '4045000', 'size': '0.0028'},
                #    {'price': '4045018', 'size': '0.0001'},
                #    {'price': '4049000', 'size': '0.001'},
                #    {'price': '4049310', 'size': '0.0329'},
                #    {'price': '4049520', 'size': '0.0125'},
                #    {'price': 5381822.0, 'size': '0.001'}],
                #   'bids': [{'price': 5013050.0, 'size': '0.0749'},
                #    {'price': 5013000.0, 'size': '0.0015'},
                #    {'price': 5012425.0, 'size': '0.05'},
                #    {'price': 5012420.0, 'size': '0.05'},
                #    {'price': 4740000.0, 'size': '0.2135'}],
                #   'symbol': 'BTC'},
    
                ### Normal Case      
    
                ####################################################### debug info
                # print('-'*100, 'dump response from get_orderbooks()')
                # print(dict) 
                # print('-'*100, 'dump response from get_orderbooks()')    
                ####################################################### debug info               
    
                status = dict.get('status')
    
                if status == 0:
                    data_dict = dict.get('data')
    
                    asks = data_dict.get('asks') # sell order info (ascending order) 
                    bids = data_dict.get('bids') # buy order info (descending order)       
    
                    sell_df = pd.DataFrame(asks)      
                    buy_df = pd.DataFrame(bids)
    
                    # {'asks': [{'price': '4064331', 'size': '0.0001'},
                    #   {'price': '4069888', 'size': '0.0309'},
                    #   {'price': '4070000', 'size': '0.0073'},
                    #   {'price': '4070620', 'size': '0.001'},
                    #   {'price': 5381822.0, 'size': '0.001'}],
    
                    #  'bids': [{'price': 5013050.0, 'size': '0.0749'},
                    #    {'price': 5013000.0, 'size': '0.0015'},
                    #    {'price': 5012425.0, 'size': '0.05'},
                    #    {'price': 5012420.0, 'size': '0.05'},
                    #    {'price': 4740000.0, 'size': '0.2135'}],            
    
                    if sell_df.shape[0] > 0:
                        # convert data types
                        sell_df = sell_df.astype({'price': 'float', 'size': 'float'})  
                        
                    if buy_df.shape[0] > 0:
                        # convert data types
                        buy_df = buy_df.astype({'price': 'float', 'size': 'float'})                    
                
                else:   # error return
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')
                    msg_str = msg_list[0].get('message_string')	            
    
                    if status == 5:   # Maintenance
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_orderbooks({symbol}) ▶ {status=}, {msg_code} - {msg_str} quit python...")
                        self.debug.sound_alert(3)  
                        quit()
    
                    else:   # status == ?
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}Invalid Request:{Bcolors.ENDC} get_orderbooks({symbol}) ▶ {status=}, {msg_code} - {msg_str} error return with empty dataframe... ") 
                        ###################################################### debug info
                        print('-'*100, 'dump response from get_orderbooks()')
                        print(dict) 
                        print('-'*100, 'dump response from get_orderbooks()')    
                        ##################################################### debug info            
                        sell_df = pd.DataFrame()    # return empty dataframe  
                        buy_df = pd.DataFrame()     # return empty dataframe                
                
                # end if if status == 0:        
                break
            # end of while true:
      
            #trace_write(f".... get_orderbooks('{symbol}'): ▶ {sell_df.shape[0]}, {buy_df.shape[0]} ")  
            return (status, sell_df, buy_df)    # status, sell_df(ascending order), buy_df(descending order)
    
        ################################################################################ 
        def get_order_status_try(self, order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)     
    
            retry_count = 0
    
            # retry N times if invalid or connection or time out errors
            while True:
                status, order_status = self.get_order_status(order_id, qty, price)   # return (status, order_status)      
                # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED
    
                # invalid request (requests are too many), connection error, time out error ?  
                if status == 4 or status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~61 seconds  
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_order_status({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ")                       
                        sleep(sec)    # sleep 3~61 seconds                                   
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (status, order_status)   # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error)    
        
        ############################################################################    
        def get_order_status(self, order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)            
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            if not self.coin.real_mode: 
                status = 0
                order_status = 'EXECUTED'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_status=} {Bcolors.FAIL}FAKE{Bcolors.ENDC} ") 
                return (status, order_status)
            # end of if not self.coin.real_mode: 
    
            ### real mode
    
            status = 9  # misc rrror
    
            while True:            
                timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
                method    = 'GET'
                endPoint  = 'https://api.coin.z.com/private'
                path      = '/v1/orders'
    
                url = timestamp + method + path
                sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
                parameters = {
                    'orderId': order_id # '2478774888' : DO NOT USE multiple orderId  '2478774888','2478774999'  max 10 orderId     
                }
    
                headers = {
                    'API-KEY': self.api_key,
                    'API-TIMESTAMP': timestamp,
                    'API-SIGN': sign
                }
    
                try:
                    res_dict = requests.get(endPoint + path, headers=headers, params=parameters, timeout=3)
    
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)               
                    status = 9              # misc error
                    order_status = 'ERROR'  # http error   
                    break   # error return                     
    
                except requests.exceptions.ConnectionError as errc:    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {errc}  ")  
                    status = 8              # retry error  
                    order_status = 'ERROR'  # connection error   
                    break   # error return                                            
    
                except requests.exceptions.Timeout as errt:                    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                     
                    status = 8              # retry error
                    order_status = 'ERROR'  # timeout error   
                    break   # error return                
      
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)               
                    status = 9              # misc error
                    order_status = 'ERROR'  # exception error   
                    break   # error return                  
    
                except:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_order_status({symbol},{order_id}) ▶ return...")
                    self.debug.sound_alert(3)                          
                    status = 9              # misc error
                    order_status = 'ERROR'  # misc exception error   
                    break   # error return                 
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try- and except block
                    pass
    
                dict = res_dict.json()
                status = dict.get('status')    # 0 or 1
    
                # {
                #   "status": 0,
                #   "data": {
                #     "list": [
                #       {
                #         "executedSize": "1000",
                #         "executionType": "MARKET",
                #         "losscutPrice": "0",
                #         "orderId": 2478774888,
                #         "orderType": "NORMAL",
                #         "price": "0",
                #         "rootOrderId": 2478774888,
                #         "settleType": "OPEN",
                #         "side": "BUY",
                #         "size": "1000",
                #         "status": "EXECUTED", # WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED
                #         "symbol": "XRP_JPY",
                #         "timeInForce": "FAK",
                #         "timestamp": "2022-05-13T05:27:46.042Z"
                #       }
                #     ]
                #   },
                #   "responsetime": "2022-05-26T00:23:48.767Z"
                # }
    
                # normal return
                if status == 0:
                    data = dict.get('data')
    
                    if len(data) == 0:
                        df = pd.DataFrame()            
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}): ▶ {status=}, {df.shape[0]=} ") 
                        order_status = 'EXPIRED'
                        break   # return (status, order_status)  
    
                    data_list = data.get('list')
                    
                    df = pd.DataFrame(data_list)
                    # convert data types
                    df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True)   # UTC (aware)
    
                    # 'executedSize': "1000" => float  
                    # 'losscutPrice': "0" => float
                    # 'orderId": 2478774888 => object        
                    # 'price': "0" => float
                    # 'rootOrderId': 2478774888 => object  
                    # 'size': "1000" => float  
                
                    df = df.astype(
                            {
                                'executedSize': 'float', 'losscutPrice': 'float', 
                                'orderId': 'object', 'price': 'float',
                                'rootOrderId': 'object', 'size': 'float'
                            }
                        )  
    
                    # Data columns (total 14 columns):
                    # --------------------------------------------------------------
                    #  #   Column         Dtype
                    # ---------------------------------------------------------------
                    #  0   executedSize   float64
                    #  1   executionType  object
                    #  2   losscutPrice   float64
                    #  3   orderId        object
                    #  4   orderType      object
                    #  5   price          float64
                    #  6   rootOrderId    object
                    #  7   settleType     object
                    #  8   side           object
                    #  9   size           float64
                    #  10  status         object
                    #  11  symbol         object
                    #  12  timeInForce    object
                    #  13  timestamp      datetime64[ns, UTC]
                    # ------------------------------------------------------------------
    
                    if df.shape[0] == 0:
                        order_status = 'EXPIRED'
                    else:
                        order_status = df.loc[0, 'status'] # WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED
    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_status=} ")               
    
                elif status == 5:   # Maintenance
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_order_status({order_id=}): ▶ {status=}, quit python...")
                    self.debug.sound_alert(3)  
                    quit()
                
                else:   # status == ?              
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')
                    msg_str = msg_list[0].get('message_string')
    
                    ################################################################## debug info
                    print('-'*100, 'Begin: dump response from get_order_status()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from get_order_status()')    
                    ################################################################## debug info         
    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status{Bcolors.ENDC}({order_id=}): ▶ {status=}, {msg_code} - {msg_str} ")            
                    order_status = 'ERROR'   
                # end of if status == 0: 
                break
            # end of while true:    
            
            return (status, order_status)   # 0(ok), 4(invalid request), 8(retry error), 9(misc error)
    
        #############################################################################################################################   
        def get_price_try(self, order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
            
            retry_count = 0
    
            # retry N times if invalid or connection or time out errors
            while True:
                status, df, msg_code, msg_str = self.get_price(order_id, qty, price, latest_book_price, debug1, debug2)   # return (status, order_status)      
                # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
                # msg_code: OK, ERR-888, ERR-999           
    
                # connection error, time out error ?  
                if status == 8:                
                    if retry_count < self.coin.api_retry_limit_count:
                        retry_count += 1
                        sec = np.random.randint(low=3, high=61, size=(1))[0]  # get random number between 3~61 seconds       
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}due to get_price({status=}) error sleeping {sec} seconds :{Bcolors.ENDC} ▶ {retry_count=} ")                    
                        sleep(sec)    # sleep 3~61 seconds                                   
                        continue
                break   # exit while loop                        
            # end of while True:                
    
            return (status, df, msg_code, msg_str)  # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
    
        #########################################################################################################################    
        def get_price(self, order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            if not self.coin.real_mode:   
                if latest_book_price == 0.0: latest_book_price = price
                buy_sell = True if self.coin.trade_type == 'buy_sell' else False
                
                return_empty_df = True
    
                if buy_sell:
                    if debug1 or debug2:
                        return_empty_df = False
                    else: # neither debug1 nor debug2
                        # skip empty position list
                        if len(self.coin.sell_position_list) > 0:   
                            # first sell position ?
                            if self.coin.sell_position_list[-1] == 0:
                                # sell_price is less than equal to latest buy orderbooks price ?
                                if price <= latest_book_price:
                                    return_empty_df = False                
                else: # sell_buy
                    if debug1 or debug2:
                        return_empty_df = False
                    else: # neither debug1 nor debug2
                        # skip empty position list
                        if len(self.coin.buy_position_list) > 0:        
                            # first buy position ?
                            if self.coin.buy_position_list[-1] == 0:
                                # buy_price is greater than or equal to latest sell orderbooks price ?
                                if price >= latest_book_price:  
                                    return_empty_df = False                
                # end of if buy_sell:
    
                if not return_empty_df:                 
                    status = 0
                    execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                    position_id  = 'P' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PMMDD-HHMMSS-999999(milliseconds) ★
                    position_list = [                         
                                        {
                                            'executionId': execution_id, 
                                            'fee': '0',                     # 1 => 0 (leverage trading) ★
                                            'lossGain': '0', 
                                            'orderId': order_id, 
                                            'positionId': position_id,      # ★
                                            'price': latest_book_price,     # ★
                                            'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                            'side': 'SELL',                 # 'SELL' or 'BUY'
                                            'size': qty, 
                                            'symbol': symbol, 
                                            'timestamp': dt.datetime.utcnow()
                                        }
                                    ]
                                                            
                    df = pd.DataFrame(position_list)
                    # convert data types
                    df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                    df['executionId'] = df['executionId'].astype(str)   # int64 => string
                    df['orderId'] = df['orderId'].astype(str)           # int64 => string
                    df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                    df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                    msg_code = 'OK'
                    msg_str = 'Normal Return'
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")               
                    return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')         
                else: # return empty dataframe                    
                    df = pd.DataFrame()
                    status = 9                          # empty dataframe
                    msg_code = 'ERR-999'                # null => ERR-999
                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")               
                    return (status, df, msg_code, msg_str)  # (9, df, 'ERR-999', 'DataFrame is empty') 
                # end of if not return_empty_df: 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            status = 9    # internal error or misc error
    
            while True:            
                timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
                method    = 'GET'
                endPoint  = 'https://api.coin.z.com/private'
                path      = '/v1/executions'
    
                url = timestamp + method + path
                sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
                parameters = {'orderId': order_id}         # buy or sell orderId
    
                headers = {
                    "API-KEY": self.api_key,
                    "API-TIMESTAMP": timestamp,
                    "API-SIGN": sign
                }
    
                try:
                    res = requests.get(endPoint + path, headers=headers, params=parameters)
                    # res = requests.get(endPoint + path, headers=headers, params=parameters, timeout=3)
    
                except requests.exceptions.HTTPError as errh:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}HTTP ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {errh} ")
                    self.debug.sound_alert(3)    
                    df = pd.DataFrame()
                    status = 9    # misc error
                    msg_code = 'ERR-999'    
                    msg_str = 'HTTP ERROR'  
                    break                 
    
                except requests.exceptions.ConnectionError as errc:   
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CONNECTION ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {errc}  ")
                    self.debug.sound_alert(3)  
                    df = pd.DataFrame()
                    status = 8      # retry error  
                    msg_code = 'ERR-888'    
                    msg_str = 'HTTP ERROR'  
                    break              
    
                except requests.exceptions.Timeout as errt: 
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}TIMEOUT ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {errt}  ")
                    self.debug.sound_alert(3)                     
                    df = pd.DataFrame()
                    status = 8      # retry error  
                    msg_code = 'ERR-888'    
                    msg_str = 'TIMEOUT ERROR'  
                    break              
                        
                except requests.exceptions.RequestException as err:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}EXCEPTION ERROR:{Bcolors.ENDC} get_price() ▶ request.get(): {err}  " )
                    self.debug.sound_alert(3)                                        
                    df = pd.DataFrame()
                    status = 9    # misc error
                    msg_code = 'ERR-999'    
                    msg_str = 'EXCEPTION ERROR'  
                    break                 
                
                except:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC EXCEPTION ERROR:{Bcolors.ENDC} get_price() ▶ quit python...")
                    self.debug.sound_alert(3)  
                    df = pd.DataFrame()
                    status = 9    # misc error
                    msg_code = 'ERR-999'    
                    msg_str = 'MISC EXCEPTION ERROR'  
                    break   
    
                else:       # block lets you execute code when there is no error
                    pass
    
                finally:    # block lets you execute code, regardless of the result of the try- and except block
                    pass
    
                dict = res.json()
    
                # BTC_JPY
                # -----------------------------------------------------
                # {
                #     'status': 0, 
                #     'data': {
                #         'list': [
                #             {'executionId': 492454252, 
                #             'fee': '0', 
                #             'lossGain': '0', 
                #             'orderId': 2350188580, 
                #             'positionId': 162375068, ★
                #             'price': '92.967', 
                #             'settleType': 'OPEN', 
                #             'side': 'SELL', 
                #             'size': '10', 
                #             'symbol': 'XRP_JPY', 
                #             'timestamp': '2022-03-17T10:12:33.135Z'
                #             }
                #           ]
                #     }, 
                #     'responsetime': '2022-03-19T00:06:00.681Z'
                # }           
                # -----------------------------------------------------
    
                status = dict.get('status')   
    
                # nornal return ?
                if status == 0:
                    data_dict = dict.get('data')
                    if len(data_dict) == 0:
                        df = pd.DataFrame()
                        status = 9                          # empy dataframe
                        msg_code = 'ERR-999'                # null => ERR-999
                        msg_str = 'DataFrame is empty'      # null => DataFrame is empty                                        
                    else: 
                        data_list = data_dict.get('list')
                        df = pd.DataFrame(data_list)
    
                        # BTC_JPY
                        # Data columns (total 11 columns):
                        # -----------------------------------------------------------
                        #  #   Column       Dtype 
                        # ----------------------------------------------------------- 
                        #  0   executionId  int64   => string
                        #  1   fee          object  => float
                        #  2   lossGain     object  => float
                        #  3   orderId      int64   => string
                        #  4   positionId   int64   => string
                        #  5   price        object  => float
                        #  6   settleType   object  'OPEN' or 'CLOSE'
                        #  7   side         object  'SELL' or 'BUY'
                        #  8   size         object  => float
                        #  9   symbol       object  => 'BTC_JPY'
                        # 10   timestamp    object  => time(utc)      
                        # -----------------------------------------------------------          
    
                        if df.shape[0] > 0:
                            # convert data types
                            df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                            df['executionId'] = df['executionId'].astype(str)       # int64 => string
                            df['orderId'] = df['orderId'].astype(str)               # int64 => string                    
                            df['positionId'] = df['positionId'].astype(str)         # int64 => string ★
                            df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})                                        
           
                            # Data columns (total 11 columns):
                            # ---------------------------------------------------------
                            #  #   Column       Dtype              
                            # ---------------------------------------------------------              
                            #  0   executionId  object             
                            #  1   fee          float64            
                            #  2   lossGain     float64            
                            #  3   orderId      object             
                            #  4   positionId   object  ★           
                            #  5   price        float64            
                            #  6   settleType   object  (OPEN or CLOSE)             
                            #  7   side         object  (BUY or SELL)           
                            #  8   size         float64            
                            #  9   symbol       object  (BTC_JPY)           
                            #  10  timestamp    datetime64[ns, UTC]
                            # ---------------------------------------------------------                    
    
                        msg_code = 'OK'
                        msg_str = 'Normal Return'                           # 'Normal Return'
                    # end of if len(data_dict) == 0:
    
                elif status == 5:   # Maintenance
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} get_price({order_id=}) ▶ {status=} quit python...")
                    self.debug.sound_alert(3)  
                    quit()    
    
                else:   # status == ?        
                    msg_list = dict.get('messages')
                    msg_code = msg_list[0].get('message_code')  # ERR-???
                    msg_str = msg_list[0].get('message_string') # error message  
    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {msg_code} - {msg_str} ")            
                    self.debug.sound_alert(3)
    
                    ######################################################## debug info
                    print('-'*100, 'Begin: dump response from get_price()')
                    print(dict) 
                    print('-'*100, 'End  : dump response from get_price()')    
                    ####################################################### debug info   
                    
                    ### change original error status(?) to empty status(9) ★
                    
                    df = pd.DataFrame()
                    status = 9                          # empty dataframe
                    msg_code = 'ERR-999'                # null => ERR-999
                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty                                      
            
                # end of if status == 0:
    
                if status == 0:
                    side_pos = df.columns.get_loc('side')   
                    side = df.iloc[0, side_pos] # get side ('BUY' or 'SELL')
                    if side == 'BUY':
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}get_price_buy{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    else:  #  'SELL':
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}get_price_sell{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                elif status == 9:   # dataframe is empty
                    pass    # do not print message
                else:   # error
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price{Bcolors.ENDC}({order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                # end of if status == 0:
                break
            # end of while loop
    
            # normal or error return
            return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return') or (8, df, 'ERR-888', 'Error Message') or (9, df, 'ERR-999', 'DataFrame is mepty')   
    
        ############################## 
        def get_assets(self) -> tuple:  # ▶ (status, asset)
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
           
            status, df = self.exe_assets()
    
            # --------------------------------------------------
            #  #   Column          Dtype 
            # -------------------------------------------------- 
            #  0   amount          object
            #  1   available       object
            #  2   conversionRate  object
            #  3   symbol          object
            # --------------------------------------------------
    
            asset = 0.0 
    
            if status != 0:     
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_assets{Bcolors.ENDC}(): ▶ {status=} ")        
                # error return
                return status, asset
    
            ############################################ debug info
            # print('-'*100, 'Begin: get_assets()')
            # print('\n', df.head()) 
            # print('-'*80)
            # print('\n', df.info())
            # print('-'*100, 'End  : getassets()')    
            ############################################ debug info
    
            # -----------------------------------------------------
            #          amount    available  conversionRate symbol
            # -----------------------------------------------------
            # 0  185822.0000  185822.0000             1.0    JPY
            # 1       0.0001       0.0001       4564624.0    BTC
            # 2       0.0000       0.0000        302500.0    ETH
            # 3       0.0100       0.0100         33100.0    BCH
            # 4       0.1000       0.1000         11992.0    LTC
            # ----------------------------------------------------
    
            filter_mask = df['symbol'] == symbol    
            dfx = df[filter_mask]
            if dfx.shape[0] > 0:            
                asset = dfx.amount.values[0]    
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_assets{Bcolors.ENDC}(): ▶ {status=}, {symbol=}, {asset=:,.{_q_}f} ")
            # normal return
            return status, asset    # 0(int), 1.123(float)
    
        ############################## execute account/assets ▶ status, df(dataframe)
        def exe_assets(self) -> tuple:   # ▶ status, df(dataframe)
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'GET'
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/account/assets'
    
            url = timestamp + method + path
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.get(endPoint + path, headers=headers, timeout=3)    
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}JCOM Maintenance:{Bcolors.ENDC} get_assets() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()                 
            else:       # block lets you execute code when there is no error
                pass
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            msg_code = ''
            msg_str = ''     
    
            if status == 0:
                data_list = dict.get('data') 
                df = pd.DataFrame(data_list)
    
                # -----------------------------------------------------------
                #  #   Column          Dtype 
                # -----------------------------------------------------------
                #  0   amount          object => float
                #  1   available       object => float
                #  2   conversionRate  object => float
                #  3   symbol          object
                # -----------------------------------------------------------
    
                # convert data types
                df = df.astype({'amount': 'float', 'available': 'float', 'conversionRate': 'float'})         
                # normal return     
                return (status, df)
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_assets(): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()   
    
            else:   # status=1 or ?
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # ERR-201
                msg_str = msg_list[0].get('message_string')     # Trading margin is insufficient
    
                # debug info
                print('-'*100, 'Begin: dump response from exe_assets()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_assets()')    
                # debug info
    
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_assets{Bcolors.ENDC}(): ▶ {status=}, {msg_code} - {msg_str} ")
                # error return
                return (status, pd.DataFrame())    
    
        ###############################################################################################  
        def exe_buy_order_market_wait(self, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)  
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                if latest_close_price == 0.0: latest_close_price = price              
                status = 0
                execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                order_id     = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                position_id  = 'P' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PMMDD-HHMMSS-999999(milliseconds) ★
                position_list = [                         
                                    {
                                        'executionId': execution_id,    # E9999-999999-999999
                                        'fee': '0',                     # 1 => 0 (leverage trading)
                                        'lossGain': '0', 
                                        'orderId': order_id,            # B9999-999999-999999 ★
                                        'positionId': position_id,      # P9999-999999-999999 ★
                                        'price': latest_close_price,    # ★
                                        'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                        'side': 'BUY',                  # 'SELL' or 'BUY'
                                        'size': qty, 
                                        'symbol': symbol, 
                                        'timestamp': dt.datetime.utcnow()
                                    }
                                ]
                                                        
                df = pd.DataFrame(position_list)
                # convert data types
                df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                df['executionId'] = df['executionId'].astype(str)   # int64 => string
                df['orderId'] = df['orderId'].astype(str)           # int64 => string
                df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                msg_code = 'OK'
                msg_str = 'Normal Return'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")             
                return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')
            # end of if not self.coin.real_mode:
            
            ### real mode
    
            df = pd.DataFrame()
    
            # exe_buy_order_market(qty: float) -> tuple:    # return (status, res_order_id, msg_code, msg_str)
            status, order_id, msg_code, msg_str = self.exe_buy_order_market(qty)    # ★ FAK (Fill and Kill) 
            # status: 0(ok), 1(Trading margin is insufficient), ?(misc)            
            
            # error ?
            if status != 0:
                return (status, df, msg_code, msg_str)    # (1, df, 'msg_code', 'msg_str') 
    
            i = 0
    
            while True:
                i += 1
    
                if i > self.coin.api_market_timeout_limit:     # time out ?
                    status = 8  # time out
                    msg_code = 'ERR-888'
                    msg_str = f'get_price() time out error: loop count={i} '
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return =>  (8, df, 'ERR-888', 'get_price() time out error:...')              
    
                # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)     
                status, order_status = self.get_order_status_try(order_id, qty, price)  
                # status: # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED, ERROR
    
                if status != 0:
                    msg_code = 'ERR-999'
                    msg_str = f"get_order_status({order_id=}) error ▶ {status=}, {order_status=}  "       
                    return (status, df, msg_code, msg_str)    # error return 
                
                ### status == 0 : normal return              
    
                # exe_buy_order_market() is pending status
                if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_buy_order_market_wait(): get_order_status({i}) ▶ {status=}, {order_status=} ")
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_order_status_try()
    
                ### order_status : EXECUTED or EXPIRED or CANCELED                 
    
                ### get_order_status() ▶ status==0 & order_status in 'EXECUTED(qty==real_qty), EXPIRED(qty > real_qty), CANCELED(real_qty==0)'         
                sleep(1)            # sleep 1 sec 
    
                # get_price_try(order_id: str, qty: float, price: float, latest_close_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
                status, df, msg_code, msg_str = self.get_price_try(order_id, qty, price)  # ★ qty and price are used for fake mode         
                # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)
                self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_buy_order_market_wait(): get_price({i}) ▶ {status=}, {df.shape=} ")
    
                # error ?
                if status != 0:     
                    # empty dataframe ?
                    if status == 9: 
                        sleep(1)        # sleep 1 sec 
                        continue        # continue get_price()
                    
                    # misc error 
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return (status, df, msg_code, msg_str)    # error return                    
                          
                if df.shape[0] == 0:
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_price()                                             
    
                ### Normal Return    
    
                # -------------------------------------------------------------
                #  #   Column       Dtype         
                # -------------------------------------------------------------       
                #  0   executionId  int64         
                #  1   fee          float64       
                #  2   lossGain     float64                               
                #  3   orderId      object(string)       
                #  4   positionId   object(string)              
                #  5   price        float64  
                #  6   settleType   object('OPEN' or 'CLOSE')  
                #  7   side         object('BUY' or 'SELL')                  
                #  8   size         float64           
                #  9   symbol       object('BTC')                        
                # 10   timestamp    datetime64[ns]   
                # ------------------------------------------------------------          
    
                # real_buy_qty = df.iloc[0]['size']
                real_buy_price = df.iloc[0]['price']
                # real_buy_fee = df.iloc[0]['fee']
    
                if real_buy_price > 0:    
                    msg_code = 'OK'
                    msg_str = 'Normal Return'
                    break   # got a real price ? => normal return
                                       
                sleep(1)    # sleep 1 sec
                # continue to get_price()
            # end of while True:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
            # normal return
            return (status, df, msg_code, msg_str)    # (0, df, 'OK', 'Normal Return')     
        
        #################################################### 
        def exe_buy_order_market(self, qty: float) -> tuple:    # return (status, res_order_id, msg_code, msg_str) 
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}' 
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # Buy MMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC} ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake/Test buy order') 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'MARKET',  # Required (MARKET, LIMIT, STOP) 
                'timeInForce': 'FAK',       # FAK (Fill and Kill)
                'size': qty_str             # Required (1.1234 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_order_market() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string 
            msg_code = ''
            msg_str = ''
        
            if status == 0:
                res_order_id = dict.get('data')  # string type 
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_order_market({qty=:,.{_q_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()        
    
            else:   # status == ?        
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-201'
                msg_str = msg_list[0].get('message_string')     # 'Trading margin is insufficient'
    
                if msg_code == 'ERR-201':    # Trading margin is insufficient
                    pass
                else:   # misc error
                    ################################################################### debug info
                    print('-'*100, 'Begin: dump response from exe_buy_order_market()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_buy_order_market()')    
                    ################################################################### debug info    
                                
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str} ")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '99999', 'OK', 'Normal Return') or (1, '',  'ERR-201', 'Trading margin is insufficient')            
    
        ################################################################## 
        def exe_buy_order_limit(self,  qty: float, price: float) -> tuple:       # return (status, res_order_id, msg_code, msg_str)   
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC} ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake/Test Buy order') 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999)        
                'size': qty_str             # Required (1.1234 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string 
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type 
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_order_limit({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()        
    
            else:   # status == ? 
                ### {'status': 1, 'messages': [{'message_code': 'ERR-5120', 'message_string': 'Too high price.'}], 'responsetime': '2022-09-26T05:00:58.198Z'}
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')
                msg_str = msg_list[0].get('message_string')
    
                if msg_code == 'ERR-201':   # Trading margin is insufficient
                    pass
                else:
                    ################################################################# debug info
                    print('-'*100, 'Begin: dump response from exe_buy_order_limit()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_buy_order_limit()')    
                    ################################################################# debug info
    
            # end of if status == 0:                        
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str} ")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '99999', 'OK', 'Normal Return') or (1, '', 'ERR-201', 'Trading margin is insufficient')
    
        ################################################################################################ 
        def exe_sell_order_market_wait(self, qty: float, price: float, latest_close_price=0.0) -> tuple: # return (status, df, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                if latest_close_price == 0.0: latest_close_price = price
                status = 0
                execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                order_id     = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)  
                position_id  = 'P' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PMMDD-HHMMSS-999999(milliseconds) ★
                position_list = [                         
                                    {
                                        'executionId': execution_id,    # E9999-999999-999999
                                        'fee': '0',                     # 1 => 0 (leverage traing) 
                                        'lossGain': '0', 
                                        'orderId': order_id,            # S9999-999999-999999
                                        'positionId': position_id,      # P9999-999999-999999 ★
                                        'price': latest_close_price,    # ★
                                        'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                        'side': 'SELL',                 # 'SELL' or 'BUY'
                                        'size': qty, 
                                        'symbol': symbol, 
                                        'timestamp': dt.datetime.utcnow()
                                    }
                                ]
                                                        
                df = pd.DataFrame(position_list)
                # convert data types
                df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                df['executionId'] = df['executionId'].astype(str)   # int64 => string
                df['orderId'] = df['orderId'].astype(str)           # int64 => string
                df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                msg_code = 'OK'
                msg_str = 'Normal Return'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC} ")                       
                return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')                 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            df = pd.DataFrame()   
    
            status, order_id, msg_code, msg_str = self.exe_sell_order_market(qty)   # ★ FAK (Fill and Kill)   
    
            # error ?
            if status != 0: 
                return status, df, msg_code, msg_str    # (1, df, 'msg_code', 'msg_str') 
    
            i = 0
    
            while True:
                i += 1
         
                if i > self.coin.api_market_timeout_limit:  # time out ?
                    status = 8      # time out
                    msg_code = 'ERR-888'
                    msg_str = f'get_price() time out error: loop count={i} '
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return =>  (8, df, 'ERR-888', 'get_price() time out error:...')  
    
                # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)    
                status, order_status = self.get_order_status_try(order_id, qty, price)  
                # status: # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED, ERROR            
                if status != 0:
                    msg_code = 'ERR-999'
                    msg_str = f"get_order_status({order_id=}) error ▶ {status=}, {order_status=} "       
                    return status, df, msg_code, msg_str    # error return 
                
                ### status == 0    
    
                # exe_sell_order_market() is pending status
                if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_sell_order_market_wait(): get_order_status({i}) ▶ {status=}, {order_status=} ")
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_order_status()
    
                ### order_staus : EXECUTED or EXPIRED or CANCELED
                
                ### get_order_status() ▶ status==0 & order_status in 'EXECUTED,EXPIRED,CANCELED'  
                sleep(1)            # sleep 1 sec            
    
                # get_price_try(order_id: str, qty: float, price: float, latest_close_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
                status, df, msg_code, msg_str = self.get_price_try(order_id, qty, price)  # qty and price are used for fake mode    
                # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)    
                self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_sell_order_market_wait(): get_price({i}) ▶ {status=}, {df.shape=} ")                                           
               
                # error ?
                if status != 0: 
                    # DataFrame is empty ?
                    if status == 9: 
                        sleep(1)        # sleep 1 sec 
                        continue        # continue get_price()
                    
                    # misc error                
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return            
    
                if df.shape[0] == 0:
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_price()
    
                ### Normal Return           
    
                # -----------------------------------------------------
                #  #   Column       Dtype         
                # -----------------------------------------------------         
                #  0   executionId  int64         
                #  1   fee          float64       
                #  2   lossGain     float64                               
                #  3   orderId      object(string)       
                #  4   positionId   object(string)               
                #  5   price        float64  
                #  6   settleType   object('OPEN' or 'CLOSE')  
                #  7   side         object('BUY' or 'SELL')                  
                #  8   size         float64           
                #  9   symbol       object('BTC_JPY')                        
                # 10   timestamp    datetime64[ns]          
                # ---------------------------------------------------- 
    
                # real_sell_qty = df.iloc[0]['size']
                real_sell_price = df.iloc[0]['price']
                # real_sell_fee = df.iloc[0]['fee']            
     
                if real_sell_price > 0:   
                    msg_code = 'OK'
                    msg_str = 'Normal Return'                
                    break   # got a real price ?     
      
                sleep(1)        # sleep 1 sec
                # continue to get_order_status_try()
            # end of while True:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market_wait{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str}")
            # normal or time out error return
            return (status, df, msg_code, msg_str)    # (0, df, 'OK', 'Normal Return') 
        
        ##################################################### 
        def exe_sell_order_market(self, qty: float) -> tuple:    # return (status, res_order_id, msg_code, msg_str)     
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC}  ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'S9999-999999-999999', 'OK', 'Fake\Test sell order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode 
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'MARKET',  # Required (MARKET, LIMIT, STOP) 
                'timeInForce': 'FAK',       # FAK (Fill and Kill)
                'size': qty_str             # Required (1 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_order_market() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
    
            status = dict.get('status')     # int type    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_order_market({qty=:,.{_q_}f}): ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # ERR-208
                msg_str = msg_list[0].get('message_string')     # Exceeds the available balance
    
                if msg_code == 'ERR-208':   # Exceeds the available balance
                    pass
                else:        
                    ##################################################################### debug info
                    print('-'*100, 'Begin: dump response from exe_sell_order_market()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_sell_order_market()')    
                    ##################################################################### debug info                     
    
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_market{Bcolors.ENDC}({qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-201', 'Exceeds the available balance')        
    
        ################################################################## 
        def exe_sell_order_limit(self, qty: float, price: float) -> tuple:       # return (status, res_order_id, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}  ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'S9999-999999-999999', 'OK', 'Fake/Test sell order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method = 'POST'
    
            endPoint = 'https://api.coin.z.com/private'
            path = '/v1/order'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC, ETH, LTC,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999 YEN)        
                'size': qty_str             # Required (1 BTC)
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_order_limit({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?         
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-208'
                msg_str = msg_list[0].get('message_string')     # 'Exceeds the available balance'     
    
                if msg_code == 'ERR-208':   # Exceeds the available balance  
                    pass
                else:
                    #################################################################### debug info
                    print('-'*100, 'Begin: dump response from exe_sell_order_limit()')
                    print(dict) 
                    print('-'*100, 'End  : dump response from exe_sell_order_limit()')    
                    #################################################################### debug info                 
            
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_order_limit{Bcolors.ENDC}({qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-5120', 'Too high price')    
        
        ####################################################################################################################### 
        def exe_buy_close_order_market_wait(self, position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)     
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                if latest_close_price == 0.0: latest_close_price = price
                status = 0
                execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                order_id     = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)  
                position_list = [                         
                                    {
                                        'executionId': execution_id,    # E9999-999999-999999
                                        'fee': '0',                     # 1 => 0 (leverage trading)
                                        'lossGain': '0', 
                                        'orderId': order_id,            # S9999-999999-999999
                                        'positionId': position_id,      # P9999-999999-999999 ★
                                        'price': latest_close_price,  
                                        'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                        'side': 'SELL',                 # 'SELL' or 'BUY'
                                        'size': qty, 
                                        'symbol': symbol, 
                                        'timestamp': dt.datetime.utcnow()
                                    }
                                ]
                                                        
                df = pd.DataFrame(position_list)
                # convert data types
                df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                df['executionId'] = df['executionId'].astype(str)   # int64 => string
                df['orderId'] = df['orderId'].astype(str)           # int64 => string
                df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                msg_code = 'OK'
                msg_str = 'Normal Return'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_id=}, {df.shape=}, {msg_code} - {msg_str}) {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC} ")
                return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')                  
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            df = pd.DataFrame()     
    
            # exe_buy_close_order_market(symbol='BTC_JPY', position_id='999999999', qty='1.1234')'
            status, order_id, msg_code, msg_str = self.exe_buy_close_order_market(position_id, qty)   # ★ FAK (Fill and Kill)   
    
            # error ?
            if status != 0: 
                return (status, df, msg_code, msg_str)    # (1, df, 'msg_code', 'msg_str') 
    
            i = 0
    
            while True:
                i += 1
    
                if i > self.coin.api_market_timeout_limit:  # time out ?
                    status = 8      # time out
                    msg_code = 'ERR-888'
                    msg_str = f'get_price() time out error: loop count={i} '
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return (status, df, msg_code, msg_str)    # error return =>  (9, df, 'ERR-999', 'get_price() time out error:...') 
    
                # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status) 
                status, order_status = self.get_order_status_try(order_id, qty, price)  
                # status: # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED, ERROR            
                if status != 0:
                    msg_code = 'ERR-999'
                    msg_str = f"get_order_status({order_id=}) error ▶ {status=}, {order_status=} "       
                    return (status, df, msg_code, msg_str)    # error return 
                
                ### status == 0    
    
                # exe_buy_close_order_market() is pending status
                if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_buy_close_order_market_wait(): get_order_status({i}) ▶ {status=}, {order_status=} ")
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_order_status()
    
                # order_status : EXECUTED or EXPIRED or CANCELED                    
    
                ### get_order_status() ▶ status==0 & order_status in 'EXECUTED,EXPIRED,CANCELED' 
                sleep(1)            # sleep 1 sec  
    
                # get_price_try(order_id: str, qty: float, price: float, latest_close_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
                status, df, msg_code, msg_str = self.get_price_try(order_id, qty, price)  # qty and price are used for fake mode    
                # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)   
                self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_buy_close_order_market_wait(): get_price({i}) ▶ {status=}, {df.shape=} ")                                           
               
                # error ?
                if status != 0: 
                    # empty dataframe ?
                    if status == 9: 
                        sleep(1)        # sleep 1 sec 
                        continue        # continue get_price()
                    
                    # misc error                
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return            
    
                if df.shape[0] == 0:
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_price()
    
                ### Normal Return           
    
                # ----------------------------------------------------------
                #  #   Column       Dtype         
                # ----------------------------------------------------------        
                #  0   executionId  int64         
                #  1   fee          float64       
                #  2   lossGain     float64                               
                #  3   orderId      object(string)     
                #  4   positionId   object(string)     
                #  5   price        float64  
                #  6   settleType   object('OPEN' or 'CLOSE')  
                #  7   side         object('BUY' or 'SELL')                  
                #  8   size         float64           
                #  9   symbol       object('BTC')                        
                # 10   timestamp    datetime64[ns]          
                # ---------------------------------------------------------
    
                # real_sell_qty = df.iloc[0]['size']
                real_sell_price = df.iloc[0]['price']
                # real_sell_fee = df.iloc[0]['fee']            
     
                if real_sell_price > 0:   
                    msg_code = 'OK'
                    msg_str = 'Normal Return'                
                    break   # got a real price ? 
    
                sleep(1)        # sleep 1 sec
                # continue to get_price()
            # end of while True:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str}")
            # normal or time out error return
            return (status, df, msg_code, msg_str)    # (0, df, 'OK', 'Normal Return')         
    
        ########################################################################### 
        def exe_buy_close_order_market(self, position_id: str, qty: float) ->tuple:     # return (status, res_order_id, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy close order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC}  ")
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake\Test close order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/closeOrder'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC_JPY, ETH_JPY, LTC_JPY,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'MARKET',  # Required (MARKET, LIMIT, STOP) 
                'timeInForce': 'FAK',       # FAK (Fill and Kill)
                'settlePosition': [
                    {
                    'positionId': position_id,   # 162401271
                    'size': qty_str              # 0.01
                    }
                ]
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_close_order_market() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
    
            status = dict.get('status')     # int type    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_close_order_market({qty=:,.{_q_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-???'
                msg_str = msg_list[0].get('message_string')     # 'Error Message'
        
                ############################################################################ debug info
                print('-'*100, 'Begin: dump response from exe_buy_close_order_market()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_buy_close_order_market()')    
                ############################################################################ debug info                     
    
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-???', 'Error Message...')        
    
        ######################################################################################## 
        def exe_buy_close_order_limit(self, position_id: str, qty: float, price: float) ->tuple:    # return (status, res_order_id, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test buy close order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")            
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake/Test close order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/closeOrder'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC_JPY, ETH_JPY, LTC_JPY,...)  
                'side': 'BUY',              # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999 YEN)        
                'settlePosition': [
                    {
                        'positionId': position_id,      # 162401271 
                        'size': qty_str                 # 0.01
                    }
                ]
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_buy_close_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_buy_close_order_limit({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?         
    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-???'
                msg_str = msg_list[0].get('message_string')     # 'Error Message...'     
               
                ######################################################################### debug info
                print('-'*100, 'Begin: dump response from exe_buy_close_order_limit()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_buy_close_order_limit()')    
                ######################################################################### debug info                 
            
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-???', 'Error Message')        
    
        ######################################################################################################################## 
        def exe_sell_close_order_market_wait(self, position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple:     #  return (status, df, msg_code, msg_str) 
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
     
            if not self.coin.real_mode:
                if latest_close_price == 0.0: latest_close_price = price
                status = 0
                execution_id = 'E' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # EMMDD-HHMMSS-999999(milliseconds)
                order_id     = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)  
                position_list = [                         
                                    {
                                        'executionId': execution_id,    # E9999-999999-999999
                                        'fee': '0',                     # 1 => 0 (leverage trading)
                                        'lossGain': '0', 
                                        'orderId': order_id,            # S9999-999999-999999
                                        'positionId': position_id,      # P9999-999999-999999 ★
                                        'price': latest_close_price,  
                                        'settleType': 'OPEN',           # 'OPEN' or 'CLOSE'
                                        'side': 'SELL',                 # 'SELL' or 'BUY'
                                        'size': qty, 
                                        'symbol': symbol, 
                                        'timestamp': dt.datetime.utcnow()
                                    }
                                ]
                                                        
                df = pd.DataFrame(position_list)
                # convert data types
                df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True, errors='coerce')   # UTC (aware) ★ add=> errors='coerce' => NaT or errors='ignore'
                df['executionId'] = df['executionId'].astype(str)   # int64 => string
                df['orderId'] = df['orderId'].astype(str)           # int64 => string
                df['positionId'] = df['positionId'].astype(str)     # int64 => string ★
                df = df.astype({'fee': 'float', 'lossGain': 'float', 'price': 'float', 'size': 'float'})   
    
                msg_code = 'OK'
                msg_str = 'Normal Return'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {order_id=}, {df.shape=}, {msg_code} - {msg_str}) {Bcolors.FAIL}FAKE MODE{Bcolors.ENDC} ")
                return (status, df, msg_code, msg_str)  # (0, df, 'OK', 'Normal Return')                  
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            df = pd.DataFrame()    
    
            # exe_sell_close_order_market(symbol='BTC_JPY', position_id='999999999', qty='1.1234')'
            status, order_id, msg_code, msg_str = self.exe_sell_close_order_market(position_id, qty)   # ★ FAK (Fill and Kill)   
    
            # error ?
            if status != 0: 
                return (status, df, msg_code, msg_str)    # (1, df, 'msg_code', 'msg_str') 
    
            i = 0
    
            while True:
                i += 1
    
                if i > self.coin.api_market_timeout_limit:  # time out ?
                    status = 8      # time out
                    msg_code = 'ERR-888'
                    msg_str = f'get_price() time out error: loop count={i} '
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return (status, df, msg_code, msg_str)    # error return =>  (9, df, 'ERR-999', 'get_price() time out error:...')  
    
                # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)
                status, order_status = self.get_order_status_try(order_id, qty, price) 
                # status: # 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
                # order_status: WAITING, ORDERED, MODIFYING, CANCELLING, CANCELED, EXECUTED, EXPIRED, ERROR             
                if status != 0:
                    msg_code = 'ERR-999'
                    msg_str = f"get_order_status({order_id=}) error ▶ {status=}, {order_status=} "       
                    return (status, df, msg_code, msg_str)    # error return 
                
                ### status == 0    
    
                # exe_sell_close_order_market() is pending status ?
                if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_sell_close_order_market_wait(): get_order_status({i}) ▶ {status=}, {order_status=} ")
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_order_status()
    
                ### order_status : EXECUTED or EXPIRED or CANCELED                    
    
                ### get_order_status() ▶ status==0 & order_status in 'EXECUTED,EXPIRED,CANCELED'
                sleep(1)            # sleep 1 sec     
    
                # get_price_try(order_id: str, qty: float, price: float, latest_close_price=0.0, debug1=False, debug2=False) -> tuple:  # return (status, df, msg_code, msg_str)  qty, price, latest_close_price are used for fake mode
                status, df, msg_code, msg_str = self.get_price(order_id, qty, price)  # qty and price are used for fake mode    
                # 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error) 
                self.debug.trace_write(f".... {self.gvar.callers_method_name} exe_sell_close_order_market_wait(): get_price({i}) ▶ {status=}, {df.shape=} ")                                           
               
                # error ?
                if status != 0: 
                    # empty dataframe ?
                    if status == 9: 
                        sleep(1)        # sleep 1 sec 
                        continue        # continue get_price()
                    
                    # misc error                
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str} ")
                    return status, df, msg_code, msg_str    # error return            
    
                if df.shape[0] == 0:
                    sleep(1)        # sleep 1 sec 
                    continue        # continue get_price()
    
                ### Normal Return           
    
                # -----------------------------------------------------------------
                #  #   Column       Dtype         
                # -----------------------------------------------------------------       
                #  0   executionId  int64         
                #  1   fee          float64       
                #  2   lossGain     float64                               
                #  3   orderId      object(string)     
                #  4   positionId   object(string)     
                #  5   price        float64  
                #  6   settleType   object('OPEN' or 'CLOSE')  
                #  7   side         object('BUY' or 'SELL')                  
                #  8   size         float64           
                #  9   symbol       object('BTC')                        
                # 10   timestamp    datetime64[ns]          
                # ----------------------------------------------------------------
    
                # real_sell_qty = df.iloc[0]['size']
                real_sell_price = df.iloc[0]['price']
                # real_sell_fee = df.iloc[0]['fee']            
     
                if real_sell_price > 0:   
                    msg_code = 'OK'
                    msg_str = 'Normal Return'                
                    break   # got a real price ?  
    
                sleep(1)        # sleep 1 sec
                # continue to get_price()
            # end of while True:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_market_wait{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}}, {price=:,.{_p_}f}): ▶ {status=}, {df.shape=}, {msg_code} - {msg_str}")
            # normal or time out error return
            return (status, df, msg_code, msg_str)    # (0, df, 'OK', 'Normal Return')         
    
        ############################################################################# 
        def exe_sell_close_order_market(self, position_id: str, qty: float) -> tuple:    # return (status, res_order_id, msg_code, msg_str)    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'B' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # BMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell close order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_buy_close_order_market{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")            
                return (status, res_order_id, msg_code, msg_str)  # (0, 'B9999-999999-999999', 'OK', 'Fake\Test close order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/closeOrder'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC_JPY, ETH_JPY, LTC_JPY,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'MARKET',  # Required (MARKET, LIMIT, STOP) 
                'timeInForce': 'FAK',       # FAK (Fill and Kill)
                'settlePosition': [
                    {
                    'positionId': position_id,   # 162401271
                    'size': qty_str              # 0.01
                    }
                ]
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_close_order_market() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
    
            status = dict.get('status')     # int type    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_close_order_market({qty=:,.{_q_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?    
    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-???'
                msg_str = msg_list[0].get('message_string')     # 'Error Message'
        
                ############################################################################ debug info
                print('-'*100, 'Begin: dump response from exe_sell_close_order_market()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_sell_close_order_market()')    
                ############################################################################ debug info                     
    
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_market{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-???', 'Error Message...')        
    
        ########################################################################################## 
        def exe_sell_close_order_limit(self, position_id: str, qty: float, price: float) -> tuple:   # return (status, res_order_id, msg_code, msg_str)   
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            qty_str = f'{qty:.{_q_}f}'
            price_str = f'{price:.{_p_}f}'
    
            if not self.coin.real_mode:
                status = 0    
                res_order_id = 'S' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # SMMDD-HHMMSS-999999(milliseconds)
                msg_code = 'OK'
                msg_str = 'Fake/Test sell close order...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, {res_order_id=} {Bcolors.FAIL}FAKE{Bcolors.ENDC}")            
                return (status, res_order_id, msg_code, msg_str)  # (0, 'S9999-999999-999999', 'OK', 'Fake/Test sell close order...')     
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/closeOrder'
    
            reqBody = {
                'symbol': symbol,           # Required (BTC_JPY, ETH_JPY, LTC_JPY,...)  
                'side': 'SELL',             # Required (BUY or SELL)
                'executionType': 'LIMIT',   # Required (MARKET, LIMIT, STOP)
                'timeInForce': 'FAS',       # FAK (Fill and Store)
                'price': price_str,         # Required (9999999 YEN)        
                'settlePosition': [
                    {
                        'positionId': position_id,      # 162401271 
                        'size': qty_str                 # 0.01
                    }
                ]
            }
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_sell_close_order_limit() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')    
    
            res_order_id = ''   # string
            msg_code = ''
            msg_str = ''
        
            # normal return ?
            if status == 0:
                res_order_id = dict.get('data')  # string type
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_sell_close_order_limit({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()    
    
            else:   # status == ?         
    
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')      # 'ERR-???'
                msg_str = msg_list[0].get('message_string')     # 'Error Message...'     
               
                ########################################################################## debug info
                print('-'*100, 'Begin: dump response from exe_sell_close_order_limit()')
                print(dict) 
                print('-'*100, 'End  : dump response from exe_sell_close_order_limit()')    
                ########################################################################## debug info                 
            
            # end of if status == 0:
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_sell_close_order_limit{Bcolors.ENDC}({position_id=}, {qty=:,.{_q_}f}, {price=:,.{_q_}f}): ▶ {status=}, {res_order_id=}, {msg_code} - {msg_str}")
            # normal or error return
            return (status, res_order_id, msg_code, msg_str)  # (0, '999999', 'OK', 'Normal Return') or (1, '', 'ERR-???', 'Error Message')
    
        ################################################### 
        def exe_cancel_order(self, order_id: str) -> tuple:      # return (status, msg_code, msg_str) 
    
            if not self.coin.real_mode:
                status = 0    
                msg_code = 'OK'
                msg_str = 'Cancelled...'
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_cancel_order{Bcolors.ENDC}({order_id=}): {Bcolors.FAIL}FAKE{Bcolors.ENDC} ")
                return (status, msg_code, msg_str)  # (0, 'OK', 'Cancelled...') 
            # end of if not self.coin.real_mode:
    
            ### real mode
    
            timestamp = '{0}000'.format(int(time.mktime(dt.datetime.now().timetuple())))
            method    = 'POST'
            endPoint  = 'https://api.coin.z.com/private'
            path      = '/v1/cancelOrder'
            reqBody = {'orderId': order_id}
    
            url = timestamp + method + path + json.dumps(reqBody)
            sign = hmac.new(bytes(self.api_secret.encode('ascii')), bytes(url.encode('ascii')), hashlib.sha256).hexdigest()
    
            headers = {
                'API-KEY': self.api_key,
                'API-TIMESTAMP': timestamp,
                'API-SIGN': sign
            }
    
            try:
                res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody), timeout=3)
            except:
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}MISC ERROR:{Bcolors.ENDC} exe_cancel_order() ▶ quit python...")
                self.debug.sound_alert(3)  
                quit()              
            else:       # block lets you execute code when there is no error
                pass
    
            finally:    # block lets you execute code, regardless of the result of the try- and except block
                pass
    
            dict = res.json()
            status = dict.get('status')       
    
            msg_code = ''
            msg_str = ''
        
            # normal return 
            if status == 0:
                msg_code = 'OK'        
                msg_str = 'Normal Return'
    
            elif status == 5:   # Maintenance
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}GMO Maintenance:{Bcolors.ENDC} exe_cancel_order({order_id=}): ▶ {status=}, quit python...")
                self.debug.sound_alert(3)  
                quit()        
    
            else:   # status == ?     
                msg_list = dict.get('messages')
                msg_code = msg_list[0].get('message_code')  # ERR-5122
                msg_str = msg_list[0].get('message_string') #  The request is invalid due to the status of the specified order.
    
                if msg_code == 'ERR-5122':  # The request is invalid due to the status of the specified order
                    pass
                else:        
                    ################################################################# debug info
                    print('-'*100, 'Begin: dump response from exe_cancel_order()')
                    print(dict) # temp temp
                    print('-'*100, 'End  : dump response from exe_cancel_order()')    
                    ################################################################# debug info
    
            # end of if status == 0:                    
    
            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}exe_cancel_order{Bcolors.ENDC}({order_id=}): ▶ {status=}, {msg_code} - {msg_str} ")
            # normal or error return
            return (status, msg_code, msg_str)  # (0, 'OK, 'Normal Return') or (1, 'ERR-5122', 'The request is invalid due to the status of the specified order')
    
        ################################################################################################
        def load_master_driver(self, symbol_list: list, coin_list: list, page: int, count: int) -> None:
            # self.debug.trace_warn(f".... DEBUG: load_master_driver() ")            
            for i, symbol in enumerate(symbol_list):    # 'BTC_JPY',   'ETH_JPY',  'BCH_JPY',  'LTC_JPY',  'XRP_JPY'                
                coin = coin_list[i] # Coin(coin_list[i])                   
                if not coin.suspend:     
                    if self.gvar.reset_mode or coin.master_csv_not_found:
                        # load_master_data(pages: int, count: int, trace=True) -> pd.DataFrame:
                        api = Api(self.gvar, coin)
                        master_df = api.load_master_data(page, count, trace=True)  # (1, 10) ★ sorted by ascending order of timestamp        
                        coin.master_df = master_df  # coin.set_master_df(master_df) 
    
                        # cache master df to a csv file
                        # csv_file = self.folder_trading_type + f'master({symbol}).csv'  
                        csv_file = api.folder_trading_type + f'master({symbol}).csv' 
                        # decktop-pc/bot/buy_sell/master(BTC_JPY).csv
                        # decktop-pc/bot/sell_buy/master(BTC_JPY).csv
                        master_df.to_csv(csv_file, index=False)     # overwrite  
    
            # end of for i, symbol in enumerate(symbol_list):        
    
            return    
        
        ############################################################################### 
        def load_master_data(self, pages: int, count: int, trace=True) -> pd.DataFrame:     # return merge_df  
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            # self.debug.trace_warn(f".... DEBUG: load_master_data() {trace=} ")
    
            # load the master data from the GMO
            if trace:
                self.debug.print_log(f".... {self.coin.fake_or_real}({symbol}) {self.coin.trade_type}: load_master_data({pages=}, {count=}): loading {symbol} master data from the GMO ")
                # .... fake buy_sell: or fake sell_buy: or real buy_sell: or real sell_buy:
    
            df_list = []
            for page in range(1, pages+1):   # (1, 11)           
                # get_crypto_try(page: int, count: int) -> tuple:   # return (status, df)
                status, df = self.get_crypto_try(page, count)       # (1, 10)    
                sleep(2.5)   # sleep 0.5 sec => 1 sec => 2 sec => 2.5 sec 
                if status == 0: df_list.append(df)                        
            # end of for page in range(1, pages+1):   # (1, 11)  
            
            if len(df_list) > 0:
                merge_df = pd.concat(df_list, axis=0) # price, side, size, timestamp(UTC) descending order
            else:
                merge_df = pd.DataFrame()
    
            # merge_df: gmo master dataframe    
            # --------------------------------------------------------------------------------
            #  #   Column     Dtype              
            # --------------------------------------------------------------------------------              
            #  0   price      float64            
            #  1   side       object    'BUY' or 'SELL'             
            #  2   size       float64            
            #  3   timestamp  datetime64[ns, UTC] ★ UTC descending order     
            # --------------------------------------------------------------------------------               
    
            ### sort by timestamp(in ascending order) 
            if merge_df.shape[0] > 0:
                merge_df.sort_values('timestamp', ascending=True, inplace=True) 
    
            return merge_df