Python {Article119}

ようこそ「Python」へ...

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

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

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

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

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

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

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

BOTのSellBuyクラスを作成する

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

    ここではSellBuyクラスを作成して__init__(), __str__()メソッドを追加します。 SellBuyクラスには、BOTが自動トレードするための各種メソッドがまとめられています。 レバレッジ取引には「買い▶売り」と「空売り▶買戻し」の2種類ありますが、 SellBuyクラスでは後者の「空売り▶買戻し」の取引に関連するメソッドがまとめられています。

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

    sell_buy.py:
    # sell_buy.py
    
    """
    SellBuy class
    """
    
    # import the libraries
    from time import sleep  
    import datetime as dt
    from datetime import timedelta
    
    import math
    import statistics
    from decimal import Decimal
    
    import numpy as np
    import pandas as pd
    
    from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe 
    from lib.debug import Debug
    from lib.api import Api
    from lib.csv import Csv
    from lib.trade import Trade    
    
    import warnings
    warnings.simplefilter('ignore')
    
    ########################### sell buy class
    class SellBuy:
        """
        __init__(), __str__(),     
        reset_restart_process(), 
        sell_buy(), sell_buy_algorithm_0(), sell_buy_algorithm_1, sell_buy_algorithm_2(), sell_buy_algorithm_3(),...
        create_sell_order(), create_buy_order()
        update_order(), update_closed_order(), update_partially_closed_order(), update_order_profit() 
        stop_loss(), stop_loss_0(), stop_loss_1(),...
        cancel_pending_order()
        close_pending_order()
        """
           
        ######################################################
        def __init__(self, gvar: object, coin: object) ->None: 
            self.gvar = gvar                            # Gvar
            self.coin = coin                            # Coin       
            self.debug = Debug(gvar)                    # dependent class       
            self.api = Api(gvar, coin)                  # dependent class
            self.csv = Csv(gvar, coin)                  # 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/sell_buy/                     
            self.debug.print_log(f"{Bcolors.OKGREEN}SellBuy({self.gvar.althorithm_id},{self.gvar.sellbuy_condition}){Bcolors.ENDC} class initiated: {self.coin.symbol}, qty=[{self.coin.qty:,.{self.coin.qty_decimal}f}], rate=[{self.coin.profit_rate:.4f}, {self.coin.stop_loss_rate:.4f}], debug1={self.coin.debug_mode_debug1}, debug2={self.coin.debug_mode_debug2}  ")  
            return
    
        #########################
        def __str__(self) -> str:              
            return f"SellBuy({self.folder_gmo=}, {self.folder_trading_type=}, {self.gvar.real_mode=}, {self.gvar.reset_mode=}, {self.gvar.althorithm_id=}, {self.coin.symbol=})"  

    click image to zoom!
    図1-1
    図1-1はBOTとSellBuyクラスの各種メソッドの関連図です。 BOTのmain()から関数「auto_trade()」を呼びます。 auto_trade()では、GMOコインから取引履歴をロードします。 そしてSellBuyクラスのsell_buy()メソッドを呼び出して取引データを渡します。

    sell_buy()メソッドではアルゴリズムを使用して取引のタイミングを決めてcreate_sell_order(), create_buy_order()メソッドを呼びます。 取引の処理が完了するとauto_trade()に戻ります。

    auto_trade()は、次にSellBuyクラスのupdate_order()メソッドを呼びます。 update_order()では、update_closed_order()メソッドを呼んで取引が約定(執行)された注文を更新します。 さらにコール元の要求があれば、 stop_loss(), cancel_pending_order(), close_pending_order()等の処理を行います。 最後にupdate_order_profit()メソッドを呼び出して取引の損益を計算してauto_trade()に戻ります。


    click image to zoom!
    図1-2
    図1-2はデモプログラムの実行結果です。 ここではSellBuyクラスの各種プロパティを表示しています。 print()でSellBuyのオブジェクトを指定するとSellBuyクラスの__str__()メソッドが呼ばれます。 __str__()はSellBuy, Gvar, Coinクラスから各種プロパティを取得して返します。


  2. SellBuyクラスにreset_restart_process()メソッドを追加する

    ここではSellBuyクラスにreset_restart_process()メソッドを追加します。 このメソッドはBOTによる自動トレードで必要となる各種CSVファイルを作成して初期化します。

    sell_buy.py:
        ######################################## 
        def reset_restart_process(self) -> None:
            """
            
            """
            self.gvar.callers_method_name = 'reset_restart_process():'
            symbol = self.coin.symbol
    
            # get master df from the coin class
            master_df = self.coin.master_df     
            if master_df.shape[0] == 0: 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - master_df is empty {master_df.shape[0]=} return ")
                self.debug.sound_alert(3)    
                return             
    
            order_qty = self.coin.qty       
            mas_df = master_df    
    
            # --------------------------------------------------------------
            #  #   Column     Dtype
            # --------------------------------------------------------------
            #  0   price      float64
            #  1   side       object
            #  2   size       float64
            #  3   timestamp  datetime64[ns, UTC]    
            # -------------------------------------------------------------              
    
            ### get latest buy order info from the GMO dataframe (timestamp is sorted by ascending order)
    
            buy_time = mas_df.iloc[0]['timestamp']  # get oldest time
            buy_price = mas_df.iloc[0]['price']     # get oldest price
            buy_order_id = 'FakeB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')       # FakeB:MMDD-HHMMSS-999999(milliseconds)
            buy_position_id = 'PosiB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PosiB:MMDD-HHMMSS-999999(milliseconds)                   
    
            # round time
            freq = '1T'                           
            buy_time = buy_time.round(freq=freq)                                        # round x minutes (where x is 1,4,10,15,30)   
            buy_time = buy_time.round(freq='S')                                         # round seconds
            buy_time = buy_time - timedelta(hours=1)                                    # substract 1 hour
    
            ### get sell order info from the GMO dataframe
               
            sell_time = buy_time # + timedelta(seconds=5)
            sell_price = self.trade.get_sell_price(buy_price)                                  
            sell_order_id = 'FakeS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')      # FakeS:MMDD-HHMMSS-999999(milliseconds)
            sell_position_id = 'PosiS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')   # PosiS:MMDD-HHMMSS-999999(milliseconds)
          
            # reset mode ?
            if self.gvar.reset_mode or self.coin.master_csv_not_found:      
                if self.gvar.reset_mode:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} reset): delete_allfiles() ▶ add a fake data into the sell/buy order csv files...")
                else:    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): master_csv_not_found(True) ▶ add a fake data into the sell/buy order files...")
    
                ### 1) add a fake data into the sell order, sell order sub files
    
                # write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:     
                self.csv.write_sell_order(sell_time, buy_time, order_qty, sell_price, order_qty, sell_price, 0.0)                     
                
                # write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:   
                self.csv.write_sell_order_sub(sell_time, buy_time, sell_order_id, buy_order_id, sell_position_id, order_qty, sell_price, order_qty, sell_price, 0.0)
    
                ### 2) add a fake data into the buy order, buy order sub files
    
                # write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
                self.csv.write_buy_order(buy_time, sell_time, order_qty, buy_price, order_qty, buy_price, 0.0)                                              
    
                # write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None: 
                self.csv.write_buy_order_sub(buy_time, sell_time, buy_order_id, sell_order_id, buy_position_id, order_qty, buy_price, order_qty, buy_price, 0.0)        
        
            else: # restart mode
                self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): skip close pending orders for restart...")     
            # end of if self.gvar.reset_mode: 
    
            return       

    click image to zoom!
    図2
    図2はデモプログラムの実行結果です。 ここではGvarクラスのreset_modeプロパティに「True」を設定して初期化モードで実行しています。 この場合、BOTのサブフォルダ「sell_buy」に売りの注文ファイル「sell_order({symbol}).csv, sell_order_sub({symbol}).csv」 と買いの注文ファイル「buy_order({symbol}).csv, buy_order_sub({symbol}).csv」が作成されて初期化されます。

    CSVファイルは仮想通貨ごとに作成されます。 たとえば、「sell_order(BTC_JPY).csv, sell_order(ETH_JPY).csv, sell_order(BCH_JPY).csv,...」のようなCSVファイルが作成されます。


  3. SellBuyクラスにsell_buy(), sell_buy_algorithm_0()メソッドを追加する

    ここではSellBuyクラスにsell_buy()メソッドを追加します。 このメソッドはBOTの肝になる部分です。 sell_buy()メソッドの引数には「algorithm_id」を指定します。 「algorithm_id=0」はデフォルトで用意されています。 BOTのテストを行うときは「algorithm_id=0」を指定して行います。

    BOTのテストが完了したら独自のアルゴリズムを作成して 新規メソッドとして「sell_buy_algorithm_1()」、「sell_buy_algorithm_2()」のように追加します。 仮想通貨を売買(トレード)するアルゴリズムは、 「記事(Article108)」で紹介しているさまざまなテクニカルインジケーターを組み合わせて作ります。 具体的な新規メソッドの作り方については後述します。

    デフォルトで用意されている「sell_buy_algorithm_0()」メソッドは単純なアルゴリズムを組み込んでいます。 まずは、前回取引(トレード)した注文が約定しているか調べます。 まだ、約定していないときは何もしないで戻ります。 前回の取引が既に約定しているときは、成行きで空売りの注文を入れます。 通常、成行きの注文はすぐに約定するので注文が約定するまで待ちます。 空売りの注文が約定したら、今度は指値で買戻しの注文を入れてポジションを決済します。 指値の買戻しの注文を入れるときの金額は、 単純に空売りの金額(レート)に「1 - 利益率」を掛けて計算します。 利益率はCoinクラスの「coin.profit_rate」プロパティに設定されています。

    具体的には、 空売りの注文を入れるときは「create_sell_order()」メソッド、 買戻しの注文を入れるときは「create_buy_order()」メソッドを呼びます。

    sell_buy.py:
        ##############################################  
        def sell_buy(self, algorithm_id: int) -> None:  
            """
            
            """
            if algorithm_id <= 3:
                eval(f'self.sell_buy_algorithm_{str(algorithm_id)}()')    # call 'self.sell_buy_algorithm_?() method
            else:
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} -  skip sell_buy ")
                self.debug.sound_alert(3)   
    
            # if algorithm_id == 0:
            #     self.sell_buy_algorithm_0() 
            # elif algorithm_id == 1:
            #     self.sell_buy_algorithm_1()                
            # elif algorithm_id == 2:             
            #     self.sell_buy_algorithm_2()
            # elif algorithm_id == 3:             
            #     self.sell_buy_algorithm_3()            
            # else:
            #     self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} -  skip sell_buy ")
            #     self.debug.sound_alert(3)                   
    
            return        
     
        #######################################  
        def sell_buy_algorithm_0(self) -> None:
            """
    
            """        
            self.gvar.callers_method_name = 'sell_buy(0):'  
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal               
    
            df = self.coin.master2_df
            mas_df = pd.DataFrame(df) # cast dataframe()
    
            ### filter buy master
            filter_mask = mas_df['side'] == 'BUY'
            mas_buy_df = mas_df[filter_mask]
            # mas_buy_df is less than trend_search_count ? => SYTEM ERROR
            if mas_buy_df.shape[0] < self.coin.trend_search_count:  # < 3 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - {mas_buy_df.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
                self.debug.sound_alert(3)    
                return   
    
            ### filter sell master
            filter_mask = mas_df['side'] == 'SELL'
            mas_sell_df = mas_df[filter_mask]
            # mas_sell_df is less than trend_search_count ? => SYTEM ERROR
            if mas_sell_df.shape[0] < self.coin.trend_search_count:  # < 3 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - {mas_sell_df.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
                self.debug.sound_alert(3)    
                return 
         
            self.debug.trace_write(f".... {self.gvar.callers_method_name} sell_buy_condition({self.gvar.sellbuy_condition}), {mas_buy_df.shape[0]=},  {mas_sell_df.shape[0]=} ")  
    
            # mas_df:
            # -------------------------------------------------------------
            #  #   Column      Dtype
            # -------------------------------------------------------------
            #  0   time        datetime64[ns, UTC]
            #  1   open        float64
            #  2   close       float64
            #  3   low         float64
            #  4   high        float64
            #  5   price       float64
            #  6   buy_price   float64
            #  7   sell_price  float64
            #  8   side        object
            #  9   size        float64
            #  10  symbol      object
            # ---------------------------------------------------------------
                           
            position = 'buy or sell'                                     
    
            ### load buy order / buy order sub files
            buy_df = self.csv.get_buy_order()             # ★   
            buy_sub_df = self.csv.get_buy_order_sub()     # ★         
    
            ### load sell order / sell order sub files
            sell_df = self.csv.get_sell_order()           # ★ 
            sell_sub_df = self.csv.get_sell_order_sub()   # ★  
      
            ### update the position depending on buy/sell sub order files            
    
            ### get the last sell order sub info
            last_buy_time  = sell_sub_df.iloc[-1]['buy_time']  
            last_sell_time = sell_sub_df.iloc[-1]['sell_time']      
            last_sell_real_qty = sell_sub_df.iloc[-1]['real_qty']       
            last_sell_real_price = sell_sub_df.iloc[-1]['real_price']      
    
            ### get the last buy order (pair of the sell order)
    
            # find the pair of the buy order
            find_mask = buy_df['buy_time'] == last_buy_time
            dfx = buy_df[find_mask]
            # not found buy order ?
            if dfx.shape[0] == 0:
                # self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ buy order not found: {last_buy_time:%Y-%m-%d %H:%M:%S=} ")
                last_buy_real_qty = 0
            else: ### found pair of the buy order
                # get buy order real_qty
                ix = dfx.index[0]
                last_buy_real_qty = dfx.loc[ix, 'real_qty']
    
            # closed sell/buy order ?
            if round(last_buy_real_qty, _q_) == round(last_sell_real_qty, _q_):
                position = 'sell'    # buy order not required => position(SELL) 
                self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}NEW SELL ORDER REQUEST{Bcolors.ENDC} ")     
            else: # not found buy order or incomplete buy order
                return  # skip return
            # end of if last_buy_real_qty == last_sell_real_qty:    
    
            # sell position ? 
            if position.startswith('sell'):       
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SELL{Bcolors.ENDC} position (short position) ")
                
                ### SELL position 
    
                ### check sell condition and create a sell order
        
                # get the latest sell_time & latest sell_price from GMO master dataframe
                mas_time  = mas_sell_df.iloc[-1]['time']     # gmo time      ★             
                mas_price = mas_sell_df.iloc[-1]['price']    # gmo price    ★                
    
                while True:    
    
                    ###########################################################
                    ### 1: check duplicate sell order
                    ###########################################################
    
                    # mas_time(gmo) is less than or equal to last_sell_time ? => SKIP
                    if mas_time <= last_sell_time: 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(1){Bcolors.ENDC} mas_time(gmo) is less than or equal to last_sell_time: {mas_time=:%Y-%m-%d %H:%M:%S} <= {last_sell_time=:%Y-%m-%d %H:%M:%S} ")      
                        break   # exit while true loop              
                                                        
                    # found sell order ? => SKIP
                    # find_order(df: pd.DataFrame, col: str, val=any) -> bool:
                    if self.trade.find_order(sell_df, col='sell_time', val=mas_time):   
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(2){Bcolors.ENDC} duplicate sell order: {mas_time=:%Y-%m-%d %H:%M:%S} ") 
                        break   # exit while true loop 
    
                    ### no duplicate sell order      
    
                    ###########################################################
                    ### 2: execute sell order (MARKET) & buy order (LIMIT)
                    ###########################################################                
    
                    qty = self.coin.qty                                                                
                    
                    ### execute sell order with qty (MARKET) 
                    # create_sell_order(sell_time: utcnow, qty: float, price: float) -> int: 
                    status = self.create_sell_order(mas_time, qty, mas_price)    # ★ price is used for fake mode: execute sell order with market price
    
                    if status == 0:
                        ### reload sell order sub file     
                        sell_sub_df = self.csv.get_sell_order_sub()     # ★ 
    
                        ### get the last sell order sub info
                        last_sell_time  = sell_sub_df.iloc[-1]['sell_time']                                          
    
                        ### execute buy order (LIMIT) 
                        # create_buy_order( buy_time: utcnow, ms_price: float, sell_time: utcnow) -> int:              
                        status = self.create_buy_order(mas_time, mas_price, last_sell_time) # ★ 
    
                    break   # exit while true loop            
                # end of while True:                
            
            return     
        
        ######################################## 
        def sell_buy_algorithm_1(self)  -> None:
            """
    
            """        
        
            return  
        
    
        ######################################## 
        def sell_buy_algorithm_2(self)  -> None:
            """
    
            """        
        
            return  
    
        ####################################### 
        def sell_buy_algorithm_3(self) -> None:
            """
    
            """        
        
            return       

    click image to zoom!
    図3
    図3はデモプログラムの実行結果です。 画面にはログ情報が表示されています。 BOTからSellBuyクラスのsell_buy(0)メソッドが呼ばれています。 さらにsell_buy(0)からcreate_sell_order(), create_buy_order()メソッドが呼ばれています。 sell_buy(0)では一定のアルゴリズムに従って空売りと買戻しの注文を出します。

    このsell_buy()がBOTの肝になる部分です。 sell_buy()には、「記事(Article108)」 で解説しているさまざまなテクニカルインジケーターを組み合わせて新規のメソッドを 「sell_buy_algorithm_1(), sell_buy_algorithm_2(),...」のようなメソッド名で追加します。 なお、デフォルトとして「sell_buy_algorithm_0()」メソッドが用意されています。 このメソッドはBOTをテストするときに使用します。 デモプログラムでは、デフォルトのアルゴリズムを使用しています。


  4. SellBuyクラスにcreate_sell_order()メソッドを追加する

    ここではSellBuyクラスにcreate_sell_order()メソッドを追加します。 このメソッドでは、新規の空売りの注文を成行きの指定で入れます。 成行き注文の場合、FAK(Fill and Kill」が適用されて、 全ての数量が執行されないで終了することがあります。 この場合、create_sell_order()では残数の数量に対して再度成行きの注文を入れます。 すべての数量が執行されたらコール元に戻ります。

    sell_buy.py:
        ############################################################################################ 
        def create_sell_order(self, sell_time: dt.datetime.utcnow, qty: float, price: float) -> int: 
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal        
            
            self.gvar.callers_method_name = 'create_sell_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}) ")  
            status = 0
    
            # reset buy/sell position count
            self.coin.position_count_0_10 = 0        
            self.coin.position_count_11_30 = 0       
            self.coin.position_count_31_50 = 0      
            self.coin.position_count_51_70 = 0      
            self.coin.position_count_71_90 = 0      
            self.coin.position_count_91_999 = 0     
        
            ################################################
            ### 【1】CHECK DUPLICATE SELL ORDER 
            ################################################
    
            ### check duplicate sell order 
    
            # OPEN (load the sell order file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order()             
    
            # find a sell order 
            find_mask = sell_df['sell_time'] == sell_time
            dfx = sell_df[find_mask]          
            # duplicate sell order ?     
            if dfx.shape[0] > 0:
                status = 9      # set internal status code 
                self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate sell order ({sell_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
                return status   # error return       
    
            # reset buy_position_list
            self.coin.buy_position_list = [] 
    
            #############################################################
            ### 【2】EXECUTE SELL ORDER MARKET (FAK: Fill And Kill)
            #############################################################
    
            ### execute sell order with market price : FAK (Fill And Kill)       
    
            sell_qty = qty       # save original sell qty ★ 
    
            # define sell order header list
            header_real_qty_list = []
            header_real_price_list = []
            header_real_fee_list = []          
    
            status = 0
            loop_count = 0      
    
            while True:
                loop_count += 1
    
                # 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 = self.api.exe_sell_order_market_wait(sell_qty, price)  # ★ FAK (Fill And Kill) : price is used for fake mode  
                res_df = pd.DataFrame(res_df)   # cast           
                self.debug.print_log(f".... {self.gvar.callers_method_name} while({loop_count}): exe_sell_order_market_wait() ▶ {status=}, {res_df.shape=} ")
    
                ####################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()           
                if not self.coin.real_mode:
                    if self.coin.debug_mode_debug1: 
                        # ★ create two sell order subs for each sell order header ★
                        # update_get_price_response_for_debug1_1(res_df: pd.DataFrame, loop_count: int) -> pd.DataFrame:
                        res_df = self.trade.update_get_price_response_for_debug1_1(res_df, loop_count)   
                        # loop_count == 1: return 2 rows => add 2 buy subs
                        # loop_count == 2: return 1 row  => add 1 buy sub                       
                # end of if not self.coin.real_mode:
                ###################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()
    
                # exe_sell_order_market_wait() error ?
                if status != 0:
                    if status == 1 and msg_code == 'ERR-201': # Trading margin is insufficient ?      
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {Bcolors.FAIL}the trading margin is insufficient error: ERR-201{Bcolors.ENDC} ")
                        self.debug.sound_alert(2)  
                        break       # exit while true loop => error return         
                    elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}get_price() time out error: {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                        break       # exit while true loop => error return 
                    else: # misc error
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                        break       # exit while true loop => error return 
                # end of if status != 0:
                             
                ### no error : res_df.shape[0] >= 1 (single row or multiple rows)            
                       
                ################################################################################################################
                ### 【3】ADD A SELL ORDER CHILD
                ################################################################################################################
    
                # res_df = get_price() response for exe_buy_order_market_wait()
                # ---------------------------------------------------------
                #  #   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]
                # --------------------------------------------------------- 
    
                ### write a sell order child
          
                # define sell order child list
                sell_order_id_list = []
                position_id_list = []
                real_qty_list = []
                real_price_list = []
                real_fee_list = []   
    
                # response df for exe_sell_order_market_wait(FAK) 
                for k in range(len(res_df)):
                    sell_order_id   = res_df.loc[k, 'orderId']          # sell order id (same order id)
                    position_id     = res_df.loc[k, 'positionId']       # position id (unique id)
                    real_qty        = res_df.loc[k, 'size']             # real qty  
                    real_price      = res_df.loc[k, 'price']            # real price
                    real_fee        = res_df.loc[k, 'fee']              # real fee
    
                    sell_order_id_list.append(sell_order_id)            # S9999-999999-999999
                    position_id_list.append(position_id)                # P9999-999999-999999
                    real_qty_list.append(real_qty)                  
                    real_price_list.append(real_price)
                    real_fee_list.append(real_fee)
    
                    # write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:        
                    _buy_time_ = sell_time; _buy_order_id_ = 'Dummy';  _qty_ = real_qty; _price_ = real_price  
                    self.csv.write_sell_order_child(sell_time, sell_order_id, _buy_time_, _buy_order_id_, position_id, _qty_, _price_, real_qty, real_price, real_fee)
                # end of for k in range(len(res_df)): 
    
                first_sell_order_id = sell_order_id_list[0]   # same order ids              
                position_ids_by_semicolon = ''  # 'positionId1;positionId2;positionId3'
                for k, position_id_x in enumerate(position_id_list):
                    if k == 0:
                        position_ids_by_semicolon += position_id_x
                    else:
                        position_ids_by_semicolon += ';' + position_id_x    
    
                sub_sum_real_qty = round(sum(real_qty_list), _q_)   # XRP_JPY(9999)              
                sub_sum_real_fee = round(sum(real_fee_list), 0)     # zero(0)
    
                if sum(real_price_list) > 0:
                    sub_mean_real_price = round(statistics.mean(real_price_list), _p_)  # 999.999
                else:
                    sub_mean_real_price = 0                      
    
                header_real_qty_list.append(sub_sum_real_qty)        
                header_real_price_list.append(sub_mean_real_price)
                header_real_fee_list.append(sub_sum_real_fee)                   
    
                ###################################################################################################################
                ### 【4】ADD A SELL ORDER SUB (buy_time, sell_time,...) ★ buy_time <= sell_time
                ###################################################################################################################
    
                ### write a sell order sub 
                # write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
                _buy_time_ = sell_time; _buy_order_id_ = 'Dummy';  _qty_ = sub_sum_real_qty # positionId = 'positionId1;positionId2;positionId3'
                self.csv.write_sell_order_sub(sell_time, _buy_time_, first_sell_order_id, _buy_order_id_, position_ids_by_semicolon, _qty_, price, sub_sum_real_qty, sub_mean_real_price, sub_sum_real_fee) # ★
    
                ### check exit condition     
    
                header_sum_real_qty = round(sum(header_real_qty_list), _q_) # XRP_JPY(9999)            
                # self.debug.trace_write(f".... {self.gvar.callers_method_name} while({loop_count}): buy_qty={buy_qty}, header_sum_real_qty={header_sum_real_qty} ")             
          
                ########################################### TEMP TEMP TEMP
                # temp_qty = round(qty, _q_) 
                # temp_real_qty = round(header_sum_real_qty, _q_)
                # self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
                ########################################### TEMP TEMP TEMP            
    
                # no outstanding sell_qty ?
                if round(qty, _q_) == round(header_sum_real_qty, _q_):  
                    # increment sell count      
                    self.gvar.sell_count += 1  
                    self.coin.sell_count += 1                  
                    break   # exit while true loop             
                else:       # outstanding sell_qty exists      
                    pass    # skip (do nothing)                      
                # end of if round(buy_qty, digits) == round(header_sum_real_qty, digits):  
    
                ### partial exe_sell_order_market_wait() was executed => continue to sell remaining qty
                    
                remaining_qty = sell_qty - header_sum_real_qty   # calculate new sell qty        
    
                sell_qty = remaining_qty # update new sell qty      
    
                # continue exe_sell_order_market_wait() ★           
            # end of while True:    
    
            #################################################################################
            ### 【5】ADD A SELL ORDER HEADER (buy_time, sell_time) ★ buy_time <= sell_time
            #################################################################################
    
            ### update sell order header(real_qty, real_price, real_fee)
            header_sum_real_qty = round(sum(header_real_qty_list), _q_)   # XRP_JPY(9999)    
            header_sum_real_fee = round(sum(header_real_fee_list), 0)     # zero(0)        
    
            if sum(header_real_price_list) > 0:
                header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)  # XRP_JPY(9999.123)
            else:
                header_mean_real_price = 0            
    
            ### write a sell order  
            # write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:    
            _buy_time_ = sell_time  
            self.csv.write_sell_order(sell_time, _buy_time_, qty, price, header_sum_real_qty, header_mean_real_price, header_sum_real_fee)       
             
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order() ▶ {status=} normal return... ")                
    
            return status         

    click image to zoom!
    図4-1
    図4-1はデモプログラムの実行結果です。 ここでは関数「auto_trade()」からSellBuyクラスのsell_buy(althorithm_id=0)メソッドを呼び出しています。 sell_buy()メソッドの引数に「althorithm_id=0」を指定しているので「アルゴリズム0」が適用されて空売り・買戻しの取引を行います。 空売りのタイミングになるとSellBuyクラスのcreate_sell_order()メソッドを呼び出して成行きの空売りの注文を入れます。


    click image to zoom!
    図4-2
    図4-2はcreate_sell_order()で作成したCSVファイルをExcelで開いた画像です。 「sell_order(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qty, real_price,...」などが格納されています。 「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。 ここでは数量(10XRP_JPY)を注文しています。

    「sell_order_sub(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, qty, price, real_qty, real_price,...」などが格納されています。 「position_id」のポジションIDは買戻しの注文を入れるときに使います。空売りの注文は成行きなので通常すぐに約定します。

    「sell_order_child(XRP_JPY).csv」ファイルには「sell_time, sell_order_id, position_id, qty, price, reqal_qty, real_price,...」などが格納されています。 空売りの注文のCSVファイルがなぜ3段階に階層化されているかと言えば、注文が約定されるとき数量が分割されるからです。 たとえば、数量「100XRP_JPY」を成行きで注文すると「20, 30, 50」のように分割されて約定されることがあります。 さらに、成行き注文のときは(FAK: Fill and Kill)が適用されて全数約定しないことがあります。 このような場合、create_sell_order()は残数の成行き注文を再度入れて全数約定するようにしています。

    たとえば、数量「100」を成行きで注文して「70」だけ約定したときは、再度「30」で成行き注文を入れます。 この場合、1回の空売りの注文で2個の注文IDが生成されることになります。 こういった理由で、空売りの注文のCSVファイルは3階層になっています。 ちなみに、買い注文のCSVファイルも同様に3階層になっています。


  5. SellBuyクラスにcreate_buy_order()メソッドを追加する

    ここではSellBuyクラスにcreate_buy_order()メソッドを追加します。 このメソッドは前出のcreate_sell_order()で空売りした仮想通貨を決済するために、 指値で買戻しの注文を入れます。 指値の場合、成行きの注文と異なり「Fill and Store」が適用されます。 したがって、成行きのように途中で注文が終了することはありません。

    sell_buy.py:
        ################################################################################################################ 
        def create_buy_order(self, buy_time: dt.datetime.utcnow, ms_price: float, sell_time: dt.datetime.utcnow) -> int:     
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal                
    
            self.gvar.callers_method_name = 'create_buy_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, {ms_price=:,.{_p_}f}, {sell_time=:%Y-%m-%d %H:%M:%S}) ") 
    
            status = 0    
            
            ################################################
            ### 【1】CHECK DUPLICATE BUY ORDER 
            ################################################
    
            ### check duplicate buy order 
    
            # get the buy order : sbuy_order(BTC_JPY).csv
            buy_df = self.csv.get_buy_order()          
    
            # find a buy order 
            find_mask = buy_df['buy_time'] == buy_time   # PK(buy_time)
            dfx = buy_df[find_mask]          
            # duplicate buy order ?     
            if dfx.shape[0] > 0:
                status = 9      # set internal status code
                self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate buy order ({buy_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
                return status   # error return 
              
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv          
            sell_sub_df = self.csv.get_sell_order_sub()  
    
            ### OPEN (load the sell order child csv file ) : sell_order_child(BTC_JPY).csv          
            sell_ch_df = self.csv.get_sell_order_child()  
       
            ######################################################################
            ### 【2】READ ALL SELL ORDER SUBs (filter by sell_time)
            ######################################################################        
            
            # define sell order sub list
            sub_qty_list = []
            sub_price_list = []
    
            # filter the sell order sub by sell_time
            filter_mask = sell_sub_df['sell_time'] == sell_time
            sell_subx_df = sell_sub_df[filter_mask]
            if sell_subx_df.shape[0] > 0:
                sell_subx_df.reset_index(inplace=True)
    
            # get all sell order sub filtered by sell_time 
            for i in range(len(sell_subx_df)):   # ★ sell_subx_df ★
                ### get sell order sub info
                sub_sell_time       = sell_subx_df.loc[i, 'sell_time']        # non unique                 
                sub_sell_order_id   = sell_subx_df.loc[i, 'sell_order_id']    # unique id  
                sub_qty             = sell_subx_df.loc[i, 'qty']                                    
                sub_price           = sell_subx_df.loc[i, 'price']               
                sub_real_qty        = sell_subx_df.loc[i, 'real_qty']                            
                sub_real_price      = sell_subx_df.loc[i, 'real_price']            
                sub_real_fee        = sell_subx_df.loc[i, 'real_fee']                  
                      
                #################################################################################
                ### 【3】READ ALL SELL ORDER CHILD (filter by sell_time + sell_order_id)
                #################################################################################  
    
                # define sell order child list
                ch_buy_order_id_list = []
                ch_sell_order_id_list = []
                ch_position_id_list = []
                ch_qty_list = []
                ch_price_list = []                  
    
                ### filter the sell order child by sell_time + selll_order_id
                
                # sell order child is empty ?
                if sell_ch_df.shape[0] == 0:            
                    sell_chx_df = pd.DataFrame()
                else: # filter sell order child    
                    filter_mask = (sell_ch_df['sell_time'] == sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id)
                    sell_chx_df = sell_ch_df[filter_mask]
                    if sell_chx_df.shape[0] > 0:
                        sell_chx_df.reset_index(inplace=True)
                # end of if sell_ch_df.shape[0] == 0: 
    
                # get all sell order child filtered by sell_time + sell_order_id
                for j in range(len(sell_chx_df)):   # ★ sell_chx_df ★
                    ### get buy order child info
                    ch_sell_time     = sell_chx_df.loc[j, 'sell_time']      # non unique time       
                    ch_sell_order_id = sell_chx_df.loc[j, 'sell_order_id']  # non unique id (same sell_order_id)      
                    ch_position_id   = sell_chx_df.loc[j, 'position_id']    # unique id
                    ch_qty           = sell_chx_df.loc[j, 'qty']                                    
                    ch_price         = sell_chx_df.loc[j, 'price']               
                    ch_real_qty      = sell_chx_df.loc[j, 'real_qty']                            
                    ch_real_price    = sell_chx_df.loc[j, 'real_price']            
                    ch_real_fee      = sell_chx_df.loc[j, 'real_fee'] 
    
                    #############################################################################################
                    ### 【4】EXECUTE BUY CLOSE ORDER LIMIT (FAS:Fill And Store) FOR EACH SELL ORDER CHILD 
                    ############################################################################################# 
           
                    buy_qty = ch_real_qty
                    buy_price = self.trade.get_buy_price(ch_real_price, trace=True)                          
    
                    # market price is less than than buy price
                    if ms_price < buy_price:
                        buy_price = ms_price                  
                  
                    ### execute a buy close order with limit(FAS) 
                    # exe_buy_close_order_limit(position_id: str, qty: float, price: float) ->tuple:    # return (status, res_order_id, msg_code, msg_str)
                    status, res_buy_order_id, msg_code, msg_str = self.api.exe_buy_close_order_limit(ch_position_id, buy_qty, buy_price) # ★      
                    # res_buy_order_id => 'B9999-999999-999999'       
    
                    # exe_buy_close_order_limit() error ?
                    if status != 0:
                        if status == 1 and msg_code == 'ERR-208':   # Exceeds the available balance
                            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}the available balance exceeded ERR-208: continue...{Bcolors.ENDC} ")
                            self.debug.sound_alert(2)  
                            continue    # continue to next sell order child        
                        else: # misc error
                            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")                               
                            self.debug.sound_alert(2) 
                            continue    # continue to next sell order child             
                    # end of if status != 0:
    
                    ### no error for exe_buy_close_order_limit()        
    
                    ################################################################################################################
                    ### 【5】ADD A BUY ORDER SUB
                    ################################################################################################################
                            
                    ### write a buy order sub
                    # write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:                                                       
                    self.csv.write_buy_order_sub(buy_time, sell_time, res_buy_order_id, ch_sell_order_id, ch_position_id, buy_qty, buy_price)       
     
                    ch_buy_order_id_list.append(res_buy_order_id)
                    ch_sell_order_id_list.append(ch_sell_order_id)
                    ch_position_id_list.append(ch_position_id)
                    ch_qty_list.append(buy_qty)
                    ch_price_list.append(buy_price)
    
                    # continue to next sell order child
                # end of for j in range(len(sell_chx_df)):    
    
                ch_sum_qty = round(sum(ch_qty_list), _q_)                                                  
                if sum(ch_price_list) > 0:
                    ch_mean_price = round(statistics.mean(ch_price_list), _p_)  
                else:
                    ch_mean_price = 0    
    
                sub_qty_list.append(ch_sum_qty)
                sub_price_list.append(ch_mean_price)                             
    
                #####################################################################################
                ### 【6】UPDATE SELL ORDER SUB (buy_order_id, buy_time)
                #####################################################################################          
    
                buy_order_ids_by_semicolon = ''    # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'
                for k, buy_order_id_x in enumerate(ch_buy_order_id_list):
                    if k == 0:
                        buy_order_ids_by_semicolon += buy_order_id_x
                    else:
                        buy_order_ids_by_semicolon += ';' + buy_order_id_x    
    
                ### update sell order sub (buy_order_id, buy_time) 
                find_mask = (sell_sub_df['sell_time'] == sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
                dfx = sell_sub_df[find_mask]
                # not found sell order sub ?
                if dfx.shape[0] == 0:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}(): {Bcolors.FAIL} sell order sub not found ▶ {sub_sell_order_id=} {Bcolors.ENDC} ")
                else: # found sell order sub => update buy_order_id, buy_time 
                    sell_sub_df.loc[find_mask, 'buy_order_id'] = buy_order_ids_by_semicolon # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'  
                    sell_sub_df.loc[find_mask, 'buy_time'] = buy_time
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {buy_order_ids_by_semicolon=}, {buy_time=:%Y-%m-%d %H:%M:%S}) ")  
                # end of if dfx.shape[0] == 0:  
    
                # continue to next sell order sub           
            # end of for i in range(len(sell_subx_df)):          
    
            #################################################################################
            ### 【7】SAVE SELL ORDER SUB TO CSV FILE
            #################################################################################
    
            ### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv     
            csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
            sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                                     
              
            ################################################################################
            ### 【8】ADD A BUY ORDER HEADER
            ################################################################################            
    
            ### update buy order (real_qty, real_price, real_fee) ★
            sub_sum_qty = round(sum(sub_qty_list), _q_)                                       
            
            if sum(sub_price_list) > 0:
                sub_mean_price = round(statistics.mean(sub_price_list), _p_)  
            else:
                sub_mean_price = 0    
    
            # write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
            self.csv.write_buy_order(buy_time, sell_time, sub_sum_qty, sub_mean_price)  
    
            ################################################################################
            ### 【9】UPDATE A SELL ORDER HEADER (buy_time)
            ################################################################################          
            # update_sell_order(sell_time: utcnow, col_name_list: list, col_value_list: list) -> object:  # PK(sell_time)
            self.csv.update_sell_order(sell_time, col_name_list=['buy_time'], col_value_list=[buy_time])    
    
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order() ▶ {status=} normal return... ")
    
            return status           

    click image to zoom!
    図5-1
    図5-1はデモプログラムの実行結果です。 ここでは関数「auto_trade()」からSellBuyクラスのsell_buy(althorithm_id=0)メソッドを呼び出しています。 sell_buy()メソッドの引数に「althorithm_id=0」を指定しているの「アルゴリズム0」が適用されて空売り・買戻しの取引を行います。

    買戻しのタイミングになるとSellBuyクラスのcreate_buy_order()メソッドを呼び出して指値の買戻しの注文(売りポジションの決済)を入れます。 つまり、前出のcreate_sell_order()メソッドで注文した売りポジションを決済しています。 画面に表示されているログから分かるように内部的には、 Apiクラスのexe_buy_close_order_limit()メソッドを呼び出して指値の買戻しの注文を入れています。


    click image to zoom!
    図5-2
    図5-2はcreate_buy_order()メソッドで作成した買戻し注文のCSVファイルをExcelで開いた画像です。 「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qty, real_price,...」などが格納されています。 「qty, price」は指値で買戻し注文を入れたときの数量と価格です。 「real_qty, real_price」は注文が約定したときの数量、価格(レート)です。

    「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, qty, price, real_qty, real_price, sell_order_id,..」 などが格納されています。 「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。 買戻しの注文はすでに約定しているので「real_qty, real_price」に約定した数量と金額(レート)が格納されています。 このファイルには対応する空売りの注文ID(sell_order_id)も格納されます。

    「buy_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, postition_id, real_qty, real_price,...」などが格納されます。 「real_qty, real_price」には約定したときの数量と価格(レート)が格納されます。 買戻しの注文はすでに約定しているので「real_qty, real_price」に約定した数量と金額(レート)が格納されています。


  6. SellBuyクラスにupdate_order(), update_closed_order(), update_partially_closed_order()メソッドを追加する

    ここではSellBuyクラスにupdate_order()、update_partially_closed_order()メソッドを追加します。 update_order()は親メソッドで、update_partially_closed_order()は子メソッドです。 update_order()からは、 update_closed_order(), update_partially_closed_order(), update_order_profit(), stop_loss(), cancel_pending_order(), close_pending_order()などのメソッドも呼びます。

    update_partially_closed_order()では、 空売りと買戻しの取引が完了した注文のCSVファイルを更新して保存します。 具体的には約定したときの数量(執行数量)、金額(レート)等を更新します。

    sell_buy.py:
        ###############################
        def update_order(self) -> None:
            """
            
            """
            ########################################################################
            ### 【1】UPDATE CLOSED SELL ORDER 
            ########################################################################
            ### update closed sell order
            self.update_closed_order()   
    
            ########################################################################
            ### 【2】UPDATE ORDER PROFIT (PART) : first_pass(True)
            ########################################################################
            ### update order profit 
            self.update_order_profit(first_pass=True)           
    
            ########################################################################
            ### 【3】CHECK STOP LOSS : gvar.stoploss_method_id : 0, 1,...
            ########################################################################
            ### check latest price(GMO) and stop loss price 
            if self.gvar.stop_loss:    
                self.stop_loss(self.gvar.stoploss_method_id)    # 0, 1,...  
           
            ########################################################################
            ### 【4】CANCEL PENDING ORDERS OR CLOSE PENDING ORDERS 
            ########################################################################
            ### cancel pending orders      
            # cancel pending order ? => call cancel_pending_orders() method       
            if self.gvar.cancel_pending_orders:         
                self.cancel_pending_order()            
            elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)  
                self.close_pending_order()                        
    
            ########################################################################
            ### 【5】 UPDATE ORDER PROFIT (PART2) : first_pass(False)
            ########################################################################
            ### update the order file ( order(BTC).csv )
            if self.gvar.stop_loss or self.gvar.cancel_pending_orders or self.gvar.close_pending_orders:
                self.update_order_profit(first_pass=False)  
    
            # if self.gvar.stop_loss:
            #     self.update_order_profit(first_pass=False)  
            # elif self.coin.cancel_pending_orders: # cancel pending order by coin (one time request)            
            #     self.update_order_profit(first_pass=False)    
            # elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)      
            #     self.update_order_profit(first_pass=False)
    
            return
            
        ###################################### 
        def update_closed_order(self) -> None:
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal  
            self.gvar.callers_method_name = 'update_closed_order():'
            order_completed = self.trade.get_order_status(exclude=False)   # include stop_loss, cancelled, closed order info        
    
            df = self.coin.master2_df      
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
            self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), cancel({self.gvar.cancel_pending_orders}) ")       
    
            # mas_df is empty ? => SYTEM ERROR
            if mas_df.shape[0] == 0: 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
                self.debug.sound_alert(3)                  
                return 
          
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)       
    
            ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            # get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
            sell_bk, buy_bk = self.api.get_orderbooks_try()
            sleep(0.5)
            sell_bk_df = pd.DataFrame(sell_bk)  # ascending order of price      : low to high price 100, 110, 120, 130, 140, 150
            buy_bk_df = pd.DataFrame(buy_bk)    # descending order of price     : high to low price 150, 140, 130, 120, 110, 100
            
            # not empty order books ?
            if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:              
                ### get sell_close_order    
                # get latest sell/buy price from the order books
                bk_sell_price = sell_bk_df.iloc[0]['price']     # get lowest sell price 100
                bk_buy_price = buy_bk_df.iloc[0]['price']       # get highest buy price 150
            else:
                bk_sell_price = 0.0
                bk_buy_price = 0.0            
    
            # incomplet buy order (pending buy order) ?
            if not order_completed:
                ### print GMO orderbooks info (sell price, buy price)
    
                # # get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
                # sell_bk, buy_bk = self.api.get_orderbooks_try()
                # sleep(0.5)
                # sell_bk_df = pd.DataFrame(sell_bk)  # ascending order of price
                # buy_bk_df = pd.DataFrame(buy_bk)    # descending order of price
                
                # not empty order books ?
                if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:              
                    ### get sell_close_order    
    
                    # get latest sell/buy price from the order books
                    bk_sell_price = sell_bk_df.iloc[0]['price']     # get lowest sell price 100
                    bk_buy_price = buy_bk_df.iloc[0]['price']       # get highest buy price 150
    
                    # get the last buy price from the buy order sub
                    last_buy_price = buy_sub_df.iloc[-1]['price']   # 120
    
                    # get buy position number (sell => buy)
                    buy_bk_df.sort_values('price', ascending=True, inplace=True)    # sort in ascending order of price ★   
                    filter_mask = buy_bk_df['price'] > last_buy_price
                    dfx = buy_bk_df[filter_mask]
                    buy_position = dfx.shape[0]
    
                    # update buy position count
                    if buy_position >= 91:
                        self.coin.position_count_91_999 += 1
                    elif buy_position >= 71:
                        self.coin.position_count_71_90 += 1
                    elif buy_position >= 51:
                        self.coin.position_count_51_70 += 1
                    elif buy_position >= 31:
                        self.coin.position_count_31_50 += 1
                    elif buy_position >= 11:
                        self.coin.position_count_11_30 += 1
                    else:
                        self.coin.position_count_0_10 += 1               
    
                    self.coin.buy_position_list.append(buy_position)  # append buy_position : coin.buy_position=[10, 8, 5, 3, 1, 0]
                    self.debug.trace_write(f".... {self.gvar.callers_method_name}{Bcolors.WARNING} {buy_position=}{Bcolors.ENDC} => close condition [{last_buy_price=:,.{_p_}f} >= {bk_sell_price=:,.{_p_}f}] ")    # 120 >= 100                 
                  
                    reverse_buy_position_list = self.coin.buy_position_list[::-1]   # [0, 1, 3, 5, 8, 10]
                    if len(reverse_buy_position_list) < 21:
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list=} ")   
                    else:
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list[0:21]=} ")    
                else: # empty order books
                    self.coin.buy_position_list = []   # reset buy position list
            # end of if not order_completed:
                 
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER 
            #################################################
         
            ### get all buy order header
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']   # unique time                  
                sell_time      = buy_df.loc[i, 'sell_time']  # unique time                        
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True        
                     
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        dfx.reset_index(inplace=True)                
                        buy_order_id = str(dfx.loc[0, 'buy_order_id'])    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")                        
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    continue    
                # end of if buy_order_closed:    
    
                ### open or incomplete buy order header
    
                ##########################################################################################
                ### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
                ##########################################################################################
    
                ### for buy order sub : use pandas dataframe groupby()
        
                buy_order_sub_seq = 0
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    ### get buy order sub info
                    sub_buy_time       = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time      = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id   = buy_subx_df.loc[j, 'buy_order_id']       # B9999-999999-999999 ★ 
                    sub_sell_order_id  = buy_subx_df.loc[j, 'sell_order_id']      # S9999-999999-999999 ★ 
                    sub_position_id    = buy_subx_df.loc[j, 'position_id']        # (unique id)
                    sub_buy_qty        = buy_subx_df.loc[j, 'qty']               
                    sub_buy_price      = buy_subx_df.loc[j, 'price']            
                    sub_buy_real_qty   = buy_subx_df.loc[j, 'real_qty']          
                    sub_buy_real_price = buy_subx_df.loc[j, 'real_price']            
                    sub_buy_real_fee   = buy_subx_df.loc[j, 'real_fee']  
                    sub_buy_stop_loss  = buy_subx_df.loc[j, 'stop_loss'] 
                    sub_buy_cancelled  = buy_subx_df.loc[j, 'cancelled']        
    
                    sub_buy_order_open = False
                    sub_buy_order_closed = False
                    sub_buy_order_incomplete = False
    
                    if sub_buy_real_qty == 0:
                        sub_buy_order_open = True
                    elif sub_buy_qty == sub_buy_real_qty:
                        sub_buy_order_closed = True
                    else:
                        sub_buy_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP
                    if sub_buy_order_id.startswith('Fake'): 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ") 
                        continue             
                        
                    # cancelled buy order sub ? => SKIP
                    if sub_buy_cancelled:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")  
                        continue
    
                    # stop loss buy order sub ? => SKIP
                    if sub_buy_stop_loss:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")  
                        continue            
    
                    # closed buy order sub ? => SKIP
                    if sub_buy_order_closed:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")  
                        continue 
    
                    ### open or incomplete buy order sub
    
                    ############################################################
                    ### 【3】UPDATE BUY ORDER SUB (get_price_count+1)
                    ############################################################
    
                    ### update buy order sub (get_price_count += 1)  ★         
                    # DO NOT USE: self.csv.update_buy_order_sub_count(buy_time, buy_order_id, symbol)        
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub_count({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub       
                        buy_sub_df.loc[find_mask, 'get_price_count'] += 1   # increment get_price_count by 1
                        ix = dfx.index[0]
                        sub_buy_get_price_count = buy_sub_df.loc[ix, 'get_price_count']
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub_count({sub_buy_order_id=}, {sub_buy_get_price_count=}) ")   
                    # end of if dfx.shape[0] == 0:                
    
                    buy_order_sub_seq = j + 1 # 1, 2, 3,....
    
                    ### get order_status
                    qty = sub_buy_qty           
                    price = sub_buy_price       
    
                    # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)
                    status, order_status = self.api.get_order_status(sub_buy_order_id, qty, price)  # ★ qty, price are used for fake mode only  
                    # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
    
                    ### status = 0; order_status = 'EXPIRED'    # DEBUG DEBUG DEBUG
    
                    # get_order_status() error ?
                    if status != 0:   
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")                               
                        continue    # continue to next buy order sub                       
                   
                    ## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
    
                    # pending exe_buy_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
                    if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':                      
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")                         
                        continue    # continue to next buy order sub                  
    
                    ############################################# DEBUG DEBUG DEBUG
                    if self.coin.debug_mode_gmo_stop_loss:
                        order_status = 'EXPIRED'        
                    ############################################# DEBUG DEBUG DEBUG
            
                    ### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'                    
    
                    if order_status in 'EXPIRED,CANCELED':                      
                        ### update buy order, buy order sub and order csv file
                        if order_status == 'CANCELED':
                             self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")   
                             self.debug.sound_alert(3)
                             continue    # continue to next buy order sub 
    
                        if order_status == 'EXPIRED':     
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")   
                            self.debug.sound_alert(3)
    
                            ###########################################    
                            ### 1: close buy order sub
                            ###########################################
    
                            # update a buy order sub (update real_qty, real_price, stop_loss)                                              
                            # DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list) 
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # not found buy order sub ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")                           
                            else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss) 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_stop_loss = True
                                buy_sub_df.loc[find_mask, 'real_qty']   = sub_buy_qty  
                                buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_sub_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee
                                buy_sub_df.loc[find_mask, 'stop_loss'] = buy_stop_loss          # buy order stop loss
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")   
                            # end of if dfx.shape[0] == 0:  
    
                            #######################################
                            ### 2: close buy order    
                            #######################################
    
                            # update buy order  (real_qty, real_price, real_fee) 
                            find_mask = buy_df['buy_time'] == buy_time
                            dfx = buy_df[find_mask]
                            # not found buy order ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                            else: # found buy order 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_df.loc[find_mask, 'real_qty']   = buy_qty                 
                                buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee 
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}) ")                                                    
                            # end of if dfx.shape[0] == 0:                                                          
    
                            ###########################################
                            ### 3: update order csv (update stop_loss)    
                            ###########################################   
                                    
                            ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
                            ord_df = self.csv.get_order_csv()                 
                            if ord_df.shape[0] > 0:
                                # find the order csv by sell_order_id        
                                find_mask = ord_df['sell_order_id'] == sub_sell_order_id
                                dfx = ord_df[find_mask]
                                if dfx.shape[0] > 0:      
                                    # update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
                                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])                            
    
                            continue    # continue to next buy order sub                  
                        # end of if order_status == 'EXPIRED':         
                    # end of if order_status in 'EXPIRED,CANCELED':   
    
                    ### get lowest sell price from sell orderbooks
                    latest_book_price = bk_sell_price    # bk_sell_price = sell_bk_df.iloc[0]['price']   
    
                    # ### get real qty, sell price, fee from the GMO         
                    # ### get the latest close price from the GMO master                                     
                    # ### mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO) => usef for fake mode                             
    
                    _debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2 
    
                    ######################################################################################### DEBUG(1), DEBUG(2)  
                    # _debug1_ = True; _debug2_ = True    # ★ TEMP TEMP TEMP ★   
                    # self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")                                       
                    ######################################################################################### DEBUG(1), DEBUG(2)  
    
                    ### get buy real qty, price & fee from the GMO : get_price() for exe_buy_close_order_limit() ★                            
                    
                    sleep(1)    # sleep 1 sec
                    # get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple: 
                    status, res_df, msg_code, msg_str = self.api.get_price_try(sub_buy_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_)  # ★ qty, price, close_price are used for fake mode           
                    # BUG status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_)  # ★ qty, price, close_price are used for fake mode                                         
                    # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)                
                    sleep(1)    # sleep 1 sec     
    
                    res_df = pd.DataFrame(res_df)   # cast dataframe
    
                    # not real mode and debug (stop loss or cancel pending orders) ?
                    ######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug1: 
                            if res_df.shape[0] > 0:
                                # ★ create two sell order subs for each sell order header ★
                                # update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
                                res_df = self.trade.update_get_price_response_for_debug1_2(res_df, buy_order_sub_seq)  # 1, 2, 3, ...
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_order_sub_seq=}) ▶ {res_df.shape=} ")  
                    ######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()        
    
    
                    ####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug2: 
                            # ★ create outstanding buy order sub depending on get_price_count ★
                            # get buy 'get_price_count'
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # found buy order sub ?
                            if dfx.shape[0] > 0:
                                ix = dfx.index[0]                                    
                                get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_sub_df.loc[find_mask, 'get_price_count'].values[0]   # 0,1,2,3,4,5,...
                                # update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
                                res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)                                
                                if res_df.shape[0] == 0:
                                    status = 9                          # set internal status code
                                    msg_code = 'ERR-999'                # null => ERR-999
                                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty  
                                else:
                                    status = 0
                                    msg_code = 'OK'
                                    msg_str = 'Normal Return'                                                            
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")                                                 
                    ###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()              
    
                    # get_price() error ?
                    if status != 0:     
                        if status == 9: # pending exe_buy_close_order_limit() request ?                     
                            # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ") 
                            status = 0  # reset status code
                            continue    # continue to next buy order sub                  
                        else: # status == 1 'ERR-5008' Timestamp for this request is too late
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")                               
                            continue    # continue to next buy order sub 
                        # end of if status == 9: 
                    else:   # normal return => executed exe_buy_close_order_limit() request                
                        pass    # self.debug.sound_alert(1) 
                    # end of if status != 0: 
    
                    ### no error : res_df.shape[0] >= 1 (single or multiple rows)  
      
                    ####################################################################
                    ### 【4】UPDATE A BUY ORDER SUB (real_qty, real_price, real_fee) 
                    ####################################################################
    
                    # res_df = get_price() response for exe_buy_close_order_limit()         
                    # ----------------------------------------------------------------  
                    #  #   Column       Dtype              
                    # ----------------------------------------------------------------              
                    #  0   executionId  object             
                    #  1   fee          float64            
                    #  2   lossGain     float64            
                    #  3   orderId      object  (non unique id => same id)
                    #  4   positionId   object  (unique id)                     
                    #  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]
                    # ----------------------------------------------------------------                  
    
                    ### update a buy order sub ★             
                    sub_sum_real_qty  = round(res_df.groupby('orderId')['size'].sum().values[0], _q_)       # round(999.12, _q_)
                    sub_sum_real_fee  = round(res_df.groupby('orderId')['fee'].sum().values[0], 0)          # zero(0) 
                    sub_mean_real_price = round(res_df.groupby('orderId')['price'].mean().values[0], _p_)   # round(999999.123, _p_)                           
    
                    # DO NOT USED: self.csv.update_buy_order_sub(buy_time, sub_order_id, col_name_list, col_value_list) 
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee) 
                        buy_sub_df.loc[find_mask, 'real_qty']   = sub_sum_real_qty  
                        buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price 
                        buy_sub_df.loc[find_mask, 'real_fee']   = sub_sum_real_fee 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, real_qty={sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={sub_sum_real_fee:,.0f}) ")   
                    # end of if dfx.shape[0] == 0:  
    
                    ##############################################################################################################################
                    ### 【5】ADD A BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
                    ##############################################################################################################################
    
                    ### check exit condition : sub_buy_qty=sub_buy_qty, sub_sum_real_qty=sum(res_df['size'])          
    
                    ########################################### TEMP TEMP TEMP
                    # temp_buy_qty = round(sub_buy_qty, _q_)
                    # temp_real_qty = round(sub_sum_real_qty, _q_)
                    # self.debug.trace_warn(f".... DEBUG: {temp_buy_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
                    ########################################## TEMP TEMP TEMP                  
    
                    # completed exe_buy_close_order_limit() request ?
                    if round(sub_buy_qty, _q_) == round(sub_sum_real_qty, _q_):                                                
                        # changed open => closed  
                        if self.coin.real_mode:                                    
                            self.debug.sound_alert(1)
    
                        # increment buy count
                        self.gvar.buy_count += 1  
                        self.coin.buy_count += 1        
           
                        # response df for get_price(): exe_buy_close_order_limit()
                        for k in range(len(res_df)):
                            buy_order_id = res_df.loc[k, 'orderId']      # buy order id (unique id)
                            position_id  = res_df.loc[k, 'positionId']   # position id (non unique id => same id)
                            real_qty     = res_df.loc[k, 'size']         # real qty
                            real_price   = res_df.loc[k, 'price']        # real price
                            real_fee     = res_df.loc[k, 'fee']          # real fee         
    
                            if not self.coin.real_mode:
                                position_id = sub_position_id                        
            
                            ### write a buy order child ★
                            # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:              
                            _qty_ = real_qty; _price_ = real_price
                            self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)    
                        # end of for k in range(len(res_df)):                                                                                                                                                                                                              
                    else:   # outstanding buy_qty exists => incomplete status  
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} incomplete buy order sub({sub_buy_order_id=}) ▶ buy_qty={sub_buy_qty:,.{_q_}f}, buy_real_qty={sub_sum_real_qty:,.{_q_}f} ")    
                        # pass    # skip (do nothing)    
                    # end of if round(sub_buy_qty, digits) == round(sub_sum_real_qty, digits): 
    
                    # continue to next buy order sub
                # end of for j in range(len(buy_subx_df)):
    
                #################################################################################
                ### 【6】SAVE BUY/SELL ORDERS SUB TO CSV FILES
                #################################################################################
    
                ### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/buy_order_sub(BTC_JPY).csv  
                buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode   
                                        
                ### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
                sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode              
    
                #################################################################################
                ### 【7】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
                #################################################################################     
    
                ### calculate total buy qty / fee and mean price ★ buy_time ★
                filter_mask = buy_sub_df['buy_time'] == buy_time
                dfx = buy_sub_df[filter_mask]    
                # not found buy order sub ?
                if dfx.shape[0] == 0:
                    self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order sub({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                else: # found buy order sub                         
                    header_sum_real_qty = dfx.groupby('buy_time')['real_qty'].sum().values[0]    
                    header_sum_real_fee = dfx.groupby('buy_time')['real_fee'].sum().values[0] 
                    header_mean_real_price = dfx.groupby('buy_time')['real_price'].mean().values[0]                      
    
                    header_sum_real_qty = round(header_sum_real_qty, _q_)       # round(990.12, _q_)     
                    header_sum_real_fee = round(header_sum_real_fee, 0)         # zero(0)
                    header_mean_real_price = round(header_mean_real_price, _p_) # round(999999.123, _p_)          
    
                    ### update buy order (real_qty, real_price, real_fee) ★
                    find_mask = buy_df['buy_time'] == buy_time
                    dfx = buy_df[find_mask]
                    # not found buy order ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order 
                        buy_df.loc[find_mask, 'real_qty']   = header_sum_real_qty                  
                        buy_df.loc[find_mask, 'real_price'] = header_mean_real_price 
                        buy_df.loc[find_mask, 'real_fee']   = header_sum_real_fee 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={header_sum_real_fee:,.0f}) ")                                                    
                    # end of if dfx.shape[0] == 0:              
                # end of if dfx.shape[0] == 0:    
    
                # continue to next buy order... ★
            # end of for i in range(len(buy_df)):
    
            #################################################################################
            ### 【8】SAVE BUY/SELL ORDER HEADER TO CSV FILES 
            #################################################################################        
    
            ### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv         
            csv_file = self.folder_trading_type + f'buy_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/buy_order(BTC_JPY).csv  
            buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode  
    
            ### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv      
            csv_file = self.folder_trading_type + f'sell_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order(BTC_JPY).csv   
            sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode        
                    
            return
    
        ################################################ 
        def update_partially_closed_order(self) -> None:
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal  
            self.gvar.callers_method_name = ':: update_partially_closed_order():'
            order_completed = self.trade.get_order_status(exclude=False)   # include stop_loss, cancelled, closed order info        
    
            df = self.coin.master2_df      
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
            self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), cancel({self.gvar.cancel_pending_orders}) ")       
    
            # mas_df is empty ? => SYTEM ERROR
            if mas_df.shape[0] == 0: 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
                self.debug.sound_alert(3)                  
                return 
          
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)       
    
            # ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            # sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            # ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            # sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            # get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
            sell_bk, buy_bk = self.api.get_orderbooks_try()
            sleep(0.5)
            sell_bk_df = pd.DataFrame(sell_bk)  # ascending order of price      : low to high price 100, 110, 120, 130, 140, 150
            buy_bk_df = pd.DataFrame(buy_bk)    # descending order of price     : high to low price 150, 140, 130, 120, 110, 100
            
            # not empty order books ?
            if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:              
                ### get sell_close_order    
                # get latest sell/buy price from the order books
                bk_sell_price = sell_bk_df.iloc[0]['price']     # get lowest sell price 100
                bk_buy_price = buy_bk_df.iloc[0]['price']       # get highest buy price 150
            else:
                bk_sell_price = 0.0
                bk_buy_price = 0.0            
                 
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER 
            #################################################
         
            ### get all buy order header
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']   # unique time                  
                sell_time      = buy_df.loc[i, 'sell_time']  # unique time                        
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True        
                     
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        dfx.reset_index(inplace=True)                
                        buy_order_id = str(dfx.loc[0, 'buy_order_id'])    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")                        
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    continue    
                # end of if buy_order_closed:    
    
                ### open or incomplete buy order header
    
                ##########################################################################################
                ### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
                ##########################################################################################
    
                ### for buy order sub : use pandas dataframe groupby()
        
                buy_order_sub_seq = 0
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    buy_order_sub_seq = j + 1 # 1, 2, 3,....
    
                    ### get buy order sub info
                    sub_buy_time       = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time      = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id   = buy_subx_df.loc[j, 'buy_order_id']       # B9999-999999-999999 ★ 
                    sub_sell_order_id  = buy_subx_df.loc[j, 'sell_order_id']      # S9999-999999-999999 ★ 
                    sub_position_id    = buy_subx_df.loc[j, 'position_id']        # (unique id)
                    sub_buy_qty        = buy_subx_df.loc[j, 'qty']               
                    sub_buy_price      = buy_subx_df.loc[j, 'price']            
                    sub_buy_real_qty   = buy_subx_df.loc[j, 'real_qty']          
                    sub_buy_real_price = buy_subx_df.loc[j, 'real_price']            
                    sub_buy_real_fee   = buy_subx_df.loc[j, 'real_fee']  
                    sub_buy_stop_loss  = buy_subx_df.loc[j, 'stop_loss'] 
                    sub_buy_cancelled  = buy_subx_df.loc[j, 'cancelled']        
    
                    sub_buy_order_open = False
                    sub_buy_order_closed = False
                    sub_buy_order_incomplete = False
    
                    if sub_buy_real_qty == 0:
                        sub_buy_order_open = True
                    elif sub_buy_qty == sub_buy_real_qty:
                        sub_buy_order_closed = True
                    else:
                        sub_buy_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP
                    if sub_buy_order_id.startswith('Fake'): 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ") 
                        continue             
                        
                    # cancelled buy order sub ? => SKIP
                    if sub_buy_cancelled:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")  
                        continue
    
                    # stop loss buy order sub ? => SKIP
                    if sub_buy_stop_loss:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")  
                        continue            
    
                    # closed buy order sub ? => SKIP
                    if sub_buy_order_closed:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")  
                        continue 
    
                    ### open or incomplete buy order sub
    
                    ############################################################
                    ### 【3】GET ORDER STATUS
                    ############################################################
                    
                    ### get order_status
                    qty = sub_buy_qty           
                    price = sub_buy_price       
    
                    # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)
                    status, order_status = self.api.get_order_status(sub_buy_order_id, qty, price)  # ★ qty, price are used for fake mode only  
                    # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
    
                    ### status = 0; order_status = 'EXPIRED'    # DEBUG DEBUG DEBUG
    
                    # get_order_status() error ?
                    if status != 0:   
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")                               
                        continue    # continue to next buy order sub                       
                   
                    ## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
    
                    # pending exe_buy_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
                    if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':                      
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")                         
                        continue    # continue to next buy order sub                  
            
                    ############################################# DEBUG DEBUG DEBUG
                    if self.coin.debug_mode_gmo_stop_loss:
                        order_status = 'EXPIRED'        
                    ############################################# DEBUG DEBUG DEBUG
    
                    ### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'                    
    
                    if order_status in 'EXPIRED,CANCELED':                      
                        ### update buy order, buy order sub and order csv file
                        if order_status == 'CANCELED':
                             self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")   
                             self.debug.sound_alert(3)
                             continue    # continue to next buy order sub 
    
                        if order_status == 'EXPIRED':     
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")   
                            self.debug.sound_alert(3)
                          
                            ###########################################    
                            ### 1: close buy order sub
                            ###########################################                        
    
                            # update a buy order sub (update real_qty, real_price, stop_loss)                                              
                            # DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list) 
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # not found buy order sub ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")   
                            else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss) 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_stop_loss = True
                                buy_sub_df.loc[find_mask, 'real_qty']   = sub_buy_qty  
                                buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_sub_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee
                                buy_sub_df.loc[find_mask, 'stop_loss']  = buy_stop_loss         # buy order stop loss
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")   
                            # end of if dfx.shape[0] == 0:  
    
                            #######################################
                            ### 2: close buy order    
                            #######################################
    
                            # update buy order  (real_qty, real_price, real_fee) 
                            find_mask = buy_df['buy_time'] == buy_time
                            dfx = buy_df[find_mask]
                            # not found buy order ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                            else: # found buy order 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_df.loc[find_mask, 'real_qty']   = buy_qty                 
                                buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee 
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}) ")                                                    
                             # end of if dfx.shape[0] == 0:                                                      
                            
                            ###########################################
                            ### 3: update order csv (update stop_loss)    
                            ###########################################   
                                    
                            ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
                            ord_df = self.csv.get_order_csv()                 
                            if ord_df.shape[0] > 0:
                                # find the order csv by sell_order_id        
                                find_mask = ord_df['sell_order_id'] == sub_sell_order_id
                                dfx = ord_df[find_mask]
                                if dfx.shape[0] > 0:      
                                    # update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
                                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])                            
    
                            continue    # continue to next buy order sub                  
                        # end of if order_status == 'EXPIRED':         
                    # end of if order_status in 'EXPIRED,CANCELED':   
    
                    ### get lowest sell price from sell orderbooks
                    latest_book_price = bk_sell_price    # bk_sell_price = sell_bk_df.iloc[0]['price']   
    
                    ##########################################################################################
                    ### 【4】GET ORDER PRICE
                    ##########################################################################################                
    
                    # ### get real qty, sell price, fee from the GMO         
                    # ### get the latest close price from the GMO master                                     
                    # ### mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO) => usef for fake mode                             
    
                    _debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2 
    
                    ######################################################################################### DEBUG(1), DEBUG(2)  
                    # _debug1_ = True; _debug2_ = True    # ★ TEMP TEMP TEMP ★   
                    # self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")                                       
                    ######################################################################################### DEBUG(1), DEBUG(2)  
    
                    ### get buy real qty, price & fee from the GMO : get_price() for exe_buy_close_order_limit() ★                            
                    
                    sleep(1)    # sleep 1 sec
                    # get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple: 
                    status, res_df, msg_code, msg_str = self.api.get_price_try(sub_buy_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_)  # ★ qty, price, close_price are used for fake mode                  
                    # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)                
                    sleep(1)    # sleep 1 sec     
    
                    res_df = pd.DataFrame(res_df)   # cast dataframe
    
                    # not real mode and debug (stop loss or cancel pending orders) ?
                    ######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug1: 
                            if res_df.shape[0] > 0:
                                # ★ create two sell order subs for each sell order header ★
                                # update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
                                res_df = self.trade.update_get_price_response_for_debug1_2(res_df, buy_order_sub_seq)  # 1, 2, 3, ...
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_order_sub_seq=}) ▶ {res_df.shape=} ")  
                    ######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()        
    
    
                    ####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug2: 
                            # ★ create outstanding buy order sub depending on get_price_count ★
                            # get buy 'get_price_count'
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # found buy order sub ?
                            if dfx.shape[0] > 0:
                                ix = dfx.index[0]                                    
                                get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_sub_df.loc[find_mask, 'get_price_count'].values[0]   # 0,1,2,3,4,5,...
                                # update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
                                res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)                                
                                if res_df.shape[0] == 0:
                                    status = 9                          # set internal status code
                                    msg_code = 'ERR-999'                # null => ERR-999
                                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty  
                                else:
                                    status = 0
                                    msg_code = 'OK'
                                    msg_str = 'Normal Return'                                                            
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")                                                 
                    ###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()              
    
                    # get_price() error ?
                    if status != 0:     
                        if status == 9: # pending exe_buy_close_order_limit() request ?                     
                            # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ") 
                            status = 0  # reset status code
                            continue    # continue to next buy order sub                  
                        else: # status == 1 'ERR-5008' Timestamp for this request is too late
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")                               
                            continue    # continue to next buy order sub 
                        # end of if status == 9: 
                    else:   # normal return => executed exe_buy_close_order_limit() request                
                        pass    # self.debug.sound_alert(1) 
                    # end of if status != 0: 
    
                    ### no error : res_df.shape[0] >= 1 (single or multiple rows)     
    
                    ##############################################################################################################################
                    ### 【5】ADD A BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
                    ##############################################################################################################################
    
                    # response df for get_price(): exe_buy_close_order_limit()
                    for k in range(len(res_df)):
                        buy_order_id = res_df.loc[k, 'orderId']      # buy order id (unique id)
                        position_id  = res_df.loc[k, 'positionId']   # position id (non unique id => same id)
                        real_qty     = res_df.loc[k, 'size']         # real qty
                        real_price   = res_df.loc[k, 'price']        # real price
                        real_fee     = res_df.loc[k, 'fee']          # real fee         
    
                        if not self.coin.real_mode:
                            position_id = sub_position_id                        
        
                        ### write a buy order child ★
                        # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:              
                        _qty_ = real_qty; _price_ = real_price
                        self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)    
                    # end of for k in range(len(res_df)):     
    
                    # continue to next buy order sub
                # end of for j in range(len(buy_subx_df)):
       
                # continue to next buy order... ★
            # end of for i in range(len(buy_df)):
                        
            return  

    click image to zoom!
    図6-1
    図6-1はデモプログラムの実行結果です。 この画面は指値の買戻し注文が約定したときの画面です。 Apiクラスのget_order_status()の戻り値が「EXECUTED」になっているので約定しています。 Apiクラスのget_price()では約定した買戻し注文の数量、金額(レート)等を取得しています。


    click image to zoom!
    図6-2
    図6-2は空売り・買戻し注文のときに作成したCSVファイルをExcelで開いた画面です。 「sell_order_(XRP_JPY).csv」ファイルには「sell_time, qty, price, real_qty, real_price,..」などが格納されています。 「real_qty, real_price」は約定した数量と金額(レート)です。

    「sell_order_sub(XRP_JPY).csv」ファイルには「sell_order_id, position_id, real_qty, real_price,...」などが格納されています。 「position_id」は買戻しの注文を出すときに使います。

    「sell_order_child(XRP_JPY).csv」ファイルには「sell_order_id, position_id, real_qty, real_price,...」などが格納されています。 「buy_order_child(XRP_JPY).csv」ファイルには「buy_order_id, position_id, qty, price, real_qty, real_price,...」などが格納されています。 ここでは数量「40」で買戻したの注文を出したのに数量が「20, 10, 10」に分割されて約定しています。


    click image to zoom!
    図6-3
    図6-3は買戻し注文で作成されたCSVファイルをExcelで開いた画像です。 「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qty, real_price, buy_time,...」などが格納されています。 「real_qty, real_price」は約定した数量と金額(レート)です。

    「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, qty, price, real_qty, real_price, sell_order_id,...」などが格納されています。

    「buy_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, real_qty, real_price, sell_order_id,..」などが格納されています。 ここでは数量「40」で買戻しの注文を出したのに数量が「20, 10, 10」に分割されて約定しています。


  7. SellBuyクラスにupdate_order_profit()メソッドを追加する

    ここではSellBuyクラスにupdate_order_profit()メソッドを追加します。 このメソッドは取引の損益を計算してCSVファイルとExcelファイルに保存します。

    sell_buy.py:
        ####################################################### 
        def update_order_profit(self, first_pass=True) -> None:
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            self.gvar.callers_method_name = 'update_order_profit():'
            order_completed = self.trade.get_order_status(exclude=False)   # include stop_loss, cancelled, closed order info
            self.debug.trace_write(f".... {self.gvar.callers_method_name} first_pass({first_pass}), last order completed({order_completed})  ")   
    
            ###############################################################################################################################################################################
            ### 【1】IMPORT SELL ORDER SUB INFO & APPEND/UPDATE ORDER CSV FILE (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee) & (buy_time, buy_order_id)
            ###############################################################################################################################################################################
    
            ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
            ord_df = self.csv.get_order_csv()               
            
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
            sell_sub_df = self.csv.get_sell_order_sub()         
    
            ### import buy order sub file       
    
            # get all sell order sub
            for i in range(len(sell_sub_df)):
                ### get sell sub order info
                sub_sell_time            = sell_sub_df.loc[i, 'sell_time']           # ★ (non unique)
                sub_buy_time             = sell_sub_df.loc[i, 'buy_time']            # ★ (non unique)
                sub_sell_order_id        = sell_sub_df.loc[i, 'sell_order_id']       # ★ 'S9999-999999-999999' (unique)
                sub_buy_order_id         = sell_sub_df.loc[i, 'buy_order_id']        # ★ 'B9999-999999-999999;B9999-999999-999999;B9999-999999-999999' (list of buy_order_ids) : sell sub(one) <= buy sub(many)
                sub_sell_qty             = sell_sub_df.loc[i, 'qty']
                sub_sell_price           = sell_sub_df.loc[i, 'price']
                sub_sell_real_qty        = sell_sub_df.loc[i, 'real_qty']                
                sub_sell_real_price      = sell_sub_df.loc[i, 'real_price']
                sub_sell_real_fee        = sell_sub_df.loc[i, 'real_fee']
                sub_sell_get_price_count = sell_sub_df.loc[i, 'get_price_count']    
                sub_sell_stop_loss       = sell_sub_df.loc[i, 'stop_loss']
                sub_sell_order_cancelled = sell_sub_df.loc[i, 'cancelled']
                sub_sell_order_closed = True if sub_sell_qty == sub_sell_real_qty else False                  
           
                # fake order sub ? => SKIP
                if sub_sell_order_id.startswith('Fake'): 
                    # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ") 
                    continue             
                    
                # empty order csv ?
                if ord_df.shape[0] == 0:
                    dfx = pd.DataFrame()
                else:   # not empty dataframe => find the order csv by sell_order_id
                    find_mask = ord_df['sell_order_id'] == sub_sell_order_id  # S9999-999999-999999
                    dfx = ord_df[find_mask]
                
                # not found order csv ?
                if dfx.shape[0] == 0:                 
                    ### append order csv ★IMPORTANT★ DO NOT APEND 'Null' Value => buy_order_id = 'B9999-999999-999999'
                    columns = Gmo_dataframe.ORDER_CSV 
                    _buy_real_qty_=0.0; _buy_real_price_=0.0; _buy_real_fee_=0.0; _real_profit_=0.0 
                    _accumulated_leverage_fee_=0.0; _close_count_=0; _close_update_date_=dt.datetime.now()
                    _order_closed_ = False; _stop_loss_=sub_sell_stop_loss; _cancelled_=sub_sell_order_cancelled
                    _order_open_date_ = dt.datetime.now(); _order_close_date_ = dt.datetime.now(); _log_time_=dt.datetime.now()
                    data = [[sub_buy_time, sub_sell_time, sub_buy_order_id, sub_sell_order_id, _buy_real_qty_, sub_sell_real_qty, _buy_real_price_, sub_sell_real_price, _buy_real_fee_, sub_sell_real_fee, _real_profit_, _accumulated_leverage_fee_, _close_count_, _close_update_date_, _order_closed_, _stop_loss_, _cancelled_, _order_open_date_, _order_close_date_, _log_time_]] 
                    new_df = pd.DataFrame(data, columns=columns)                           
    
                    # convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
                    new_df['buy_time']  = pd.to_datetime(new_df['buy_time'], utc=True)          # object => utc time (aware)
                    new_df['sell_time'] = pd.to_datetime(new_df['sell_time'], utc=True)         # object => utc time (aware)   
                    new_df['close_update_date']  = pd.to_datetime(new_df['close_update_date'])  # object => ns time (jp time)   
                    new_df['order_open_date']    = pd.to_datetime(new_df['order_open_date'])    # object => ns time (jp time) 
                    new_df['order_close_date']   = pd.to_datetime(new_df['order_close_date'])   # object => ns time (jp time)                    
                    new_df['log_time']  = pd.to_datetime(new_df['log_time'])                    # object => ns time (jp time)    
    
                    new_df['buy_order_id']  = new_df['buy_order_id'].astype(str)                # int64 => string
                    new_df['sell_order_id'] = new_df['sell_order_id'].astype(str)               # int64 => string  
    
                    # append a new row (order info row)
                    ord_sell_order_id = sub_sell_order_id
                    ord_sell_real_qty = sub_sell_real_qty
                    ord_sell_real_price = sub_sell_real_price
                    ord_sell_real_fee = sub_sell_real_fee
                    ord_order_closed = False                
                    ord_df = ord_df.append(new_df, ignore_index=True)                                      
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} write_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee:,.0f}, {ord_order_closed=}) ")                              
                else: # found order csv => update order csv
                    # get buy_real_qty and sell_real_qty
                    ix = dfx.index[0]   # get index of the first row
                    # ord_buy_order_id  = ord_df.loc[ix, 'buy_order_id']
                    # ord_buy_real_qty  = ord_df.loc[ix, 'buy_real_qty']
                    # ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']               
                    # ord_order_closed = ord_df.loc[ix, 'order_closed'] 
    
                    ord_buy_order_id    = ord_df.loc[ix, 'buy_order_id']
                    ord_sell_order_id   = ord_df.loc[ix, 'sell_order_id']
                    ord_buy_real_qty    = ord_df.loc[ix, 'buy_real_qty']
                    ord_sell_real_qty   = ord_df.loc[ix, 'sell_real_qty']      
                    ord_buy_real_price  = ord_df.loc[ix, 'buy_real_price']
                    ord_sell_real_price = ord_df.loc[ix, 'sell_real_price']      
                    ord_buy_real_fee    = ord_df.loc[ix, 'buy_real_fee']
                    ord_sell_real_fee   = ord_df.loc[ix, 'sell_real_fee']                          
                    ord_order_closed   = ord_df.loc[ix, 'order_closed']                    
                                                    
                    # completed order csv file ?
                    if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):
                       if not ord_order_closed:
                            # update order csv file 
                            ord_order_closed = True
                            ord_df.loc[find_mask, 'order_closed']     = ord_order_closed      
                            ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now() 
                            ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()    
    
                            ord_df['order_close_date']   = pd.to_datetime(ord_df['order_close_date'])  # object => ns time (jp time)                         
                            ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])    # object => ns time (jp time)     
                            self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee=:,.0f}, {ord_order_closed=}) ")                                          
                    else: # incomplete order csv => update order csv                                        
                        # update order csv 
                        ord_sell_time = sub_sell_time
                        ord_sell_order_id = sub_sell_order_id
                        ord_sell_real_qty = sub_sell_real_qty
                        ord_sell_real_price = sub_sell_real_price
                        ord_sell_real_fee = sub_sell_real_fee
    
                        ord_df.loc[find_mask, 'sell_time']       = ord_sell_time      
                        ord_df.loc[find_mask, 'sell_order_id']   = ord_sell_order_id
                        ord_df.loc[find_mask, 'sell_real_qty']   = ord_sell_real_qty                
                        ord_df.loc[find_mask, 'sell_real_price'] = ord_sell_real_price
                        ord_df.loc[find_mask, 'sell_real_fee']   = ord_sell_real_fee 
                        ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
    
                        ### update order_closed flag
                        
                        # get buy_real_qty and sell_real_qty
                        ix = dfx.index[0]   # get index of the first row
                        # ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
                        # ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
                        # ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']               
                        # order_closed = ord_df.loc[ix, 'order_closed']          
                        if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):       
                            ord_order_closed = True
                            ord_df.loc[find_mask, 'order_closed'] = ord_order_closed                         
    
                        # convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
                        ord_df['buy_time']  = pd.to_datetime(ord_df['buy_time'], utc=True)      # object => utc time (aware)
                        ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True)     # object => utc time (aware)    
                        ord_df['close_update_date']  = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)   
                        ord_df['order_open_date']    = pd.to_datetime(ord_df['order_open_date'])   # object => ns time (jp time) 
                        ord_df['order_close_date']   = pd.to_datetime(ord_df['order_close_date'])  # object => ns time (jp time)                       
                        ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])                # object => ns time (jp time)    
    
                        ord_df['buy_order_id']  = ord_df['buy_order_id'].astype(str)            # int64 => string
                        ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str)           # int64 => string                   
        
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee=:,.0f}, {ord_order_closed=}) ")                              
                    # end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:                             
                # end of if dfx.shape[0] == 0:
                # continue to next sell order sub
            # end of for i in range(len(sell_sub_df)):	           
    		
            #######################################################################################################################################
            ### 【2】IMPORT BUY ORDER SUB INFO & UPDATE ORDER CSV FILE (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee)
            #######################################################################################################################################
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
            buy_sub_df = self.csv.get_buy_order_sub()	
    
            ### import buy order sub file      
    
            for i in range(len(buy_sub_df)):
                ### get buy order sub info
                sub_buy_time             = buy_sub_df.loc[i, 'buy_time']         # ★ (non unique)
                sub_sell_time            = buy_sub_df.loc[i, 'sell_time']        # ★ (non unique)
                sub_buy_order_id         = buy_sub_df.loc[i, 'buy_order_id']     # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
                sub_sell_order_id        = buy_sub_df.loc[i, 'sell_order_id']    # ★ 'S9999-999999-999999' (unique id)
                sub_buy_qty              = buy_sub_df.loc[i, 'qty']
                sub_buy_price            = buy_sub_df.loc[i, 'price']
                sub_buy_real_qty         = buy_sub_df.loc[i, 'real_qty']
                sub_buy_real_price       = buy_sub_df.loc[i, 'real_price']
                sub_buy_real_fee         = buy_sub_df.loc[i, 'real_fee']
                sub_sell_get_price_count = buy_sub_df.loc[i, 'get_price_count']     
                sub_buy_stop_loss        = buy_sub_df.loc[i, 'stop_loss']         
                sub_buy_order_cancelled  = buy_sub_df.loc[i, 'cancelled']
    
                sub_buy_order_open = False
                sub_buy_order_closed = False
                sub_buy_order_incomplete = False
    
                if sub_buy_real_qty == 0:
                    sub_buy_order_open = True
                elif sub_buy_qty == sub_buy_real_qty:
                    sub_buy_order_closed = True
                else:
                    sub_buy_order_incomplete = True       
    
                # fake buy order sub ? => SKIP
                if sub_buy_order_id.startswith('Fake'): 
                    # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ") 
                    continue             
                                             
                # empty order csv ?           
                if ord_df.shape[0] == 0:
                    self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - order csv is empty: continue... ")
                    self.debug.sound_alert(3)  
                    continue
    
                # find the order csv by sell_order_id        
                find_mask = ord_df['sell_order_id'] == sub_sell_order_id
                dfx = ord_df[find_mask]
                # not found order csv ?
                if dfx.shape[0] == 0:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} order csv not found: sell_order_id={sub_sell_order_id} ")  
                    self.debug.sound_alert(3)  
                    continue                
    
                ### found order csv file
                
                # get buy_real_qty and sell_real_qty
                ix = dfx.index[0]   # get index of the first row
                ord_buy_order_id    = ord_df.loc[ix, 'buy_order_id']
                ord_sell_order_id   = ord_df.loc[ix, 'sell_order_id']
                ord_buy_real_qty    = ord_df.loc[ix, 'buy_real_qty']
                ord_sell_real_qty   = ord_df.loc[ix, 'sell_real_qty']      
                ord_buy_real_price  = ord_df.loc[ix, 'buy_real_price']
                ord_sell_real_price = ord_df.loc[ix, 'sell_real_price']      
                ord_buy_real_fee    = ord_df.loc[ix, 'buy_real_fee']
                ord_sell_real_fee   = ord_df.loc[ix, 'sell_real_fee']                          
                ord_order_closed    = ord_df.loc[ix, 'order_closed']                   
                        
                # completed order csv ?
                if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_): 
                    if not ord_order_closed:
                        # update order csv 
                        ord_order_closed = True 
                        ord_df.loc[find_mask, 'order_closed'] = ord_order_closed        
                        ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now() 
                        ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()    
    
                        ord_df['order_close_date']   = pd.to_datetime(ord_df['order_close_date'])  # object => ns time (jp time)                         
                        ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])    # object => ns time (jp time)          
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")        
                else: # incomplete order csv => update order csv
                    ### get a list of buy_order_id from the buy sub orders ★ sell_order_id ★
                    filter_mask = buy_sub_df['sell_order_id'] == sub_sell_order_id
                    dfx = buy_sub_df[filter_mask]    
                    buy_order_id_list = dfx.groupby('sell_order_id')['buy_order_id'].apply(list)[0]     
                    buy_order_ids_by_semicolon = ''
                    for k, order_id in enumerate(buy_order_id_list):
                        if k == 0:
                            buy_order_ids_by_semicolon += order_id 
                        else:
                            buy_order_ids_by_semicolon += ';' + order_id 
    
                    ### calculate total sell qty / fee and mean price ★ sell_order_id ★
                    buy_sum_real_qty = dfx.groupby('sell_order_id')['real_qty'].sum().values[0]    
                    buy_sum_real_fee = dfx.groupby('sell_order_id')['real_fee'].sum().values[0] 
                    buy_mean_real_price = dfx.groupby('sell_order_id')['real_price'].mean().values[0]                           
    
                    ### update buy csv (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee) 
                    ord_buy_real_qty = round(buy_sum_real_qty, _q_)                           # round(999.12, _q_)  
                    ord_buy_real_price = round(buy_mean_real_price, _p_)                      # round(999999.123, _p_)  
                    ord_buy_real_fee = round(buy_sum_real_fee, 0)                             # zero(0)  
    
                    # order completed ?
                    if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_): 
                        ord_order_closed = True   
                        ord_df.loc[find_mask, 'order_closed'] = ord_order_closed  
    
                    ord_df.loc[find_mask, 'buy_time']       = sub_buy_time    
                    ord_df.loc[find_mask, 'buy_order_id']   = buy_order_ids_by_semicolon        # 'OrderId1;OrderId2;OrderId3'                       
                    ord_df.loc[find_mask, 'buy_real_qty']   = ord_buy_real_qty                  # round(buy_sum_real_qty, _q_)               
                    ord_df.loc[find_mask, 'buy_real_price'] = ord_buy_real_price                # round(buy_mean_real_price, _p_)     
                    ord_df.loc[find_mask, 'buy_real_fee']   = ord_buy_real_fee                  # round(buy_sum_real_fee, 0)           
    
                    # convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
                    ord_df['buy_time']  = pd.to_datetime(ord_df['buy_time'], utc=True)          # object => utc time (aware)
                    ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True)         # object => utc time (aware)   
                    ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date'])   # object => ns time (jp time)  
                    ord_df['order_open_date']   = pd.to_datetime(ord_df['order_open_date'])     # object => ns time (jp time) 
                    ord_df['order_close_date']  = pd.to_datetime(ord_df['order_close_date'])    # object => ns time (jp time)                         
                    ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])                    # object => ns time (jp time)    
    
                    ord_df['buy_order_id']  = ord_df['buy_order_id'].astype(str)                # int64 => string
                    ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str)               # int64 => string               
    
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")                              
    
                # end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:    
                # continue to next buy order sub
            # end of for i in range(len(buy_sub_df)):
    
            #######################################################################################################################################
            ### 【3】SAVE ORDER CSV TO CSV/EXCEL FILES
            #######################################################################################################################################
    
            ### CLOSE (save to the order csv file) : order(BTC_JPY).csv
            if ord_df.shape[0] > 0:
                ### CLOSE (order csv file)
    
                csv_file = self.folder_trading_type + f'order({symbol}).csv'  
                # desktop-pc/bot/sell_buy/order(BTC_JPY).csv   
                ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file  
         
                ### SAVE (order csv excel file)
    
                # save to the order excel file
                excel_file = self.folder_trading_type + f'order({symbol}).xlsx'
                # desktop-pc/bot/sell_buy/order(BTC_JPY).xlsx   
    
                excel_df = ord_df.copy()
    
                # (1) convert time to string              
                excel_df['buy_time']  = excel_df['buy_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))  
                excel_df['sell_time'] = excel_df['sell_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))      
                excel_df['close_update_date']  = excel_df['close_update_date'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))     
                excel_df['log_time']  = excel_df['log_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))  
    
                # (2) drop real_profit columns
                excel_df = excel_df.drop(['real_profit'], axis=1)            
    
                # (3) add new columns
                excel_df['buy(SUM)']    = '=ROUND(E2*G2,3)'                     # add a new column
                excel_df['sell(SUM)']   = '=ROUND(F2*H2,3)'                     # add a new column
                excel_df['profit(AMT)'] = '=IF((H2-G2)>0,ROUNDDOWN((H2 - G2) * F2, 0),ROUNDUP((H2 - G2) * F2, 0))'  # add a new column : rounddown(profit), roundup(loss)
                excel_df['profit(%)']   = '=M2/K2'                              # add a new column
    
                # (4) update excel formula
    
                # get all excel order info  
                row = 2
                for i in range(len(excel_df)):
                    order_closed = excel_df.loc[i, 'order_closed']              # True or False
                    stop_loss = excel_df.loc[i, 'stop_loss']                    # True or False
                    cancelled = excel_df.loc[i, 'cancelled']                    # True or False                
                    if order_closed or stop_loss or cancelled:                
                        cell = str(row)
                        excel_df.loc[i, 'buy(SUM)']     = f'=ROUND(E{cell}*G{cell},3)'                     
                        excel_df.loc[i, 'sell(SUM)']    = f'=ROUND(F{cell}*H{cell},3)'                     
                        excel_df.loc[i, 'profit(AMT)']  = f'=IF((H{cell}-G{cell})>0,ROUNDDOWN((H{cell} - G{cell}) * F{cell}, 0),ROUNDUP((H{cell} - G{cell}) * F{cell}, 0))'  
                        excel_df.loc[i, 'profit(%)']    = f'=M{cell}/K{cell}'                   
                    else:
                        excel_df.loc[i, 'buy(SUM)']     = ''                  
                        excel_df.loc[i, 'sell(SUM)']    = ''                    
                        excel_df.loc[i, 'profit(AMT)']  = '' 
                        excel_df.loc[i, 'profit(%)']    = ''                     
    
                    row += 1
                # end of for j in range(len(excel_df)):   
    
    
                # (5) re-order columns
                columns = [
                    'buy_time','sell_time','buy_order_id','sell_order_id','buy_real_qty','sell_real_qty','buy_real_price','sell_real_price','buy_real_fee','sell_real_fee',
                    'buy(SUM)','sell(SUM)','profit(AMT)','profit(%)',
                    'accumulated_leverage_fee','close_count','close_update_date',
                    'order_closed','stop_loss','cancelled','order_open_date','order_close_date','log_time'
                    ]    
                       
                excel_df = excel_df[columns]
                 
                # print('excel_df.info()\n', excel_df.info())
    
                # ------------------------------------------------------------------------------------------
                #  #   Column                Dtype
                # ------------------------------------------------------------------------------------------
                #  1   time(B)               datetime64[utc]    => Convert to string
                #  2   time(S)               datetime64[utc]    => Convert to string
                #  3   order(B)              object  
                #  4   order(S)              object    
                #  5   qty(B)                float64    
                #  6   qty(S)                float64                
                #  7   price(B)              float64
                #  8   price(S)              float64
                #  9   fee(B)                float64
                # 10   fee(S)                float64
                #      real_profit           float64             => REMOVE
                # 11   buy(SUM)              float64             => ADD =ROUNDUP(E2*G2,3)+I2
                # 12   sell(SUM)             float64             => ADD =ROUNDDOWN(F2*H2,3)+J2
                # 13   profit(AMT)           float64             => ADD =L2-K2             
                # 14   profit(%)             float64             => ADD =M2/K2  
                # 15   fee(L)                float64                       
                # 16   count(L)              int                 
                # 17   close(L)              datetime64[ns]            
                # 18   order_closed          bool                 
                # 19   stop                  bool
                # 20   cancel                bool
                # 21   open_date             datetime64[ns]      
                # 22   close_date            datetime64[ns]      
                # 23   log_time              datetime64[ns]      => Convert to string
                # ----------------------------------------------------------------------------------------                             
    
                # (5) rename column names & export to excel file order_by(BTC_JPY).xlsx
                excel_df.columns = Gmo_dataframe.ORDER_XLSX
                excel_df.to_excel(excel_file, sheet_name='GMO Orders', index=False) 
            # end of if ord_df.shape[0] > 0:
    
            return          

    click image to zoom!
    図7-1
    図7-1はデモプログラムの実行結果です。 このデモプログラムでは仮想通貨「XRP_JPY, LTC_JPY, BCH_JPY」に対して10回自動トレードを繰り返しています。 注文数は「10XRP_JPY, 1LTC_JPY, 0.1BCH_JPY」で利益率は「0.0035, 0.0015, 0.0015」を設定しています。 注文数は最小値で利益率も小さいので金額は無視して約定の回数に注目してください。

    画像にはSellBuyクラスのupdate_order_profit()メソッドが実行されたことがログに表示されています。 このメソッドでは仮想通貨ごとの損益を計算してExcelのファイルに保存します。


    click image to zoom!
    図7-2
    図7-2はupdate_order_profit()メソッドが作成した損益ファイルをExcelで開いています。 このファイルには「time(B/S), order(B/S), qty(B/S), price(B/S), profit(AMT), profit(%), close, stop, cancel,..」などが格納されています。 「()」のBはBuy, SはSellを意味します。 「profit(AMT)」には損益額、「profit(%)」には損益をパーセント(%)で表示します。

    「closed」が「TRUE」のときは取引が約定していることを意味します。 「stop, cancel」が「TRUE」のときは取引がストップ・ロス、キャンセルされたことを意味します。 「BCH_JPY」が2回、「LTC_JPY」と「XRP_JPY」が1回約定しています。 BOTのログには仮想通貨ごとの約定回数と損益が集計されてリアルタイムで表示されます。


  8. SellBuyクラスにstop_loss(), stop_loss_0()メソッドを追加する

    ここではSellBuyクラスにstop_loss()メソッドを追加します。 このメソッドは価格が暴騰したときに強制的にポジションを決済して損失が大きくなるのを回避します。 stop_loss()もBOTの肝となる部分です。 sell_buy()とstop_loss()の作り方によって利益がでるかどうかが決まります。 stop_loss()の引数には「method_id」を指定します。 デフォルトで「method_id=0」が用意されています。 stop_loss_0()メソッドはBOTをテストするときに使用します。 BOTのテストが完了したら本番用のストップ・ロスのメソッドを作って 「stop_loss_1(), stop_loss_2(),...」のようなメソッド名を追加します。

    sell_buy.py:
        ############################################ 
        def stop_loss(self, method_id: int) -> None:
            """
            
            """
    
            if method_id <= 1:
                eval(f'self.stop_loss_{str(method_id)}()')    # call 'self.stop_loss_?() method
            else:
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} -  skip stop_loss() ")
                self.debug.sound_alert(3)          
    
            # if method_id == 0:
            #     self.stop_loss_0()  # stop loss method 0
            # elif method_id == 1:
            #     self.stop_loss_1()  # stop loss method 1
            # else:   #
            #     self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} -  skip stop_loss() ")
            #     self.debug.sound_alert(3)  
    
            return
    
        ############################## 
        def stop_loss_0(self) -> None:
            """
            
            """
         
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            self.gvar.callers_method_name = 'stop_loss(0):'
            df = self.coin.master2_df
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
       
            self.debug.trace_write(f".... {self.gvar.callers_method_name} mas_df.shape({mas_df.shape[0]}), loss cut: 18%({self.coin.sell_buy_action14}), 15%({self.coin.sell_buy_action13}), 10%({self.coin.sell_buy_action12}) ")         
    
            # ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
            # ord_df = self.csv.get_order_csv()                             
         
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)  
           
            ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER 
            #################################################    
    
            # define buy order header list
            header_real_qty_list = []
            header_real_price_list = []
            header_real_fee_list = []        
    
            ### get all buy order   
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']              # ★ unique time  
                sell_time      = buy_df.loc[i, 'sell_time']             # ★ unique time  
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True  
    
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        ix = dfx.index[0]               
                        buy_order_id = dfx.loc[ix, 'buy_order_id']    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
                        else:
                            pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({buy_order_id=}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    # end of if dfx.shape[0] > 0:
                    continue    # skip closed order                                                            
                # end of if buy_order_closed:
                    
                #################################################################
                ### 【2】READ BUY ORDER SUB (open or incomplete status)
                #################################################################
                         
                ### for buy order sub (open or incomplete status)
    
                # define buy order sub list
                sub_real_qty_list = []
                sub_real_price_list = []
                sub_real_fee_list = []         
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order 
                for j in range(len(buy_subx_df)):  # ★ buy_subx_df ★   
                    ### get buy order sub info
                    sub_buy_time        = buy_subx_df.loc[j, 'buy_time']           # non unique time                
                    sub_sell_time       = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id    = buy_subx_df.loc[j, 'buy_order_id']       # B9999-999999-999999 ★ (non unique id) : buy sub(many) => sell sub(one)
                    sub_sell_order_id   = buy_subx_df.loc[j, 'sell_order_id']      # S9999-999999-999999 ★ (unique id)     
                    sub_position_id     = buy_subx_df.loc[j, 'position_id']        # ★ (unique id)
                    sub_qty             = buy_subx_df.loc[j, 'qty']                
                    sub_price           = buy_subx_df.loc[j, 'price']            
                    sub_real_qty        = buy_subx_df.loc[j, 'real_qty']               
                    sub_real_price      = buy_subx_df.loc[j, 'real_price']            
                    sub_real_fee        = buy_subx_df.loc[j, 'real_fee']         
                    sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']   
                    sub_stop_loss       = buy_subx_df.loc[j, 'stop_loss']
                    sub_cancelled       = buy_subx_df.loc[j, 'cancelled']  
    
                    sub_order_open = False
                    sub_order_closed = False
                    sub_order_incomplete = False
    
                    if sub_real_qty == 0:
                        sub_order_open = True
                    elif sub_qty == sub_real_qty:
                        sub_order_closed = True
                    else:
                        sub_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP
                    if sub_buy_order_id.startswith('Fake') :
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id=}) ")  
                        continue   
    
                    # buy order sub has been stop loss ? => SKIP
                    if sub_stop_loss: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss: {sub_stop_loss=} ")  
                        continue
    
                    # buy order sub has been cancelled ? => SKIP
                    if sub_cancelled: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")  
                        continue
    
                    # closed buy order sub ? => SKIP
                    if sub_order_closed:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed: {sub_order_closed=} ")  
                        continue  
    
                    ##########################################################################################
                    ### 【3】CHECK STOP LOSS FOR EACH BUY ORDER SUB (open or incomplte buy sub order)
                    ##########################################################################################
    
                    ### open or incomplete buy order sub             
                     
                    stop_loss_detected = False
    
                    while True:
    
                        #####################################################
                        ### Check stop loss debug mode
                        #####################################################
                        if self.coin.debug_mode_stop_loss:
                            stop_loss_detected = True
                            break # exit while true loop 
    
                        #####################################################
                        ### Unconditional stop loss
                        #####################################################                    
    
                        ### 1) check loss cut price : sell => buy 
    
                        ### calculate stop loss amount
    
                        stop_loss_amt = 0
                        latest_buy_price = 0
    
                        filter_mask = mas_df['side'] == 'BUY'
                        dfx = mas_df[filter_mask]        
                        if dfx.shape[0] > 0:
                            # get latest buy price from the GMO master dataframe
                            buy_price = dfx.iloc[-1]['price']
                            latest_buy_price = buy_price
    
                            # get last sell qty & price from the sell order header
                            qty = sell_df.iloc[-1]['qty']
                            sell_price = sell_df.iloc[-1]['real_price'] # get a real sell price
    
                            # calculate a stop loss amount : stop_loss_amt = math.floor((sell_price - buy_price) * qty)
                            stop_loss_amt = math.floor((sell_price - buy_price) * qty)  # positive => profit, negative => loss
                            stop_loss_amt = math.floor(stop_loss_amt) # positive => profit, negative => loss     
    
    
                        ### sell_buy : buy position => calculate upper limit for 5%, 10%, 15%, 18%, 20%, 23%, 25%(GMO loss cut rate)
    
                        # calculate loss cut price from the sell sub order  : loss_cut_price = last_sell_real_price + (last_sell_real_price * 0.20)                              
     
                        last_sell_real_price = sell_sub_df.iloc[-1]['real_price']
                        loss_cut_price_05 = last_sell_real_price + (last_sell_real_price * 0.05) # 0.25 => 0.05 
                        loss_cut_price_10 = last_sell_real_price + (last_sell_real_price * 0.10) # 0.25 => 0.10                    
                        loss_cut_price_15 = last_sell_real_price + (last_sell_real_price * 0.15) # 0.25 => 0.15
                        loss_cut_price_18 = last_sell_real_price + (last_sell_real_price * 0.18) # 0.25 => 0.18
                        loss_cut_price_20 = last_sell_real_price + (last_sell_real_price * 0.20) # 0.25 => 0.20 BOT LOSS CUT
                        loss_cut_price_23 = last_sell_real_price + (last_sell_real_price * 0.23) # 0.25 => 0.23 DO NOT USE THIS
                        loss_cut_price_25 = last_sell_real_price + (last_sell_real_price * 0.25) # 0.25 => 0.25 GMO LOSS CUT
            
                        # gmo master latest buy price is greater than zero ?
                        if latest_buy_price > 0:
                            if latest_buy_price >= loss_cut_price_25:   # >= 125
                                self.coin.sell_buy_lc25 += 1
                                # self.debug.trace_warn(f".... DEBUG: loss cut 25%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_25%({loss_cut_price_25:,.{_p_}f}) ")                                          
                            elif latest_buy_price >= loss_cut_price_23: # >= 120
                                self.coin.sell_buy_lc23 += 1         
                                # self.debug.trace_warn(f".... DEBUG: loss cut 23%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_23%({loss_cut_price_23:,.{_p_}f}) ")    
                            elif latest_buy_price >= loss_cut_price_20: # >= 115
                                self.coin.sell_buy_lc20 += 1   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 20%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_20%({loss_cut_price_20:,.{_p_}f}) ")  
                            elif latest_buy_price >= loss_cut_price_18: # >= 113
                                self.coin.sell_buy_lc18 += 1                                                   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 18%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_18%({loss_cut_price_18:,.{_p_}f}) ")                           
                            elif latest_buy_price >= loss_cut_price_15: # >= 112
                                self.coin.sell_buy_lc15 += 1   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 15%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_15%({loss_cut_price_15:,.{_p_}f}) ")  
                            elif latest_buy_price >= loss_cut_price_10: # >= 110
                                self.coin.sell_buy_lc10 += 1   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 10%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_10%({loss_cut_price_10:,.{_p_}f}) ")  
                            elif latest_buy_price >= loss_cut_price_05: # >= 105
                                self.coin.sell_buy_lc5 += 1               
                                # self.debug.trace_warn(f".... DEBUG: loss cut 5%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_5%({loss_cut_price_05:,.{_p_}f}) ")                                                                                                                                                               
    
                            ### check upper limit based on sell price
    
                            # GMO latest buy price is greater than or equal to loss cut price (20%) ? => EXECUTE STOP LOSS 
                            if latest_buy_price >= loss_cut_price_20:
                                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}STOP LOSS DETECTED REAL(1-5) EXECUTE STOP LOSS {Bcolors.ENDC} loss cut 20%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_20({loss_cut_price_20:,.{_p_}f})  ")                   
                                stop_loss_detected = True          
                                break # exit while true loop                             
    
                        # end of if latest_buy_price > 0:      
                                           
                        break # exit while true loop     
                    # end of while True:
    
                    if not stop_loss_detected: continue
    
                    #######################################################
                    # ★ STOP LOSS DETECTED ★ #                                        
                    #######################################################
        
                    # increment stop loss count
                    self.gvar.stop_loss_count += 1
                    self.coin.stop_loss_count += 1  
    
                    ######################################################
                    ### 【4】UPDATE PARTIALLY CLOSED ORDER
                    ######################################################                
    
                    if sub_order_incomplete:
                        ### add partially closed sell order child
                        self.update_partially_closed_order()
                        self.gvar.callers_method_name = 'stop_loss(0):'                    
                   
                    ######################################################
                    ### 【5】CANCEL CLOSED SELL ORDER SUB
                    ######################################################
    
                    ### cancel closed sell order   
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ") 
    
                    # increment buy cancel count
                    self.gvar.sell_cancel_count += 1   # cancel closed sell order            
                    self.coin.sell_cancel_count += 1   # cancel closed sell order
    
                    ### set a cancelled flag for sell order sub : PK(sell_time + sell_order_id) ★
                    find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
                    sell_sub_df.loc[find_mask, 'cancelled'] = True   
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, cancelled=True) ")  
    
                    ######################################################
                    ### 【6】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
                    ######################################################
    
                    ### for open or incomplete buy order sub     
    
                    # cancel open/incomplete buy sub order                                                    
                    status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id)    # ★  
    
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id}): exe_cancel_order() ▶ {status=} ") 
    
                    sleep(0.5)    # sleep 0.5 sec
    
                    ###########################################################################################
                    ### 【7】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET (FAK)
                    ###########################################################################################
    
                    ### rebuy open/incomplete buy order sub with market price (stop loss price)
    
                    outstanding_qty = sub_qty - sub_real_qty         
                    new_buy_qty = outstanding_qty                                   
                    qty = new_buy_qty
    
                    if sub_real_price > 0:
                        new_buy_price = sub_real_price + sub_real_fee                                                                                     
                    else:                    
                        new_buy_price = sub_price                                                   
                                     
                    ### get the latest close price from the GMO master
    
                    # get a column position of the GMO master                               
                    mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO)              
    
                    status = 0            
                    loop_count = 0 
    
                    while True:
                        loop_count += 1    
    
                        ### rebuy buy sub order outstanding qty
                        # exe_buy_close_order_market_wait( position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)
                        status, res_df, msg_code, msg_str = self.api.exe_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price)  # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ") 
            
                        # error ?
                        if status != 0:      
                            if status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while true loop and continue to read next buy sub orders     
                            else: # misc error
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while tue loop and continue to read next buy sub orders  
                            # end of if status == 8 and msg_code == 'ERR-888': 
                        # end of if status != 0:        
    
                        ### no error : res_df.shape[0] >= 1 (single or multiple rows)   
    
                        ######################################################################################################
                        ### 【8】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
                        ######################################################################################################
    
                        # res_df = get_price() response for exe_buy_close_order_market_wait()
                        # ----------------------------------------------------------------
                        #  #   Column       Dtype         
                        # ----------------------------------------------------------------         
                        #  0   executionId  int64         
                        #  1   fee          float64       
                        #  2   lossGain     float64                               
                        #  3   orderId      object(string)   
                        #  4   posiitonId   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]     
                        # ----------------------------------------------------------------                   
    
                        ### IMPORTANT ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
    
                        ### append a new record into the buy order child (★ this is the rebuy version of buy order ★)
    
                        # define buy order child list
                        order_id_list = []
                        position_id_list = []
                        real_qty_list = []
                        real_price_list = []
                        real_fee_list = []  
    
                        # response df for get_price for exe_buy_close_order_market() 
                        for k in range(len(res_df)):
                            order_id    = res_df.loc[k, 'orderId']          # orderId (same orderId): rebuy version of orderId
                            position_id = res_df.loc[k, 'positionId']       # positionId (same positionId)
                            real_qty    = res_df.loc[k, 'size']             # real qty 
                            real_price  = res_df.loc[k, 'price']            # real price
                            real_fee    = res_df.loc[k, 'fee']              # real fee        
    
                            order_id_list.append(order_id)                  # B9999-999999-999999
                            position_id_list.append(position_id)            # P9999-999999-999999
                            real_qty_list.append(real_qty)
                            real_price_list.append(real_price)
                            real_fee_list.append(real_fee)
    
                            ### write a buy order child (rebuy version) ★    
                            # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:                       
                            _qty_ = real_qty; _price_ = real_price
                            self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)    
                        # end of for k in range(len(res_df)):              
      
                        sum_real_qty = round(sum(real_qty_list), _q_)   # round(999.12, _q_)
                        sum_real_fee = round(sum(real_fee_list), 0)     # zero(0)          
    
                        if sum(real_price_list) > 0:
                            mean_real_price = round(statistics.mean(real_price_list), _p_) # round(999999.123, _p_)
                        else:
                            mean_real_price = 0                       
    
                        sub_real_qty_list.append(sum_real_qty)             
                        sub_real_price_list.append(mean_real_price)
                        sub_real_fee_list.append(sum_real_fee)
    
                        ### check exit condition:      
    
                        sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)     # round(999.12, _q_) 
                        new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty    #  
                   
                        # ####################################################### TEMP TEMP TEMP
                        # temp_qty = round(sub_qty, _q_)
                        # temp_real_qty = round(new_sub_sum_real_qty, _q_)
                        # self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
                        # ####################################################### TEMP TEMP TEMP  
    
                        if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):                                                
                            # increment buy cancel count
                            self.gvar.buy_cancel_count += 1  # cancel open/incomplete buy order            
                            self.coin.buy_cancel_count += 1  # cancel open/incomplete buy order
                            break   # exit while true loop 
                        else:       # outstanding buy_qty exists                    
                            pass    # skip (do nothing)                   
                        # end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
    
                        ### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
                            
                        remaining_qty = new_buy_qty - new_sub_sum_real_qty         
                        qty = remaining_qty
    
                        # continue exe_buy_close_order_market_wait() ★                                                             
                    # end of while True:                            
                      
                    #################################################################################
                    ### 【9】UPDATE BUY ORDER SUB (real_qty, real_price, real_fee, stop_loss)
                    #################################################################################
    
                    sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)   # round(999.12, _q_)                                
                    sub_sum_real_fee = round(sum(sub_real_fee_list), 0)     # zero(0)
    
                    if sum(sub_real_price_list) > 0:
                        sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_)  # round(999999.123, _p_)
                    else:
                        sub_mean_real_price = 0                   
    
                    header_real_qty_list.append(sub_sum_real_qty)
                    header_real_price_list.append(sub_mean_real_price)
                    header_real_fee_list.append(sub_sum_real_fee)
    
                    ### update the buy order sub (real_qty, real_price, real_fee, stop_loss) ★
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub => update
                        buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty      # add real_qty (rebuy version)                           
                        buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price  # update real_price (rebuy version)
                        buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee      # add real_fee (rebuy version)
                        buy_sub_df.loc[find_mask, 'stop_loss'] = True
                        ix = dfx.index[0]
                        new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']
                        new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']                    
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub(buy_order_id={sub_buy_order_id}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, stop_loss=True) ")                     
                    # end of if dfx.shape[0] == 0:  
    
                    #################################################################################
                    ### 【10】UPDATE ORDER CSV FILE (stop_loss, order_closed)
                    #################################################################################
    
                    ### update order info (stop_loss=True, order_closed=True) ★   
              
                    # update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])                                                                           
    
                    # reset buy order sub list values
                    sub_real_qty_list = []
                    sub_real_price_list = []
                    sub_real_fee_list = [] 
    
                    # continue to next buy order sub ... ★ 
                # end of for j in range(len(buy_sub_df)):
    
                #################################################################################
                ### 【11】SAVE BUY/SELL ORDER SUB TO CSV FILES
                #################################################################################
    
                ### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/buy_order_sub(BTC_JPY).csv  
                buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                
                            
                ### CLOSE (save the sell order sib csv file) : sell_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
                sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                                              
    
                #################################################################################
                ### 【12】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
                #################################################################################
    
                if len(header_real_qty_list) > 0:
                    ### update sell order (real_qty, real_price, real_fee) ★
                    header_sum_real_qty = round(sum(header_real_qty_list), _q_)     # round(999.12, _q_)        
                    header_sum_real_fee = round(sum(header_real_fee_list), 0)       # zero(0)            
                    
                    if sum(header_real_price_list) > 0:
                        header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)  # round(999999.123, _p_)
                    else:
                        header_mean_real_price = 0            
    
                    find_mask = buy_df['buy_time'] == buy_time
                    dfx = buy_df[find_mask]
                    # not found buy order ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order => update
                        buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty      # add real_qty (rebuy version)                          
                        buy_df.loc[find_mask, 'real_price'] = header_mean_real_price  # update real_price (rebuy version) 
                        buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee      # add real_fee (rebuy version)
                        ix = dfx.index[0]
                        new_header_sum_real_qty = buy_df.loc[ix, 'real_qty'] 
                        new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ") 
                    # end of if dfx.shape[0] == 0:                   
                # end of if len(header_real_qty_list) > 0:
    
                # reset list values
                header_real_qty_list = []
                header_real_price_list = []
                header_real_fee_list = []     
    
                # continue to next buy order 
            # end of for i in range(len(buy_df)):
    
            #################################################################################
            ### 【13】SAVE BUY/SELL ORDER HEADER TO CSV FILES 
            #################################################################################
    
            ### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv         
            csv_file = self.folder_trading_type + f'buy_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/buy_order(BTC_JPY).csv  
            buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode  
    
            ### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv      
            csv_file = self.folder_trading_type + f'sell_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order(BTC_JPY).csv   
            sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode     
    
            return      
      
        ############################## 
        def stop_loss_1(self) -> None:  
            pass    

    click image to zoom!
    図8-1
    図8-1はデモプログラムの実行結果です。 画面にはSellBuyクラスのstop_loss(0)メソッドのログが表示されています。 stop_loss(0)では未執行分の買戻し注文のキャンセルが実行されています。 さらに未執行の買いボジションに対して成行きの買い取り注文を出して強制的に決済しています。 詳細は図8-2で説明します。


    click image to zoom!
    図8-2
    図8-2は買い取り注文で作成されたCSVファイルをExcelで開いた画像です。 買い取り注文のCSVファイルも売り注文と同様3階層になっています。 ここでは3種類のCSVファイルを開いています。 「buy_order(XRP_JPY).csv」ファイルには「buy_time, qty, price, real_qry, real_price,...」などが格納されています。 「real_qty, real_price」には約定した数量と価格(レート)が格納されます。

    「buy_order_sub(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, real_qty, real_price, stop_loss,...」などが格納されています。 「real_qty」には約定した数量(40)が格納されています。 「stop_loss」には「TRUE」が格納されているのでストップ・ロスが適用されています。

    「buy_order_child(XRP_JPY).csv」ファイルには「buy_time, buy_order_id, position_id, real_qty, real_price,...」などが格納されています。 実はこの買い取り注文は数量「40」で出されていますが、数量が「20」だけ執行された状態でストップ・ロスが適用されています。 この場合、未執行の数量「20」に対してキャンセル処理を行います。 さらに、未執行の買いポジションに対して成行きの買い注文を出して残数の「20」を強制的に決済します。 つまり、ストップ・ロスを適用します。

    「buy_order_child(XRP_JPY).csv」のExcelシートの行2は取引所で部分的に執行された買い注文のデータで、 行3はストップ・ロスで強制的に決済したときの買い注文のデータです。 したがってシートの列(B)の「buy_order_id」には異なる買い取りの注文IDが格納されています。 余談ですが、リアルで取引している状態でこのような条件を検証するのは無理です。 ところが、BOTのシミュレーション機能を使えば簡単に今回のような処理の検証ができます。


    click image to zoom!
    図8-3
    図8-3は損益ファイルをExcelで開いた画像です。 「order(XRP_JPY).xlsx」ファイルには「time(B/S), order(B/S), qty(B/S), price(B/S), profit(AMT), profit(%), closed, stop, cancel,..」などの情報が格納されています。 「()」内のBはBuy, SはSellの意味です。 「stop」に「TRUE」が格納されているので、この取引にはストップ・ロスが適用されたことが分かります。 「qty」に執行された数量「40」が格納されていますが、これだと全数ストップ・ロスが適用されたのか、 部分的にストップ・ロスが適用されたのか分かりません。 このような場合は、図8-2の「buy_order_sub(XRP_JPY).csv」と「buy_order_child(XRP_JPY).csv」ファイルを参照します。


  9. SellBuyクラスにcancel_pending_order()メソッドを追加する

    ここではSellBuyクラスにcancel_pending_order()メソッドを追加します。 このメソッドは、保留状態になっているポジションを強制的に決済してレバレッジ手数料が加算されるのを回避するときに使用します。 GMOコインの場合、レバレッジ手数料は午前6時を経過したときに加算されます。 したがって、レバレッジ手数料を回避するには午前6時前にポジションを決済させる必要があります。

    sell_buy.py:
        ########################################   
        def cancel_pending_order(self) -> None:
            """        
    
            """   
    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            self.gvar.callers_method_name = 'cancel_pending_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} ") 
    
            df = self.coin.master2_df  
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
           
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)  
              
            ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER
            #################################################
    
            # define buy order header list
            header_real_qty_list = []
            header_real_price_list = []
            header_real_fee_list = []        
    
            ### get all buy order 
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']    # ★ unique time 
                sell_time      = buy_df.loc[i, 'sell_time']   # ★ unique time                                   
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True         
            
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        ix = dfx.index[0]                
                        buy_order_id = dfx.loc[ix, 'buy_order_id']    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    # end of if dfx.shape[0] > 0:
    
                    continue    
                # end of if buy_order_closed:  
    
                #################################################################
                ### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
                #################################################################
    
                ### for buy order sub (open or incomplete status)
    
                # define buy order sub list
                sub_real_qty_list = []
                sub_real_price_list = []
                sub_real_fee_list = [] 
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    ### get buy order sub info
                    sub_buy_time        = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time       = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id    = buy_subx_df.loc[j, 'buy_order_id']       # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
                    sub_sell_order_id   = buy_subx_df.loc[j, 'sell_order_id']      # ★ 'S9999-999999-999999' (unique id)           
                    sub_position_id     = buy_subx_df.loc[j, 'position_id']        # ★ (unique id)
                    sub_qty             = buy_subx_df.loc[j, 'qty']                                  
                    sub_price           = buy_subx_df.loc[j, 'price']               
                    sub_real_qty        = buy_subx_df.loc[j, 'real_qty']                        
                    sub_real_price      = buy_subx_df.loc[j, 'real_price']            
                    sub_real_fee        = buy_subx_df.loc[j, 'real_fee']                  
                    sub_stop_loss       = buy_subx_df.loc[j, 'stop_loss']
                    sub_cancelled       = buy_subx_df.loc[j, 'cancelled']                
            
                    sub_order_open = False
                    sub_order_closed = False
                    sub_order_incomplete = False
    
                    if sub_real_qty == 0:
                        sub_order_open = True
                    elif sub_qty == sub_real_qty:
                        sub_order_closed = True
                    else:
                        sub_order_incomplete = True                             
    
                    # fake buy sub order ? => SKIP    
                    if sub_buy_order_id.startswith('Fake') :
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id=}) ") 
                        continue               
    
                    # buy sub order has been stopped loss ? => SKIP
                    if sub_stop_loss: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")  
                        continue
    
                    # cancelled buy sub order ? => SKIP    
                    if sub_cancelled: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ") 
                        continue           
    
                    # closed buy sub order ? => SKIP    
                    if sub_order_closed: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ") 
                        continue  
    
                    ### open or incomplete buy order sub    
    
                    ######################################################
                    ### 【3】UPDATE PARTIALLY CLOSED ORDER
                    ######################################################                
    
                    if sub_order_incomplete:
                        ### add partially closed sell order child
                        self.update_partially_closed_order()
                        self.gvar.callers_method_name = 'cancel_pending_order():'                  
    
                    ##############################################################################################
                    ### 【4】CANCEL CLOSED SELL ORDER SUB : ★ buy order sub (many) => sell order sub (one) ★
                    ##############################################################################################
    
                    ### for closed sell order sub                              
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ") 
    
                    # increment sell cancel count
                    self.gvar.sell_cancel_count += 1   # cancel closed sell order             
                    self.coin.sell_cancel_count += 1   # cancel closed sell order 
    
                    ### set a cancelled flag for sell order sub : ★ PK(sell_time + sell_order_id) ★
                    find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
                    sell_sub_df.loc[find_mask, 'cancelled'] = True                
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, cancelled=True) ")                                                              
                                       
                    ######################################################
                    ### 【5】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
                    ######################################################
    
                    ### cancel open or incomplete buy order sub  
    
                    ### cancel buy order sub                    
                    status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id)    # ★        
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_cancel_order() ▶ {status=} ")   
                    sleep(0.5)  # sleep 0.5 sec                       
    
                    ##################################################################################################
                    ### 【6】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET WAIT (FAK)
                    ##################################################################################################
    
                    ### rebuy open or incomplete buy order sub with market price 
    
                    outstanding_qty = sub_qty - sub_real_qty    # calculate outstanding qty 
                    new_buy_qty = outstanding_qty                                       
                    qty = new_buy_qty
    
                    if sub_real_price > 0:
                        new_buy_price = sub_real_price + sub_real_fee                              
                    else:
                        new_buy_price = sub_price               
    
                    ### get the latest close price from the GMO master
    
                    # get a column position of the GMO master                              
                    mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO)                             
    
                    status = 0
                    loop_count = 0
    
                    while True:
                        loop_count += 1    
    
                        # exe_buy_close_order_market_wait(position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)
                        status, res_df, msg_code, msg_str = self.api.exe_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price)  # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC               
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ") 
            
                        # error ?
                        if status != 0:                                 
                            if status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while true loop and continue to read next buy sub orders 
                            else: # misc error
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) misc error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while true loop and continue to read next buy sub orders                    
                            # end of if status == 8 and msg_code == 'ERR-888': 
                        # end of if status != 0:  
         
                        ### no error : res_df.shape[0] >= 1 (single or multiple rows)
    
                        ##################################################################################################
                        ### 【7】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
                        ##################################################################################################
    
                        # res_df = response for exe_buy_close_order_market_wait() 
                        # -----------------------------------------------------------
                        #  #   Column       Dtype         
                        # -----------------------------------------------------------         
                        #  0   executionId  int64         
                        #  1   fee          float64       
                        #  2   lossGain     float64                               
                        #  3   orderId      object(string)  (non unique)  
                        #  4   positionId   object(string)  (non unique)     
                        #  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]  
                        # ----------------------------------------------------------
    
                        ### IMPORTANT ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
                        
                        ### append a new record into the buy order child  (★ this is the rebuy version of buy order child ★)
    
                        # define buy order child list
                        order_id_list = []
                        position_id_list = []
                        real_qty_list = []
                        real_price_list = []
                        real_fee_list = []   
    
                        # response for exe_buy_close_order_market_wait()
                        for k in range(len(res_df)):
                            order_id    = res_df.loc[k, 'orderId']          # orderId (same orderId) : rebuy version of orderId         
                            position_id = res_df.loc[k, 'positionId']       # positionId (same positionId)               
                            real_qty    = res_df.loc[k, 'size']             # real qty     
                            real_price  = res_df.loc[k, 'price']            # real price
                            real_fee    = res_df.loc[k, 'fee']              # real fee
    
                            order_id_list.append(order_id)                  # B9999-999999-999999
                            position_id_list.append(position_id)            # P9999-999999-999999
                            real_qty_list.append(real_qty)                  # 
                            real_price_list.append(real_price)
                            real_fee_list.append(real_fee)
    
                            ### write a buy order child (rebuy version) ★    
                            # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:                       
                            _qty_ = real_qty; _price_ = real_price
                            self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)                             
                        # end of for k in range(len(res_df)):                      
    
                        sum_real_qty = round(sum(real_qty_list), _q_)                                                          
                        sum_real_fee = round(sum(real_fee_list), 0)                             
    
                        if sum(real_price_list) > 0:
                            mean_real_price = round(statistics.mean(real_price_list), _p_)        
                        else:
                            mean_real_price = 0    
    
                        sub_real_qty_list.append(sum_real_qty)                                  
                        sub_real_price_list.append(mean_real_price)
                        sub_real_fee_list.append(sum_real_fee)
                                          
                        ### check exit condition:      
                         
                        sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)                       
                        new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty          
                 
                        # no outstanding buy_qty ?
                        if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):            
                            # increment buy cancel count
                            self.gvar.buy_cancel_count += 1  # cancel open/incomplete buy order            
                            self.coin.buy_cancel_count += 1  # cancel open/incomplete buy order
                            break   # exit while true loop
                        else:       # outstanding buy_qty exists
                            pass    # skip (do nothing)                 
                        # end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
    
                        ### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
                            
                        remaining_qty = new_buy_qty - new_sub_sum_real_qty        
                        qty = remaining_qty
    
                        # continue exe_buy_close_order_market_wait() ★
                    # end of while True:   
                     
                    #################################################################################
                    ### 【8】UPDATE BUY ORDER SUB (real_qty, real_price, real_fee, cancelled)
                    #################################################################################                                  
        
                    sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)                                                   
                    sub_sum_real_fee = round(sum(sub_real_fee_list), 0)          
    
                    if sum(sub_real_price_list) > 0:
                        sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_) 
                    else:
                        sub_mean_real_price = 0                      
    
                    header_real_qty_list.append(sub_sum_real_qty)                            
                    header_real_price_list.append(sub_mean_real_price)
                    header_real_fee_list.append(sub_sum_real_fee)
    
                    ### update the buy order sub (real_qty, real_price, real_fee, cancelled) 
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL}buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub => update 
                        buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty      # add real_qty (rebuy version)                                  
                        buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price  # update real_price (rebuy version)
                        buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee      # add real_fee (rebuy version)
                        buy_sub_df.loc[find_mask, 'cancelled'] = True
                        ix = dfx.index[0]
                        new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']          
                        new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, cancelled=True) ")                                    
                    # end of if dfx.shape[0] == 0:   
                        
                    #################################################################################
                    ### 【9】UPDATE ORDER CSV FILE (cancelled, order_closed)
                    #################################################################################                        
                        
                    ### update order csv (cancelled=True, order_closed=True)  ★  
    
                    # update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['cancelled'], col_value_list=[True]) # PK(sell_time + sell_order_id)                                                                                                                    
    
                    # reset buy order sub list values
                    sub_real_qty_list = []
                    sub_real_price_list = []
                    sub_real_fee_list = [] 
    
                    # continue to next buy order sub
                # end of for j in range(len(buy_subx_df)):     
    
                #################################################################################
                ### 【10】SAVE BUY/SELL ORDERS SUB TO CSV FILES
                #################################################################################
    
                ### CLOSE (save the buy order sub csv file)  
                csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/buy_order_sub(BTC_JPY).csv  
                buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode   
                                         
                ### CLOSE (save the sell order sub csv file) 
                csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
                sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                                             
    
                #################################################################################
                ### 【11】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
                #################################################################################
                    
                if len(header_real_qty_list) > 0:    
                    ### update buy order (real_qty, real_price, real_fee) ★
                    header_sum_real_qty = round(sum(header_real_qty_list), _q_)                            
                    header_sum_real_fee = round(sum(header_real_fee_list), 0)                                  
                    
                    if sum(header_real_price_list) > 0:
                        header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)  
                    else:
                        header_mean_real_price = 0    
    
                    find_mask = buy_df['buy_time'] == buy_time
                    dfx = buy_df[find_mask]
                    # not found buy order ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL}buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order => update
                        buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty     # add real_qty (rebuy version)                            
                        buy_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (rebuy version) 
                        buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee     # add real_fee (rebuy version) 
                        ix = dfx.index[0]
                        new_header_sum_real_qty = buy_df.loc[ix, 'real_qty'] 
                        new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']                
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ")                                                    
                    # end of if dfx.shape[0] == 0:          
                # end of if len(header_real_qty_list) > 0: 
    
                # reset buy header list values
                header_real_qty_list = []
                header_real_price_list = []
                header_real_fee_list = []     
    
                # continue to next buy order... ★              
            # end of for i in range(len(buy_df)):  
    
            #################################################################################
            ### 【12】SAVE BUY/SELL ORDER HEADER TO CSV FILES 
            #################################################################################        
    
            ### CLOSE (save the buy order csv file)       
            csv_file = self.folder_trading_type + f'buy_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/buy_order(BTC_JPY).csv  
            buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode  
    
            ### CLOSE (save the sell order csv file)      
            csv_file = self.folder_trading_type + f'sell_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order(BTC_JPY).csv   
            sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode 
    
            return      

    click image to zoom!
    図9-1
    図9-1はデモプログラムの実行結果です。 画面にはSellBuyクラスのcancel_pending_order()メソッドが実行されたログが表示されています。 このメソッドは後述するレバレッジ手数料を発生させたくない場合などに使用します。 GMOコインはポジションを保持した状態で午前6時を経過するとレバレッジ手数料を加算します。 BOTはこのレバレッジ手数料を回避するために強制的にポジションを決済する機能をサポートしています。

    ポジションを強制的に決済させるには、 Gvarクラスのcancel_pending_ordersプロパティに「True」を設定して自動トレードします。


    click image to zoom!
    図9-2
    図9-2は買いのポジションを強制的に決済したときに作成されたCSVファイルと損益ファイルをExcelで表示した画面です。 「buy_order_sub(XRP_JPY).csv」ファイルの「real_qty」には買いの数量「40XRP_JPY」が格納されています。 「buy_order_child(XRP_JPY).csv」ファイルの「real_qty」には執行された数量が「20XRP_JPY」に分割されて格納されています。 Excelシートの行2(上段)はGMOコインが部分的に執行した数量で、 行3(下段)はBOTが強制的に残数「20XRP_JPY」を決済したときの数量です。

    ポジションが強制的に決済されたかどうか調べるには、 まず「buy_order_sub(XRP_JPY).csv」の「cancelled」の値をチェックします。 「TRUE」が格納されていれば強制的に決済されています。 この場合、「buy_order_child(XRP_JPY).csv」の「buy_order_id」に異なる買い注文IDが格納されます。


  10. SellBuyクラスにclose_pending_order()メソッドを追加する

    ここではSellBuyクラスにclose_pending_order()メソッドを追加します。 このメソッドはレバレッジ手数料を計算させたいときに使用します。 BOTを使用するときは、午前5時~午前6時の時間帯に自動的にこのメソッドが呼ばれます。 close_pending_order()はポジションを保留している注文のレバレッジ手数料を計算して損益ファイルを更新します。

    sell_buy.py:
        #######################################  
        def close_pending_order(self) -> None: 
            """        
         
            """    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal     
            self.gvar.callers_method_name = 'close_pending_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} ") 
    
            # df = self.coin.master2_df
            # mas_df = pd.DataFrame(df)   # cast df (master dataframe)
           
            ### OPEN (load the order info csv file) : order_by(BTC_JPY).csv 
            ord_df = self.csv.get_order_csv()  
    
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)  
    
            # ### OPEN (load the buy order child csv file) : buy_order_child(BTC_JPY).csv 
            # buy_ch_df = self.csv.get_buy_order_child() # PK(buy_time + buy_order_id + position_id)          
              
            # ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            # sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            # ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            # sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            ### OPEN (load the sell order child csv file) : sell_order_child(BTC_JPY).csv 
            sell_ch_df = self.csv.get_sell_order_child() # PK(sell_time + sell_order_id + position_id)          
    
            #################################################
            ### 【1】READ ALL BUY ORDER HEADERS
            #################################################
     
            ### get all buy order headers 
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']    # ★ unique time 
                sell_time      = buy_df.loc[i, 'sell_time']   # ★ unique time                                   
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True         
            
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        ix = dfx.index[0]                
                        buy_order_id = dfx.loc[ix, 'buy_order_id']    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    # end of if dfx.shape[0] > 0:
    
                    continue    
                # end of if buy_order_closed:  
    
                #################################################################
                ### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
                #################################################################
    
                ### for buy order sub (open or incomplete status)
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    ### get buy order sub info
                    sub_buy_time        = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time       = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id    = buy_subx_df.loc[j, 'buy_order_id']       # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
                    sub_sell_order_id   = buy_subx_df.loc[j, 'sell_order_id']      # ★ 'S9999-999999-999999' (unique id)           
                    sub_position_id     = buy_subx_df.loc[j, 'position_id']        # ★ (unique id)
                    sub_qty             = buy_subx_df.loc[j, 'qty']                                  
                    sub_price           = buy_subx_df.loc[j, 'price']               
                    sub_real_qty        = buy_subx_df.loc[j, 'real_qty']                        
                    sub_real_price      = buy_subx_df.loc[j, 'real_price']            
                    sub_real_fee        = buy_subx_df.loc[j, 'real_fee']        
                    sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']            
                    sub_stop_loss       = buy_subx_df.loc[j, 'stop_loss']
                    sub_cancelled       = buy_subx_df.loc[j, 'cancelled']                
            
                    sub_order_open = False
                    sub_order_closed = False
                    sub_order_incomplete = False
    
                    if sub_real_qty == 0:
                        sub_order_open = True
                    elif sub_qty == sub_real_qty:
                        sub_order_closed = True
                    else:
                        sub_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP    
                    if sub_buy_order_id.startswith('Fake') :
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id=}) ") 
                        continue               
    
                    # buy order sub has been stopped loss ? => SKIP
                    if sub_stop_loss: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")  
                        continue
    
                    # cancelled buy order sub ? => SKIP    
                    if sub_cancelled: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ") 
                        continue           
    
                    # closed buy order sub ? => SKIP    
                    if sub_order_closed: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ") 
                        continue                 
                 
                    ### open or incomplete buy order sub  
                     
                    #################################################################
                    ### 【3】CALCULATE LEVERAGE FEE AND UPDATE ORDER CSV FILE
                    #################################################################
    
                    ### calculate leverage fee 
    
                    # get a sell order child 
    
                    find_mask = (sell_ch_df['sell_time'] == sub_sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id) & (sell_ch_df['position_id'] == sub_position_id) 
                    dfx = sell_ch_df[find_mask]
                    # not found sell order child ?
                    if dfx.shape[0] == 0:
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found sell order child{Bcolors.ENDC} : {sub_sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {sub_position_id=}  ") 
                    else: # found sell order child 
                        ix = dfx.index[0]
                        ch_sell_real_qty = dfx.loc[ix, 'real_qty']
                        ch_sell_real_price = dfx.loc[ix, 'real_price']
                        ch_leverage_fee = math.ceil((ch_sell_real_price * ch_sell_real_qty) * 0.0004)   # leverage fee (0.04%)
    
                        ### update order csv (accumulated_leverage_fee, close_count, close_update_date)
    
                        # not empty order csv ?
                        if ord_df.shape[0] > 0:
                            find_mask = ord_df['sell_order_id'] == sub_sell_order_id  # S9999-999999-999999
                            dfx = ord_df[find_mask]
                            # not found order csv ?
                            if dfx.shape[0] == 0:   
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found order csv{Bcolors.ENDC} : {sub_sell_order_id=}  ") 
                            else: # found order csv 
                                update_order_csv = False
                                ix = dfx.index[0]
                                close_count = ord_df.loc[ix, 'close_count']
                                close_update_date = ord_df.loc[ix, 'close_update_date']
                                accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
                                if close_count == 0:
                                    update_order_csv = True                             
                                else: # close_count > 0
                                    now_date_str = f"{dt.datetime.now():%Y%m%d}"            # YYYYMMDD    
                                    close_update_date_str = f"{close_update_date:%Y%m%d}"   # YYYYMMDD                                
                                    if close_update_date_str != now_date_str:
                                        update_order_csv = True
                                    # self.debug.trace_warn(f"DEBUG: update_order_csv={update_order_csv}, close_count={close_count}, now_date_str={now_date_str}, close_update_date_str={close_update_date_str}, leverage_fee={accumulated_leverage_fee:,.0f} ")    
                                    # update_order_csv = True  # DEBUG DEBUG DEBUG
                                # end of if close_count == 0:
    
                                if update_order_csv:
                                    # update order csv 
                                    ord_df.loc[find_mask, 'accumulated_leverage_fee'] += ch_leverage_fee      
                                    ord_df.loc[find_mask, 'close_count'] += 1
                                    ord_df.loc[find_mask, 'close_update_date'] = dt.datetime.now()           
                                    ord_df.loc[find_mask, 'close_update_date'] = dt.datetime.now() 
    
                                    ord_df['close_update_date']  = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)  
                                    new_accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
                                    new_close_count = ord_df.loc[ix, 'close_count']                               
                                    new_close_update_date = ord_df.loc[ix, 'close_update_date']
                                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell(): sell_order_id={sub_sell_order_id}, leverage_fee={new_accumulated_leverage_fee:,.0f}, close_count={new_close_count}, close_update_date={new_close_update_date:%Y-%m-%d %H:%M:%S}  ") 
                                                        
                        # end of if ord_df.shape[0] > 0:
                    # end of if dfx.shape[0] == 0:                 
             
                    # continue to next buy order sub...
                # end of for j in range(len(buy_subx_df)):                                
    
                # continue to next buy order... ★              
            # end of for i in range(len(buy_df)): 
    
            #################################################################################
            ### 【4】SAVE ORDER CSV TO CSV FILE 
            #################################################################################        
    
            ### CLOSE (save to the order csv file) : order(BTC_JPY).csv
            if ord_df.shape[0] > 0:
                ### CLOSE (order csv file)
    
                csv_file = self.folder_trading_type + f'order({symbol}).csv'  
                # desktop-pc/bot/sell_buy/order(BTC_JPY).csv   
                ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file  
    
            return         

    click image to zoom!
    図10-1
    図10-1はデモプログラムの実行結果です。 画面にはclose_pending_order()メソッドが実行されたことがログに表示されています。


    click image to zoom!
    図10-2
    図10-2はCSVファイルと損益ファイルをExcelで開いたときの画面です。 「sell_order_child(XRP_JPY).csv」ファイルの「real_qty」には空売りの注文で執行された数量「40XRP_JPY」が格納されています。 「buy_order_sub(XRP_JPY).csv」ファイルの「real_qty」には買い取りの注文で執行された数量「20XRP_JPY」が格納されています。 買い取り注文の残数「20XRP_JPY」があるので、GMOコインは午前6時を経過するとレバレッジ手数料を加算します。

    「order(XRP_JPY).xlsx」ファイルにはこのレバレッジ手数料が自動的に計算されて格納されます。 Excelのシート「fee(L)」にはレバレッジ手数料の累計が格納されます。 「count(L)」にはレバレッジ手数料が加算された回数が格納されます。 「close(L)」にはレバレッジ手数料が加算された最終日時が格納されます。 ちなみに(L)は「Leverage」の略です。 「profit(AMT)」から「fee(L)」を減算すると純利益が計算できます。


  11. SellBuyクラスの全てのソースコードを掲載

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

    sell_buy.py:
    
    # sell_buy.py
    
    """
    SellBuy class
    """
    
    # import the libraries
    from time import sleep  
    import datetime as dt
    from datetime import timedelta
    
    import math
    import statistics
    from decimal import Decimal
    
    import numpy as np
    import pandas as pd
    
    from lib.base import Bcolors, Gvar, Coin, Gmo_dataframe 
    from lib.debug import Debug
    from lib.api import Api
    from lib.csv import Csv
    from lib.trade import Trade    
    
    import warnings
    warnings.simplefilter('ignore')
    
    ########################### sell buy class
    class SellBuy:
        """
        __init__(), __str__(),     
        reset_restart_process(), 
        sell_buy(), sell_buy_algorithm_0(), sell_buy_algorithm_1, sell_buy_algorithm_2(), sell_buy_algorithm_3(),...
        create_sell_order(), create_buy_order()
        update_order(), update_closed_order(), update_partially_closed_order(), update_order_profit() 
        stop_loss(), stop_loss_0(), stop_loss_1(),...
        cancel_pending_order()
        close_pending_order()
        """
               
        ######################################################
        def __init__(self, gvar: object, coin: object) ->None: 
            self.gvar = gvar                            # Gvar
            self.coin = coin                            # Coin       
            self.debug = Debug(gvar)                    # dependent class       
            self.api = Api(gvar, coin)                  # dependent class
            self.csv = Csv(gvar, coin)                  # 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/sell_buy/                     
            self.debug.print_log(f"{Bcolors.OKGREEN}{self.gvar.exchange.upper()} SellBuy({self.gvar.althorithm_id},{self.gvar.sellbuy_condition}){Bcolors.ENDC} class initiated: {self.coin.symbol}, qty=[{self.coin.qty:,.{self.coin.qty_decimal}f}], rate=[{self.coin.profit_rate:.4f}, {self.coin.stop_loss_rate:.4f}], debug1={self.coin.debug_mode_debug1}, debug2={self.coin.debug_mode_debug2}  ")  
            return
    
        #########################
        def __str__(self) -> str:              
            return f"SellBuy({self.folder_gmo=}, {self.folder_trading_type=}, {self.gvar.real_mode=}, {self.gvar.reset_mode=}, {self.gvar.althorithm_id=}, {self.coin.symbol=})"  
        
        ######################################## 
        def reset_restart_process(self) -> None:
            """
            
            """
            self.gvar.callers_method_name = 'reset_restart_process():'
            symbol = self.coin.symbol
    
            # get master df from the coin class
            master_df = self.coin.master_df     
            if master_df.shape[0] == 0: 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - master_df is empty {master_df.shape[0]=} return ")
                self.debug.sound_alert(3)    
                return             
    
            order_qty = self.coin.qty       
            mas_df = master_df    
    
            # --------------------------------------------------------------
            #  #   Column     Dtype
            # --------------------------------------------------------------
            #  0   price      float64
            #  1   side       object
            #  2   size       float64
            #  3   timestamp  datetime64[ns, UTC]    
            # -------------------------------------------------------------              
    
            ### get latest buy order info from the GMO dataframe (timestamp is sorted by ascending order)
    
            buy_time = mas_df.iloc[0]['timestamp']  # get oldest time
            buy_price = mas_df.iloc[0]['price']     # get oldest price
            buy_order_id = 'FakeB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')       # FakeB:MMDD-HHMMSS-999999(milliseconds)
            buy_position_id = 'PosiB:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')    # PosiB:MMDD-HHMMSS-999999(milliseconds)                   
    
            # round time
            freq = '1T'                           
            buy_time = buy_time.round(freq=freq)                                        # round x minutes (where x is 1,4,10,15,30)   
            buy_time = buy_time.round(freq='S')                                         # round seconds
            buy_time = buy_time - timedelta(hours=1)                                    # substract 1 hour
    
            ### get sell order info from the GMO dataframe
               
            sell_time = buy_time # + timedelta(seconds=5)
            sell_price = self.trade.get_sell_price(buy_price)                                  
            sell_order_id = 'FakeS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')      # FakeS:MMDD-HHMMSS-999999(milliseconds)
            sell_position_id = 'PosiS:' + dt.datetime.now().strftime('%m%d-%H%M%S-%f')   # PosiS:MMDD-HHMMSS-999999(milliseconds)
          
            # reset mode ?
            if self.gvar.reset_mode or self.coin.master_csv_not_found:      
                if self.gvar.reset_mode:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} reset): delete_allfiles() ▶ add a fake data into the sell/buy order csv files...")
                else:    
                    self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): master_csv_not_found(True) ▶ add a fake data into the sell/buy order files...")
    
                ### 1) add a fake data into the sell order, sell order sub files
    
                # write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:     
                self.csv.write_sell_order(sell_time, buy_time, order_qty, sell_price, order_qty, sell_price, 0.0)                     
                
                # write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:   
                self.csv.write_sell_order_sub(sell_time, buy_time, sell_order_id, buy_order_id, sell_position_id, order_qty, sell_price, order_qty, sell_price, 0.0)
    
                ### 2) add a fake data into the buy order, buy order sub files
    
                # write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
                self.csv.write_buy_order(buy_time, sell_time, order_qty, buy_price, order_qty, buy_price, 0.0)                                              
    
                # write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None: 
                self.csv.write_buy_order_sub(buy_time, sell_time, buy_order_id, sell_order_id, buy_position_id, order_qty, buy_price, order_qty, buy_price, 0.0)        
        
            else: # restart mode
                self.debug.print_log(f".... {self.gvar.callers_method_name} processing({symbol} restart): skip close pending orders for restart...")     
            # end of if self.gvar.reset_mode: 
    
            return
    
        ##############################################  
        def sell_buy(self, algorithm_id: int) -> None:  
            """
            
            """
            if algorithm_id <= 3:
                eval(f'self.sell_buy_algorithm_{str(algorithm_id)}()')    # call 'self.sell_buy_algorithm_?() method
            else:
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} -  skip sell_buy ")
                self.debug.sound_alert(3)   
    
            # if algorithm_id == 0:
            #     self.sell_buy_algorithm_0() 
            # elif algorithm_id == 1:
            #     self.sell_buy_algorithm_1()                
            # elif algorithm_id == 2:             
            #     self.sell_buy_algorithm_2()
            # elif algorithm_id == 3:             
            #     self.sell_buy_algorithm_3()            
            # else:
            #     self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid Algorithm_id {algorithm_id=} {Bcolors.ENDC} -  skip sell_buy ")
            #     self.debug.sound_alert(3)                   
    
            return        
     
        #######################################  
        def sell_buy_algorithm_0(self) -> None:
            """
    
            """        
            self.gvar.callers_method_name = 'sell_buy(0):'  
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal               
    
            df = self.coin.master2_df
            mas_df = pd.DataFrame(df) # cast dataframe()
    
            ### filter buy master
            filter_mask = mas_df['side'] == 'BUY'
            mas_buy_df = mas_df[filter_mask]
            # mas_buy_df is less than trend_search_count ? => SYTEM ERROR
            if mas_buy_df.shape[0] < self.coin.trend_search_count:  # < 3 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - {mas_buy_df.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
                self.debug.sound_alert(3)    
                return   
    
            ### filter sell master
            filter_mask = mas_df['side'] == 'SELL'
            mas_sell_df = mas_df[filter_mask]
            # mas_sell_df is less than trend_search_count ? => SYTEM ERROR
            if mas_sell_df.shape[0] < self.coin.trend_search_count:  # < 3 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - {mas_sell_df.shape[0]=} is less than {self.coin.trend_search_count}. skip sell_buy() ")
                self.debug.sound_alert(3)    
                return 
         
            self.debug.trace_write(f".... {self.gvar.callers_method_name} sell_buy_condition({self.gvar.sellbuy_condition}), {mas_buy_df.shape[0]=},  {mas_sell_df.shape[0]=} ")  
    
            # mas_df:
            # -------------------------------------------------------------
            #  #   Column      Dtype
            # -------------------------------------------------------------
            #  0   time        datetime64[ns, UTC]
            #  1   open        float64
            #  2   close       float64
            #  3   low         float64
            #  4   high        float64
            #  5   price       float64
            #  6   buy_price   float64
            #  7   sell_price  float64
            #  8   side        object
            #  9   size        float64
            #  10  symbol      object
            # ---------------------------------------------------------------
                           
            position = 'buy or sell'                                     
    
            ### load buy order / buy order sub files
            buy_df = self.csv.get_buy_order()             # ★   
            buy_sub_df = self.csv.get_buy_order_sub()     # ★         
    
            ### load sell order / sell order sub files
            sell_df = self.csv.get_sell_order()           # ★ 
            sell_sub_df = self.csv.get_sell_order_sub()   # ★  
      
            ### update the position depending on buy/sell sub order files            
    
            ### get the last sell order sub info
            last_buy_time  = sell_sub_df.iloc[-1]['buy_time']  
            last_sell_time = sell_sub_df.iloc[-1]['sell_time']      
            last_sell_real_qty = sell_sub_df.iloc[-1]['real_qty']       
            last_sell_real_price = sell_sub_df.iloc[-1]['real_price']      
    
            ### get the last buy order (pair of the sell order)
    
            # find the pair of the buy order
            find_mask = buy_df['buy_time'] == last_buy_time
            dfx = buy_df[find_mask]
            # not found buy order ?
            if dfx.shape[0] == 0:
                # self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ buy order not found: {last_buy_time:%Y-%m-%d %H:%M:%S=} ")
                last_buy_real_qty = 0
            else: ### found pair of the buy order
                # get buy order real_qty
                ix = dfx.index[0]
                last_buy_real_qty = dfx.loc[ix, 'real_qty']
    
            # closed sell/buy order ?
            if round(last_buy_real_qty, _q_) == round(last_sell_real_qty, _q_):
                position = 'sell'    # buy order not required => position(SELL) 
                self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}NEW SELL ORDER REQUEST{Bcolors.ENDC} ")     
            else: # not found buy order or incomplete buy order
                return  # skip return
            # end of if last_buy_real_qty == last_sell_real_qty:    
    
            # sell position ? 
            if position.startswith('sell'):       
                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SELL{Bcolors.ENDC} position (short position) ")
                
                ### SELL position 
    
                ### check sell condition and create a sell order
        
                # get the latest sell_time & latest sell_price from GMO master dataframe
                mas_time  = mas_sell_df.iloc[-1]['time']     # gmo time      ★             
                mas_price = mas_sell_df.iloc[-1]['price']    # gmo price    ★                
    
                while True:    
    
                    ###########################################################
                    ### 1: check duplicate sell order
                    ###########################################################
    
                    # mas_time(gmo) is less than or equal to last_sell_time ? => SKIP
                    if mas_time <= last_sell_time: 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(1){Bcolors.ENDC} mas_time(gmo) is less than or equal to last_sell_time: {mas_time=:%Y-%m-%d %H:%M:%S} <= {last_sell_time=:%Y-%m-%d %H:%M:%S} ")      
                        break   # exit while true loop              
                                                        
                    # found sell order ? => SKIP
                    # find_order(df: pd.DataFrame, col: str, val=any) -> bool:
                    if self.trade.find_order(sell_df, col='sell_time', val=mas_time):   
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP(2){Bcolors.ENDC} duplicate sell order: {mas_time=:%Y-%m-%d %H:%M:%S} ") 
                        break   # exit while true loop 
    
                    ### no duplicate sell order      
    
                    ###########################################################
                    ### 2: execute sell order (MARKET) & buy order (LIMIT)
                    ###########################################################                
    
                    qty = self.coin.qty                                                                
                    
                    ### execute sell order with qty (MARKET) 
                    # create_sell_order(sell_time: utcnow, qty: float, price: float) -> int: 
                    status = self.create_sell_order(mas_time, qty, mas_price)    # ★ price is used for fake mode: execute sell order with market price
    
                    if status == 0:
                        ### reload sell order sub file     
                        sell_sub_df = self.csv.get_sell_order_sub()     # ★ 
    
                        ### get the last sell order sub info
                        last_sell_time  = sell_sub_df.iloc[-1]['sell_time']                                          
    
                        ### execute buy order (LIMIT) 
                        # create_buy_order( buy_time: utcnow, ms_price: float, sell_time: utcnow) -> int:              
                        status = self.create_buy_order(mas_time, mas_price, last_sell_time) # ★ 
    
                    break   # exit while true loop            
                # end of while True:                
            
            return     
        
        #######################################  
        def sell_buy_algorithm_1(self) -> None:
            """
    
            """   
            return         
    
        ######################################## 
        def sell_buy_algorithm_2(self)  -> None:
            """
    
            """        
        
            return  
    
        ####################################### 
        def sell_buy_algorithm_3(self) -> None:
            """
    
            """        
        
            return  
    
        ############################################################################################ 
        def create_sell_order(self, sell_time: dt.datetime.utcnow, qty: float, price: float) -> int: 
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal        
            
            self.gvar.callers_method_name = 'create_sell_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order({sell_time=:%Y-%m-%d %H:%M:%S}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}) ")  
            status = 0
    
            # reset buy/sell position count
            self.coin.position_count_0_10 = 0        
            self.coin.position_count_11_30 = 0       
            self.coin.position_count_31_50 = 0      
            self.coin.position_count_51_70 = 0      
            self.coin.position_count_71_90 = 0      
            self.coin.position_count_91_999 = 0     
        
            ################################################
            ### 【1】CHECK DUPLICATE SELL ORDER 
            ################################################
    
            ### check duplicate sell order 
    
            # OPEN (load the sell order file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order()             
    
            # find a sell order 
            find_mask = sell_df['sell_time'] == sell_time
            dfx = sell_df[find_mask]          
            # duplicate sell order ?     
            if dfx.shape[0] > 0:
                status = 9      # set internal status code 
                self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate sell order ({sell_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
                return status   # error return       
    
            # reset buy_position_list
            self.coin.buy_position_list = [] 
    
            #############################################################
            ### 【2】EXECUTE SELL ORDER MARKET (FAK: Fill And Kill)
            #############################################################
    
            ### execute sell order with market price : FAK (Fill And Kill)       
    
            sell_qty = qty       # save original sell qty ★ 
    
            # define sell order header list
            header_real_qty_list = []
            header_real_price_list = []
            header_real_fee_list = []          
    
            status = 0
            loop_count = 0      
    
            while True:
                loop_count += 1
    
                # 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 = self.api.exe_sell_order_market_wait(sell_qty, price)  # ★ FAK (Fill And Kill) : price is used for fake mode  
                res_df = pd.DataFrame(res_df)   # cast           
                self.debug.print_log(f".... {self.gvar.callers_method_name} while({loop_count}): exe_sell_order_market_wait() ▶ {status=}, {res_df.shape=} ")
    
                ####################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()           
                if not self.coin.real_mode:
                    if self.coin.debug_mode_debug1: 
                        # ★ create two sell order subs for each sell order header ★
                        # update_get_price_response_for_debug1_1(res_df: pd.DataFrame, loop_count: int) -> pd.DataFrame:
                        res_df = self.trade.update_get_price_response_for_debug1_1(res_df, loop_count)   
                        # loop_count == 1: return 2 rows => add 2 buy subs
                        # loop_count == 2: return 1 row  => add 1 buy sub                       
                # end of if not self.coin.real_mode:
                ###################################################################### DEBUG(1_1) DEBUG(1_1) DEBUG(1_1) : create_sell_order()
    
                # exe_sell_order_market_wait() error ?
                if status != 0:
                    if status == 1 and msg_code == 'ERR-201': # Trading margin is insufficient ?      
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}, {price=:,.{_p_}f}): ▶ {Bcolors.FAIL}the trading margin is insufficient error: ERR-201{Bcolors.ENDC} ")
                        self.debug.sound_alert(2)  
                        break       # exit while true loop => error return         
                    elif status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}get_price() time out error: {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                        break       # exit while true loop => error return 
                    else: # misc error
                        self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_sell_order{Bcolors.ENDC}({sell_qty=:,.{_q_}f}, {price=:,.{_p_}f})): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                        break       # exit while true loop => error return 
                # end of if status != 0:
                             
                ### no error : res_df.shape[0] >= 1 (single row or multiple rows)            
                       
                ################################################################################################################
                ### 【3】ADD A SELL ORDER CHILD
                ################################################################################################################
    
                # res_df = get_price() response for exe_buy_order_market_wait()
                # ---------------------------------------------------------
                #  #   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]
                # --------------------------------------------------------- 
    
                ### write a sell order child
          
                # define sell order child list
                sell_order_id_list = []
                position_id_list = []
                real_qty_list = []
                real_price_list = []
                real_fee_list = []   
    
                # response df for exe_sell_order_market_wait(FAK) 
                for k in range(len(res_df)):
                    sell_order_id   = res_df.loc[k, 'orderId']          # sell order id (same order id)
                    position_id     = res_df.loc[k, 'positionId']       # position id (unique id)
                    real_qty        = res_df.loc[k, 'size']             # real qty  
                    real_price      = res_df.loc[k, 'price']            # real price
                    real_fee        = res_df.loc[k, 'fee']              # real fee
    
                    sell_order_id_list.append(sell_order_id)            # S9999-999999-999999
                    position_id_list.append(position_id)                # P9999-999999-999999
                    real_qty_list.append(real_qty)                  
                    real_price_list.append(real_price)
                    real_fee_list.append(real_fee)
    
                    # write_sell_order_child(sell_time: utcnow, sell_order_id: str, buy_time: utcnow, buy_order_id: str, position_id: str, qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:        
                    _buy_time_ = sell_time; _buy_order_id_ = 'Dummy';  _qty_ = real_qty; _price_ = real_price  
                    self.csv.write_sell_order_child(sell_time, sell_order_id, _buy_time_, _buy_order_id_, position_id, _qty_, _price_, real_qty, real_price, real_fee)
                # end of for k in range(len(res_df)): 
    
                first_sell_order_id = sell_order_id_list[0]   # same order ids              
                position_ids_by_semicolon = ''  # 'positionId1;positionId2;positionId3'
                for k, position_id_x in enumerate(position_id_list):
                    if k == 0:
                        position_ids_by_semicolon += position_id_x
                    else:
                        position_ids_by_semicolon += ';' + position_id_x    
    
                sub_sum_real_qty = round(sum(real_qty_list), _q_)   # XRP_JPY(9999)              
                sub_sum_real_fee = round(sum(real_fee_list), 0)     # zero(0)
    
                if sum(real_price_list) > 0:
                    sub_mean_real_price = round(statistics.mean(real_price_list), _p_)  # 999.999
                else:
                    sub_mean_real_price = 0                      
    
                header_real_qty_list.append(sub_sum_real_qty)        
                header_real_price_list.append(sub_mean_real_price)
                header_real_fee_list.append(sub_sum_real_fee)                   
    
                ###################################################################################################################
                ### 【4】ADD A SELL ORDER SUB (buy_time, sell_time,...) ★ buy_time <= sell_time
                ###################################################################################################################
    
                ### write a sell order sub 
                # write_sell_order_sub(sell_time: utcnow, buy_time: utcnow, sell_order_id: str, buy_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
                _buy_time_ = sell_time; _buy_order_id_ = 'Dummy';  _qty_ = sub_sum_real_qty # positionId = 'positionId1;positionId2;positionId3'
                self.csv.write_sell_order_sub(sell_time, _buy_time_, first_sell_order_id, _buy_order_id_, position_ids_by_semicolon, _qty_, price, sub_sum_real_qty, sub_mean_real_price, sub_sum_real_fee) # ★
    
                ### check exit condition     
    
                header_sum_real_qty = round(sum(header_real_qty_list), _q_) # XRP_JPY(9999)            
                # self.debug.trace_write(f".... {self.gvar.callers_method_name} while({loop_count}): buy_qty={buy_qty}, header_sum_real_qty={header_sum_real_qty} ")             
          
                ########################################### TEMP TEMP TEMP
                # temp_qty = round(qty, _q_) 
                # temp_real_qty = round(header_sum_real_qty, _q_)
                # self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
                ########################################### TEMP TEMP TEMP            
    
                # no outstanding sell_qty ?
                if round(qty, _q_) == round(header_sum_real_qty, _q_):  
                    # increment sell count      
                    self.gvar.sell_count += 1  
                    self.coin.sell_count += 1                  
                    break   # exit while true loop             
                else:       # outstanding sell_qty exists      
                    pass    # skip (do nothing)                      
                # end of if round(buy_qty, digits) == round(header_sum_real_qty, digits):  
    
                ### partial exe_sell_order_market_wait() was executed => continue to sell remaining qty
                    
                remaining_qty = sell_qty - header_sum_real_qty   # calculate new sell qty        
    
                sell_qty = remaining_qty # update new sell qty      
    
                # continue exe_sell_order_market_wait() ★           
            # end of while True:    
    
            #################################################################################
            ### 【5】ADD A SELL ORDER HEADER (buy_time, sell_time) ★ buy_time <= sell_time
            #################################################################################
    
            ### update sell order header(real_qty, real_price, real_fee)
            header_sum_real_qty = round(sum(header_real_qty_list), _q_)   # XRP_JPY(9999)    
            header_sum_real_fee = round(sum(header_real_fee_list), 0)     # zero(0)        
    
            if sum(header_real_price_list) > 0:
                header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)  # XRP_JPY(9999.123)
            else:
                header_mean_real_price = 0            
    
            ### write a sell order  
            # write_sell_order(sell_time: utcnow, buy_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:    
            _buy_time_ = sell_time  
            self.csv.write_sell_order(sell_time, _buy_time_, qty, price, header_sum_real_qty, header_mean_real_price, header_sum_real_fee)       
             
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_sell_order() ▶ {status=} normal return... ")                
    
            return status       
    
        ################################################################################################################ 
        def create_buy_order(self, buy_time: dt.datetime.utcnow, ms_price: float, sell_time: dt.datetime.utcnow) -> int:     
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal                
    
            self.gvar.callers_method_name = 'create_buy_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, {ms_price=:,.{_p_}f}, {sell_time=:%Y-%m-%d %H:%M:%S}) ") 
    
            status = 0    
            
            ################################################
            ### 【1】CHECK DUPLICATE BUY ORDER 
            ################################################
    
            ### check duplicate buy order 
    
            # get the buy order : sbuy_order(BTC_JPY).csv
            buy_df = self.csv.get_buy_order()          
    
            # find a buy order 
            find_mask = buy_df['buy_time'] == buy_time   # PK(buy_time)
            dfx = buy_df[find_mask]          
            # duplicate buy order ?     
            if dfx.shape[0] > 0:
                status = 9      # set internal status code
                self.debug.trace_write(f".... {self.gvar.callers_method_name} duplicate buy order ({buy_time=:%Y-%m-%d %H:%M:%S}) ▶ {status=} error return... ")
                return status   # error return 
              
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv          
            sell_sub_df = self.csv.get_sell_order_sub()  
    
            ### OPEN (load the sell order child csv file ) : sell_order_child(BTC_JPY).csv          
            sell_ch_df = self.csv.get_sell_order_child()  
       
            ######################################################################
            ### 【2】READ ALL SELL ORDER SUBs (filter by sell_time)
            ######################################################################        
            
            # define sell order sub list
            sub_qty_list = []
            sub_price_list = []
    
            # filter the sell order sub by sell_time
            filter_mask = sell_sub_df['sell_time'] == sell_time
            sell_subx_df = sell_sub_df[filter_mask]
            if sell_subx_df.shape[0] > 0:
                sell_subx_df.reset_index(inplace=True)
    
            # get all sell order sub filtered by sell_time 
            for i in range(len(sell_subx_df)):   # ★ sell_subx_df ★
                ### get sell order sub info
                sub_sell_time       = sell_subx_df.loc[i, 'sell_time']        # non unique                 
                sub_sell_order_id   = sell_subx_df.loc[i, 'sell_order_id']    # unique id  
                sub_qty             = sell_subx_df.loc[i, 'qty']                                    
                sub_price           = sell_subx_df.loc[i, 'price']               
                sub_real_qty        = sell_subx_df.loc[i, 'real_qty']                            
                sub_real_price      = sell_subx_df.loc[i, 'real_price']            
                sub_real_fee        = sell_subx_df.loc[i, 'real_fee']                  
                      
                #################################################################################
                ### 【3】READ ALL SELL ORDER CHILD (filter by sell_time + sell_order_id)
                #################################################################################  
    
                # define sell order child list
                ch_buy_order_id_list = []
                ch_sell_order_id_list = []
                ch_position_id_list = []
                ch_qty_list = []
                ch_price_list = []                  
    
                ### filter the sell order child by sell_time + selll_order_id
                
                # sell order child is empty ?
                if sell_ch_df.shape[0] == 0:            
                    sell_chx_df = pd.DataFrame()
                else: # filter sell order child    
                    filter_mask = (sell_ch_df['sell_time'] == sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id)
                    sell_chx_df = sell_ch_df[filter_mask]
                    if sell_chx_df.shape[0] > 0:
                        sell_chx_df.reset_index(inplace=True)
                # end of if sell_ch_df.shape[0] == 0: 
    
                # get all sell order child filtered by sell_time + sell_order_id
                for j in range(len(sell_chx_df)):   # ★ sell_chx_df ★
                    ### get buy order child info
                    ch_sell_time     = sell_chx_df.loc[j, 'sell_time']      # non unique time       
                    ch_sell_order_id = sell_chx_df.loc[j, 'sell_order_id']  # non unique id (same sell_order_id)      
                    ch_position_id   = sell_chx_df.loc[j, 'position_id']    # unique id
                    ch_qty           = sell_chx_df.loc[j, 'qty']                                    
                    ch_price         = sell_chx_df.loc[j, 'price']               
                    ch_real_qty      = sell_chx_df.loc[j, 'real_qty']                            
                    ch_real_price    = sell_chx_df.loc[j, 'real_price']            
                    ch_real_fee      = sell_chx_df.loc[j, 'real_fee'] 
    
                    #############################################################################################
                    ### 【4】EXECUTE BUY CLOSE ORDER LIMIT (FAS:Fill And Store) FOR EACH SELL ORDER CHILD 
                    ############################################################################################# 
           
                    buy_qty = ch_real_qty
                    buy_price = self.trade.get_buy_price(ch_real_price, trace=True)                          
    
                    # market price is less than than buy price
                    if ms_price < buy_price:
                        buy_price = ms_price                  
                  
                    ### execute a buy close order with limit(FAS) 
                    # exe_buy_close_order_limit(position_id: str, qty: float, price: float) ->tuple:    # return (status, res_order_id, msg_code, msg_str)
                    status, res_buy_order_id, msg_code, msg_str = self.api.exe_buy_close_order_limit(ch_position_id, buy_qty, buy_price) # ★      
                    # res_buy_order_id => 'B9999-999999-999999'       
    
                    # exe_buy_close_order_limit() error ?
                    if status != 0:
                        if status == 1 and msg_code == 'ERR-208':   # Exceeds the available balance
                            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}the available balance exceeded ERR-208: continue...{Bcolors.ENDC} ")
                            self.debug.sound_alert(2)  
                            continue    # continue to next sell order child        
                        else: # misc error
                            self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}({buy_qty=:,.{_q_}f}): ▶ {Bcolors.FAIL}misc error: {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")                               
                            self.debug.sound_alert(2) 
                            continue    # continue to next sell order child             
                    # end of if status != 0:
    
                    ### no error for exe_buy_close_order_limit()        
    
                    ################################################################################################################
                    ### 【5】ADD A BUY ORDER SUB
                    ################################################################################################################
                            
                    ### write a buy order sub
                    # write_buy_order_sub(buy_time: utcnow, sell_time: utcnow, buy_order_id: str, sell_order_id: str, position_id: str, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:                                                       
                    self.csv.write_buy_order_sub(buy_time, sell_time, res_buy_order_id, ch_sell_order_id, ch_position_id, buy_qty, buy_price)       
     
                    ch_buy_order_id_list.append(res_buy_order_id)
                    ch_sell_order_id_list.append(ch_sell_order_id)
                    ch_position_id_list.append(ch_position_id)
                    ch_qty_list.append(buy_qty)
                    ch_price_list.append(buy_price)
    
                    # continue to next sell order child
                # end of for j in range(len(sell_chx_df)):    
    
                ch_sum_qty = round(sum(ch_qty_list), _q_)                                                  
                if sum(ch_price_list) > 0:
                    ch_mean_price = round(statistics.mean(ch_price_list), _p_)  
                else:
                    ch_mean_price = 0    
    
                sub_qty_list.append(ch_sum_qty)
                sub_price_list.append(ch_mean_price)                             
    
                #####################################################################################
                ### 【6】UPDATE SELL ORDER SUB (buy_order_id, buy_time)
                #####################################################################################          
    
                buy_order_ids_by_semicolon = ''    # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'
                for k, buy_order_id_x in enumerate(ch_buy_order_id_list):
                    if k == 0:
                        buy_order_ids_by_semicolon += buy_order_id_x
                    else:
                        buy_order_ids_by_semicolon += ';' + buy_order_id_x    
    
                ### update sell order sub (buy_order_id, buy_time) 
                find_mask = (sell_sub_df['sell_time'] == sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
                dfx = sell_sub_df[find_mask]
                # not found sell order sub ?
                if dfx.shape[0] == 0:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}create_buy_order{Bcolors.ENDC}(): {Bcolors.FAIL} sell order sub not found ▶ {sub_sell_order_id=} {Bcolors.ENDC} ")
                else: # found sell order sub => update buy_order_id, buy_time 
                    sell_sub_df.loc[find_mask, 'buy_order_id'] = buy_order_ids_by_semicolon # 'BuyOrderID1;BuyOrderID2;BuyOrderID3'  
                    sell_sub_df.loc[find_mask, 'buy_time'] = buy_time
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {buy_order_ids_by_semicolon=}, {buy_time=:%Y-%m-%d %H:%M:%S}) ")  
                # end of if dfx.shape[0] == 0:  
    
                # continue to next sell order sub           
            # end of for i in range(len(sell_subx_df)):          
    
            #################################################################################
            ### 【7】SAVE SELL ORDER SUB TO CSV FILE
            #################################################################################
    
            ### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv     
            csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
            sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                                     
              
            ################################################################################
            ### 【8】ADD A BUY ORDER HEADER
            ################################################################################            
    
            ### update buy order (real_qty, real_price, real_fee) ★
            sub_sum_qty = round(sum(sub_qty_list), _q_)                                       
            
            if sum(sub_price_list) > 0:
                sub_mean_price = round(statistics.mean(sub_price_list), _p_)  
            else:
                sub_mean_price = 0    
    
            # write_buy_order(buy_time: utcnow, sell_time: utcnow, qty: float, price: float, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:
            self.csv.write_buy_order(buy_time, sell_time, sub_sum_qty, sub_mean_price)  
    
            ################################################################################
            ### 【9】UPDATE A SELL ORDER HEADER (buy_time)
            ################################################################################          
            # update_sell_order(sell_time: utcnow, col_name_list: list, col_value_list: list) -> object:  # PK(sell_time)
            self.csv.update_sell_order(sell_time, col_name_list=['buy_time'], col_value_list=[buy_time])    
    
            self.debug.trace_write(f".... {self.gvar.callers_method_name} create_buy_order() ▶ {status=} normal return... ")
    
            return status             
        
        ###############################
        def update_order(self) -> None:
            """
            
            """
            ########################################################################
            ### 【1】UPDATE CLOSED SELL ORDER 
            ########################################################################
            ### update closed sell order
            self.update_closed_order()   
    
            ########################################################################
            ### 【2】UPDATE ORDER PROFIT (PART) : first_pass(True)
            ########################################################################
            ### update order profit 
            self.update_order_profit(first_pass=True)           
    
            ########################################################################
            ### 【3】CHECK STOP LOSS : gvar.stoploss_method_id : 0, 1,...
            ########################################################################
            ### check latest price(GMO) and stop loss price 
            if self.gvar.stop_loss:    
                self.stop_loss(self.gvar.stoploss_method_id)    # 0, 1,...  
           
            ########################################################################
            ### 【4】CANCEL PENDING ORDERS OR CLOSE PENDING ORDERS 
            ########################################################################
            ### cancel pending orders      
            # cancel pending order ? => call cancel_pending_orders() method       
            if self.gvar.cancel_pending_orders:         
                self.cancel_pending_order()            
            elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)  
                self.close_pending_order()                        
    
            ########################################################################
            ### 【5】 UPDATE ORDER PROFIT (PART2) : first_pass(False)
            ########################################################################
            ### update the order file ( order(BTC).csv )
            if self.gvar.stop_loss or self.gvar.cancel_pending_orders or self.gvar.close_pending_orders:
                self.update_order_profit(first_pass=False)  
    
            # if self.gvar.stop_loss:
            #     self.update_order_profit(first_pass=False)  
            # elif self.coin.cancel_pending_orders: # cancel pending order by coin (one time request)            
            #     self.update_order_profit(first_pass=False)    
            # elif self.gvar.close_pending_orders: # close pending orders (current time is between 05:00AM - 06:00AM)      
            #     self.update_order_profit(first_pass=False)
    
            return
            
        ###################################### 
        def update_closed_order(self) -> None:
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal  
            self.gvar.callers_method_name = 'update_closed_order():'
            order_completed = self.trade.get_order_status(exclude=False)   # include stop_loss, cancelled, closed order info        
    
            df = self.coin.master2_df      
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
            self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), cancel({self.gvar.cancel_pending_orders}) ")       
    
            # mas_df is empty ? => SYTEM ERROR
            if mas_df.shape[0] == 0: 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
                self.debug.sound_alert(3)                  
                return 
          
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)       
    
            ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            # get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
            sell_bk, buy_bk = self.api.get_orderbooks_try()
            sleep(0.5)
            sell_bk_df = pd.DataFrame(sell_bk)  # ascending order of price      : low to high price 100, 110, 120, 130, 140, 150
            buy_bk_df = pd.DataFrame(buy_bk)    # descending order of price     : high to low price 150, 140, 130, 120, 110, 100
            
            # not empty order books ?
            if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:              
                ### get sell_close_order    
                # get latest sell/buy price from the order books
                bk_sell_price = sell_bk_df.iloc[0]['price']     # get lowest sell price 100
                bk_buy_price = buy_bk_df.iloc[0]['price']       # get highest buy price 150
            else:
                bk_sell_price = 0.0
                bk_buy_price = 0.0            
    
            # incomplet buy order (pending buy order) ?
            if not order_completed:
                ### print GMO orderbooks info (sell price, buy price)
    
                # # get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
                # sell_bk, buy_bk = self.api.get_orderbooks_try()
                # sleep(0.5)
                # sell_bk_df = pd.DataFrame(sell_bk)  # ascending order of price
                # buy_bk_df = pd.DataFrame(buy_bk)    # descending order of price
                
                # not empty order books ?
                if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:              
                    ### get sell_close_order    
    
                    # get latest sell/buy price from the order books
                    bk_sell_price = sell_bk_df.iloc[0]['price']     # get lowest sell price 100
                    bk_buy_price = buy_bk_df.iloc[0]['price']       # get highest buy price 150
    
                    # get the last buy price from the buy order sub
                    last_buy_price = buy_sub_df.iloc[-1]['price']   # 120
    
                    # get buy position number (sell => buy)
                    buy_bk_df.sort_values('price', ascending=True, inplace=True)    # sort in ascending order of price ★   
                    filter_mask = buy_bk_df['price'] > last_buy_price
                    dfx = buy_bk_df[filter_mask]
                    buy_position = dfx.shape[0]
    
                    # update buy position count
                    if buy_position >= 91:
                        self.coin.position_count_91_999 += 1
                    elif buy_position >= 71:
                        self.coin.position_count_71_90 += 1
                    elif buy_position >= 51:
                        self.coin.position_count_51_70 += 1
                    elif buy_position >= 31:
                        self.coin.position_count_31_50 += 1
                    elif buy_position >= 11:
                        self.coin.position_count_11_30 += 1
                    else:
                        self.coin.position_count_0_10 += 1               
    
                    self.coin.buy_position_list.append(buy_position)  # append buy_position : coin.buy_position=[10, 8, 5, 3, 1, 0]
                    self.debug.trace_write(f".... {self.gvar.callers_method_name}{Bcolors.WARNING} {buy_position=}{Bcolors.ENDC} => close condition [{last_buy_price=:,.{_p_}f} >= {bk_sell_price=:,.{_p_}f}] ")    # 120 >= 100                 
                  
                    reverse_buy_position_list = self.coin.buy_position_list[::-1]   # [0, 1, 3, 5, 8, 10]
                    if len(reverse_buy_position_list) < 21:
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list=} ")   
                    else:
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {reverse_buy_position_list[0:21]=} ")    
                else: # empty order books
                    self.coin.buy_position_list = []   # reset buy position list
            # end of if not order_completed:
                 
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER 
            #################################################
         
            ### get all buy order header
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']   # unique time                  
                sell_time      = buy_df.loc[i, 'sell_time']  # unique time                        
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True        
                     
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        dfx.reset_index(inplace=True)                
                        buy_order_id = str(dfx.loc[0, 'buy_order_id'])    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")                        
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    continue    
                # end of if buy_order_closed:    
    
                ### open or incomplete buy order header
    
                ##########################################################################################
                ### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
                ##########################################################################################
    
                ### for buy order sub : use pandas dataframe groupby()
        
                buy_order_sub_seq = 0
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    ### get buy order sub info
                    sub_buy_time       = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time      = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id   = buy_subx_df.loc[j, 'buy_order_id']       # B9999-999999-999999 ★ 
                    sub_sell_order_id  = buy_subx_df.loc[j, 'sell_order_id']      # S9999-999999-999999 ★ 
                    sub_position_id    = buy_subx_df.loc[j, 'position_id']        # (unique id)
                    sub_buy_qty        = buy_subx_df.loc[j, 'qty']               
                    sub_buy_price      = buy_subx_df.loc[j, 'price']            
                    sub_buy_real_qty   = buy_subx_df.loc[j, 'real_qty']          
                    sub_buy_real_price = buy_subx_df.loc[j, 'real_price']            
                    sub_buy_real_fee   = buy_subx_df.loc[j, 'real_fee']  
                    sub_buy_stop_loss  = buy_subx_df.loc[j, 'stop_loss'] 
                    sub_buy_cancelled  = buy_subx_df.loc[j, 'cancelled']        
    
                    sub_buy_order_open = False
                    sub_buy_order_closed = False
                    sub_buy_order_incomplete = False
    
                    if sub_buy_real_qty == 0:
                        sub_buy_order_open = True
                    elif sub_buy_qty == sub_buy_real_qty:
                        sub_buy_order_closed = True
                    else:
                        sub_buy_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP
                    if sub_buy_order_id.startswith('Fake'): 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ") 
                        continue             
                        
                    # cancelled buy order sub ? => SKIP
                    if sub_buy_cancelled:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")  
                        continue
    
                    # stop loss buy order sub ? => SKIP
                    if sub_buy_stop_loss:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")  
                        continue            
    
                    # closed buy order sub ? => SKIP
                    if sub_buy_order_closed:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")  
                        continue 
    
                    ### open or incomplete buy order sub
    
                    ############################################################
                    ### 【3】UPDATE BUY ORDER SUB (get_price_count+1)
                    ############################################################
    
                    ### update buy order sub (get_price_count += 1)  ★         
                    # DO NOT USE: self.csv.update_buy_order_sub_count(buy_time, buy_order_id, symbol)        
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub_count({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub       
                        buy_sub_df.loc[find_mask, 'get_price_count'] += 1   # increment get_price_count by 1
                        ix = dfx.index[0]
                        sub_buy_get_price_count = buy_sub_df.loc[ix, 'get_price_count']
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub_count({sub_buy_order_id=}, {sub_buy_get_price_count=}) ")   
                    # end of if dfx.shape[0] == 0:                
    
                    buy_order_sub_seq = j + 1 # 1, 2, 3,....
    
                    ### get order_status
                    qty = sub_buy_qty           
                    price = sub_buy_price       
    
                    # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)
                    status, order_status = self.api.get_order_status(sub_buy_order_id, qty, price)  # ★ qty, price are used for fake mode only  
                    # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
    
                    ### status = 0; order_status = 'EXPIRED'    # DEBUG DEBUG DEBUG
    
                    # get_order_status() error ?
                    if status != 0:   
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")                               
                        continue    # continue to next buy order sub                       
                   
                    ## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
    
                    # pending exe_buy_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
                    if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':                      
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")                         
                        continue    # continue to next buy order sub                  
    
                    ############################################# DEBUG DEBUG DEBUG
                    if self.coin.debug_mode_gmo_stop_loss:
                        order_status = 'EXPIRED'        
                    ############################################# DEBUG DEBUG DEBUG
            
                    ### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'                    
    
                    if order_status in 'EXPIRED,CANCELED':                      
                        ### update buy order, buy order sub and order csv file
                        if order_status == 'CANCELED':
                             self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")   
                             self.debug.sound_alert(3)
                             continue    # continue to next buy order sub 
    
                        if order_status == 'EXPIRED':     
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")   
                            self.debug.sound_alert(3)
    
                            ###########################################    
                            ### 1: close buy order sub
                            ###########################################
    
                            # update a buy order sub (update real_qty, real_price, stop_loss)                                              
                            # DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list) 
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # not found buy order sub ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")                           
                            else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss) 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_stop_loss = True
                                buy_sub_df.loc[find_mask, 'real_qty']   = sub_buy_qty  
                                buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_sub_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee
                                buy_sub_df.loc[find_mask, 'stop_loss'] = buy_stop_loss          # buy order stop loss
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")   
                            # end of if dfx.shape[0] == 0:  
    
                            #######################################
                            ### 2: close buy order    
                            #######################################
    
                            # update buy order  (real_qty, real_price, real_fee) 
                            find_mask = buy_df['buy_time'] == buy_time
                            dfx = buy_df[find_mask]
                            # not found buy order ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                            else: # found buy order 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_df.loc[find_mask, 'real_qty']   = buy_qty                 
                                buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee 
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}) ")                                                    
                            # end of if dfx.shape[0] == 0:                                                          
    
                            ###########################################
                            ### 3: update order csv (update stop_loss)    
                            ###########################################   
                                    
                            ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
                            ord_df = self.csv.get_order_csv()                 
                            if ord_df.shape[0] > 0:
                                # find the order csv by sell_order_id        
                                find_mask = ord_df['sell_order_id'] == sub_sell_order_id
                                dfx = ord_df[find_mask]
                                if dfx.shape[0] > 0:      
                                    # update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
                                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])                            
    
                            continue    # continue to next buy order sub                  
                        # end of if order_status == 'EXPIRED':         
                    # end of if order_status in 'EXPIRED,CANCELED':   
    
                    ### get lowest sell price from sell orderbooks
                    latest_book_price = bk_sell_price    # bk_sell_price = sell_bk_df.iloc[0]['price']   
    
                    # ### get real qty, sell price, fee from the GMO         
                    # ### get the latest close price from the GMO master                                     
                    # ### mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO) => usef for fake mode                             
    
                    _debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2 
    
                    ######################################################################################### DEBUG(1), DEBUG(2)  
                    # _debug1_ = True; _debug2_ = True    # ★ TEMP TEMP TEMP ★   
                    # self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")                                       
                    ######################################################################################### DEBUG(1), DEBUG(2)  
    
                    ### get buy real qty, price & fee from the GMO : get_price() for exe_buy_close_order_limit() ★                            
                    
                    sleep(1)    # sleep 1 sec
                    # get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple: 
                    status, res_df, msg_code, msg_str = self.api.get_price_try(sub_buy_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_)  # ★ qty, price, close_price are used for fake mode           
                    # BUG status, res_df, msg_code, msg_str = self.api.get_price_try(sub_sell_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_)  # ★ qty, price, close_price are used for fake mode                                         
                    # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)                
                    sleep(1)    # sleep 1 sec     
    
                    res_df = pd.DataFrame(res_df)   # cast dataframe
    
                    # not real mode and debug (stop loss or cancel pending orders) ?
                    ######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug1: 
                            if res_df.shape[0] > 0:
                                # ★ create two sell order subs for each sell order header ★
                                # update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
                                res_df = self.trade.update_get_price_response_for_debug1_2(res_df, buy_order_sub_seq)  # 1, 2, 3, ...
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_order_sub_seq=}) ▶ {res_df.shape=} ")  
                    ######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()        
    
    
                    ####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug2: 
                            # ★ create outstanding buy order sub depending on get_price_count ★
                            # get buy 'get_price_count'
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # found buy order sub ?
                            if dfx.shape[0] > 0:
                                ix = dfx.index[0]                                    
                                get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_sub_df.loc[find_mask, 'get_price_count'].values[0]   # 0,1,2,3,4,5,...
                                # update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
                                res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)                                
                                if res_df.shape[0] == 0:
                                    status = 9                          # set internal status code
                                    msg_code = 'ERR-999'                # null => ERR-999
                                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty  
                                else:
                                    status = 0
                                    msg_code = 'OK'
                                    msg_str = 'Normal Return'                                                            
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")                                                 
                    ###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()              
    
                    # get_price() error ?
                    if status != 0:     
                        if status == 9: # pending exe_buy_close_order_limit() request ?                     
                            # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ") 
                            status = 0  # reset status code
                            continue    # continue to next buy order sub                  
                        else: # status == 1 'ERR-5008' Timestamp for this request is too late
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")                               
                            continue    # continue to next buy order sub 
                        # end of if status == 9: 
                    else:   # normal return => executed exe_buy_close_order_limit() request                
                        pass    # self.debug.sound_alert(1) 
                    # end of if status != 0: 
    
                    ### no error : res_df.shape[0] >= 1 (single or multiple rows)  
      
                    ####################################################################
                    ### 【4】UPDATE A BUY ORDER SUB (real_qty, real_price, real_fee) 
                    ####################################################################
    
                    # res_df = get_price() response for exe_buy_close_order_limit()         
                    # ----------------------------------------------------------------  
                    #  #   Column       Dtype              
                    # ----------------------------------------------------------------              
                    #  0   executionId  object             
                    #  1   fee          float64            
                    #  2   lossGain     float64            
                    #  3   orderId      object  (non unique id => same id)
                    #  4   positionId   object  (unique id)                     
                    #  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]
                    # ----------------------------------------------------------------                  
    
                    ### update a buy order sub ★             
                    sub_sum_real_qty  = round(res_df.groupby('orderId')['size'].sum().values[0], _q_)       # round(999.12, _q_)
                    sub_sum_real_fee  = round(res_df.groupby('orderId')['fee'].sum().values[0], 0)          # zero(0) 
                    sub_mean_real_price = round(res_df.groupby('orderId')['price'].mean().values[0], _p_)   # round(999999.123, _p_)                           
    
                    # DO NOT USED: self.csv.update_buy_order_sub(buy_time, sub_order_id, col_name_list, col_value_list) 
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee) 
                        buy_sub_df.loc[find_mask, 'real_qty']   = sub_sum_real_qty  
                        buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price 
                        buy_sub_df.loc[find_mask, 'real_fee']   = sub_sum_real_fee 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, real_qty={sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={sub_sum_real_fee:,.0f}) ")   
                    # end of if dfx.shape[0] == 0:  
    
                    ##############################################################################################################################
                    ### 【5】ADD A BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
                    ##############################################################################################################################
    
                    ### check exit condition : sub_buy_qty=sub_buy_qty, sub_sum_real_qty=sum(res_df['size'])          
    
                    ########################################### TEMP TEMP TEMP
                    # temp_buy_qty = round(sub_buy_qty, _q_)
                    # temp_real_qty = round(sub_sum_real_qty, _q_)
                    # self.debug.trace_warn(f".... DEBUG: {temp_buy_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
                    ########################################## TEMP TEMP TEMP                  
    
                    # completed exe_buy_close_order_limit() request ?
                    if round(sub_buy_qty, _q_) == round(sub_sum_real_qty, _q_):                                                
                        # changed open => closed  
                        if self.coin.real_mode:                                    
                            self.debug.sound_alert(1)
    
                        # increment buy count
                        self.gvar.buy_count += 1  
                        self.coin.buy_count += 1        
           
                        # response df for get_price(): exe_buy_close_order_limit()
                        for k in range(len(res_df)):
                            buy_order_id = res_df.loc[k, 'orderId']      # buy order id (unique id)
                            position_id  = res_df.loc[k, 'positionId']   # position id (non unique id => same id)
                            real_qty     = res_df.loc[k, 'size']         # real qty
                            real_price   = res_df.loc[k, 'price']        # real price
                            real_fee     = res_df.loc[k, 'fee']          # real fee         
    
                            if not self.coin.real_mode:
                                position_id = sub_position_id                        
            
                            ### write a buy order child ★
                            # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:              
                            _qty_ = real_qty; _price_ = real_price
                            self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)    
                        # end of for k in range(len(res_df)):                                                                                                                                                                                                              
                    else:   # outstanding buy_qty exists => incomplete status  
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} incomplete buy order sub({sub_buy_order_id=}) ▶ buy_qty={sub_buy_qty:,.{_q_}f}, buy_real_qty={sub_sum_real_qty:,.{_q_}f} ")    
                        # pass    # skip (do nothing)    
                    # end of if round(sub_buy_qty, digits) == round(sub_sum_real_qty, digits): 
    
                    # continue to next buy order sub
                # end of for j in range(len(buy_subx_df)):
    
                #################################################################################
                ### 【6】SAVE BUY/SELL ORDERS SUB TO CSV FILES
                #################################################################################
    
                ### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/buy_order_sub(BTC_JPY).csv  
                buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode   
                                        
                ### CLOSE (save the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
                sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode              
    
                #################################################################################
                ### 【7】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
                #################################################################################     
    
                ### calculate total buy qty / fee and mean price ★ buy_time ★
                filter_mask = buy_sub_df['buy_time'] == buy_time
                dfx = buy_sub_df[filter_mask]    
                # not found buy order sub ?
                if dfx.shape[0] == 0:
                    self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order sub({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                else: # found buy order sub                         
                    header_sum_real_qty = dfx.groupby('buy_time')['real_qty'].sum().values[0]    
                    header_sum_real_fee = dfx.groupby('buy_time')['real_fee'].sum().values[0] 
                    header_mean_real_price = dfx.groupby('buy_time')['real_price'].mean().values[0]                      
    
                    header_sum_real_qty = round(header_sum_real_qty, _q_)       # round(990.12, _q_)     
                    header_sum_real_fee = round(header_sum_real_fee, 0)         # zero(0)
                    header_mean_real_price = round(header_mean_real_price, _p_) # round(999999.123, _p_)          
    
                    ### update buy order (real_qty, real_price, real_fee) ★
                    find_mask = buy_df['buy_time'] == buy_time
                    dfx = buy_df[find_mask]
                    # not found buy order ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order 
                        buy_df.loc[find_mask, 'real_qty']   = header_sum_real_qty                  
                        buy_df.loc[find_mask, 'real_price'] = header_mean_real_price 
                        buy_df.loc[find_mask, 'real_fee']   = header_sum_real_fee 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={header_sum_real_fee:,.0f}) ")                                                    
                    # end of if dfx.shape[0] == 0:              
                # end of if dfx.shape[0] == 0:    
    
                # continue to next buy order... ★
            # end of for i in range(len(buy_df)):
    
            #################################################################################
            ### 【8】SAVE BUY/SELL ORDER HEADER TO CSV FILES 
            #################################################################################        
    
            ### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv         
            csv_file = self.folder_trading_type + f'buy_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/buy_order(BTC_JPY).csv  
            buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode  
    
            ### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv      
            csv_file = self.folder_trading_type + f'sell_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order(BTC_JPY).csv   
            sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode        
                    
            return
    
        ################################################ 
        def update_partially_closed_order(self) -> None:
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal  
            self.gvar.callers_method_name = ':: update_partially_closed_order():'
            order_completed = self.trade.get_order_status(exclude=False)   # include stop_loss, cancelled, closed order info        
    
            df = self.coin.master2_df      
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
            self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}last order completed({order_completed}){Bcolors.ENDC}, mas_df.shape({mas_df.shape[0]}), cancel({self.gvar.cancel_pending_orders}) ")       
    
            # mas_df is empty ? => SYTEM ERROR
            if mas_df.shape[0] == 0: 
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - mas_df({mas_df.shape[0]}) is empty. skip update_order() ")
                self.debug.sound_alert(3)                  
                return 
          
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)       
    
            # ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            # sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            # ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            # sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            # get_orderbooks_try() -> tuple:  # return (sell_df, buy_df) 
            sell_bk, buy_bk = self.api.get_orderbooks_try()
            sleep(0.5)
            sell_bk_df = pd.DataFrame(sell_bk)  # ascending order of price      : low to high price 100, 110, 120, 130, 140, 150
            buy_bk_df = pd.DataFrame(buy_bk)    # descending order of price     : high to low price 150, 140, 130, 120, 110, 100
            
            # not empty order books ?
            if sell_bk_df.shape[0] > 0 and buy_bk_df.shape[0] > 0:              
                ### get sell_close_order    
                # get latest sell/buy price from the order books
                bk_sell_price = sell_bk_df.iloc[0]['price']     # get lowest sell price 100
                bk_buy_price = buy_bk_df.iloc[0]['price']       # get highest buy price 150
            else:
                bk_sell_price = 0.0
                bk_buy_price = 0.0            
                 
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER 
            #################################################
         
            ### get all buy order header
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']   # unique time                  
                sell_time      = buy_df.loc[i, 'sell_time']  # unique time                        
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True        
                     
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        dfx.reset_index(inplace=True)                
                        buy_order_id = str(dfx.loc[0, 'buy_order_id'])    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id})... ")                        
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    continue    
                # end of if buy_order_closed:    
    
                ### open or incomplete buy order header
    
                ##########################################################################################
                ### 【2】READ ALL BUY ORDER SUB & EXECUTE GET_PRICE() FOR EACH BUY ORDER SUB
                ##########################################################################################
    
                ### for buy order sub : use pandas dataframe groupby()
        
                buy_order_sub_seq = 0
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    buy_order_sub_seq = j + 1 # 1, 2, 3,....
    
                    ### get buy order sub info
                    sub_buy_time       = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time      = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id   = buy_subx_df.loc[j, 'buy_order_id']       # B9999-999999-999999 ★ 
                    sub_sell_order_id  = buy_subx_df.loc[j, 'sell_order_id']      # S9999-999999-999999 ★ 
                    sub_position_id    = buy_subx_df.loc[j, 'position_id']        # (unique id)
                    sub_buy_qty        = buy_subx_df.loc[j, 'qty']               
                    sub_buy_price      = buy_subx_df.loc[j, 'price']            
                    sub_buy_real_qty   = buy_subx_df.loc[j, 'real_qty']          
                    sub_buy_real_price = buy_subx_df.loc[j, 'real_price']            
                    sub_buy_real_fee   = buy_subx_df.loc[j, 'real_fee']  
                    sub_buy_stop_loss  = buy_subx_df.loc[j, 'stop_loss'] 
                    sub_buy_cancelled  = buy_subx_df.loc[j, 'cancelled']        
    
                    sub_buy_order_open = False
                    sub_buy_order_closed = False
                    sub_buy_order_incomplete = False
    
                    if sub_buy_real_qty == 0:
                        sub_buy_order_open = True
                    elif sub_buy_qty == sub_buy_real_qty:
                        sub_buy_order_closed = True
                    else:
                        sub_buy_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP
                    if sub_buy_order_id.startswith('Fake'): 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ") 
                        continue             
                        
                    # cancelled buy order sub ? => SKIP
                    if sub_buy_cancelled:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been cancelled ▶ {sub_buy_cancelled=} ")  
                        continue
    
                    # stop loss buy order sub ? => SKIP
                    if sub_buy_stop_loss:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been stopped loss ▶ {sub_buy_stop_loss=} ")  
                        continue            
    
                    # closed buy order sub ? => SKIP
                    if sub_buy_order_closed:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed ▶ {sub_buy_order_closed=} ")  
                        continue 
    
                    ### open or incomplete buy order sub
    
                    ############################################################
                    ### 【3】GET ORDER STATUS
                    ############################################################
                    
                    ### get order_status
                    qty = sub_buy_qty           
                    price = sub_buy_price       
    
                    # get_order_status_try(order_id: str, qty: float, price: float) ->tuple:    # return (status, order_status)
                    status, order_status = self.api.get_order_status(sub_buy_order_id, qty, price)  # ★ qty, price are used for fake mode only  
                    # status: 0(ok), 4(invalid request), 8(retry error), 9(internal error), ?(misc error) 
    
                    ### status = 0; order_status = 'EXPIRED'    # DEBUG DEBUG DEBUG
    
                    # get_order_status() error ?
                    if status != 0:   
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}) error ▶ {Bcolors.FAIL} {status=}, {order_status=} {Bcolors.ENDC} ")                               
                        continue    # continue to next buy order sub                       
                   
                    ## get_order_status() : status == 0, order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED,EXECUTED,EXPIRED,CANCELED'
    
                    # pending exe_buy_close_order_limit() request ? : ORDERED(指値注文有効中), EXECUTE(全量約定)
                    if order_status in 'WAITING,MODIFYING,CANCELLING,ORDERED':                      
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_order_status({sub_buy_order_id=}, {qty=:,.{_q_}f}, {price=:,.{_p_}f}){Bcolors.ENDC} ▶ {order_status=} pending exe_buy_close_order_limit() request ")                         
                        continue    # continue to next buy order sub                  
            
                    ############################################# DEBUG DEBUG DEBUG
                    if self.coin.debug_mode_gmo_stop_loss:
                        order_status = 'EXPIRED'        
                    ############################################# DEBUG DEBUG DEBUG
    
                    ### executed exe_buy_close_order_limit() : order_status in 'EXECUTED(全量約定),EXPIRED(全量失効|一部失効),CANCELED'                    
    
                    if order_status in 'EXPIRED,CANCELED':                      
                        ### update buy order, buy order sub and order csv file
                        if order_status == 'CANCELED':
                             self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been cancelled by GMO. please confirm the reason{Bcolors.ENDC} ")   
                             self.debug.sound_alert(3)
                             continue    # continue to next buy order sub 
    
                        if order_status == 'EXPIRED':     
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_order_status({sub_buy_order_id=}): {Bcolors.FAIL}the order has been expired due to loss cut by GMO{Bcolors.ENDC} ")   
                            self.debug.sound_alert(3)
                          
                            ###########################################    
                            ### 1: close buy order sub
                            ###########################################                        
    
                            # update a buy order sub (update real_qty, real_price, stop_loss)                                              
                            # DO NOT USE: self.csv.update_buy_order_sub(buy_time, sub_order_id, symbol, col_name_list, col_value_list) 
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # not found buy order sub ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found: {dfx.shape=}{Bcolors.ENDC} ")   
                            else: # found buy order sub => update buy order sub (real_qty, real_price, real_fee, stop_loss) 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_stop_loss = True
                                buy_sub_df.loc[find_mask, 'real_qty']   = sub_buy_qty  
                                buy_sub_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_sub_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee
                                buy_sub_df.loc[find_mask, 'stop_loss']  = buy_stop_loss         # buy order stop loss
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, {sub_buy_qty=:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}, {buy_stop_loss=}) ")   
                            # end of if dfx.shape[0] == 0:  
    
                            #######################################
                            ### 2: close buy order    
                            #######################################
    
                            # update buy order  (real_qty, real_price, real_fee) 
                            find_mask = buy_df['buy_time'] == buy_time
                            dfx = buy_df[find_mask]
                            # not found buy order ?
                            if dfx.shape[0] == 0:
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time:%Y-%m-%d %H:%M:%S}) not found: {dfx.shape=}{Bcolors.ENDC} ")   
                            else: # found buy order 
                                gmo_loss_cut_price = 9_999_999.0
                                gmo_loss_cut_fee = 999.0
                                buy_df.loc[find_mask, 'real_qty']   = buy_qty                 
                                buy_df.loc[find_mask, 'real_price'] = gmo_loss_cut_price    # gmo loss cut rate 
                                buy_df.loc[find_mask, 'real_fee']   = gmo_loss_cut_fee      # gmo loss cut fee 
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={buy_qty:,.{_q_}f}, {gmo_loss_cut_price=:,.{_p_}f}, {gmo_loss_cut_fee=:,.0f}) ")                                                    
                             # end of if dfx.shape[0] == 0:                                                      
                            
                            ###########################################
                            ### 3: update order csv (update stop_loss)    
                            ###########################################   
                                    
                            ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
                            ord_df = self.csv.get_order_csv()                 
                            if ord_df.shape[0] > 0:
                                # find the order csv by sell_order_id        
                                find_mask = ord_df['sell_order_id'] == sub_sell_order_id
                                dfx = ord_df[find_mask]
                                if dfx.shape[0] > 0:      
                                    # update_order_csv_sell(sell_time: utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None: # PK(sell_time + sell_order_id)
                                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])                            
    
                            continue    # continue to next buy order sub                  
                        # end of if order_status == 'EXPIRED':         
                    # end of if order_status in 'EXPIRED,CANCELED':   
    
                    ### get lowest sell price from sell orderbooks
                    latest_book_price = bk_sell_price    # bk_sell_price = sell_bk_df.iloc[0]['price']   
    
                    ##########################################################################################
                    ### 【4】GET ORDER PRICE
                    ##########################################################################################                
    
                    # ### get real qty, sell price, fee from the GMO         
                    # ### get the latest close price from the GMO master                                     
                    # ### mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO) => usef for fake mode                             
    
                    _debug1_ = self.coin.debug_mode_debug1; _debug2_ = self.coin.debug_mode_debug2 
    
                    ######################################################################################### DEBUG(1), DEBUG(2)  
                    # _debug1_ = True; _debug2_ = True    # ★ TEMP TEMP TEMP ★   
                    # self.debug.trace_warn(f".... DEBUG _debug1_ = True; _debug2_ = True ")                                       
                    ######################################################################################### DEBUG(1), DEBUG(2)  
    
                    ### get buy real qty, price & fee from the GMO : get_price() for exe_buy_close_order_limit() ★                            
                    
                    sleep(1)    # sleep 1 sec
                    # get_price_try(order_id: str, qty: float, price: float, latest_book_price=0.0, debug1=False, debug2=False) -> tuple: 
                    status, res_df, msg_code, msg_str = self.api.get_price_try(sub_buy_order_id, sub_buy_qty, sub_buy_price, latest_book_price, _debug1_, _debug2_)  # ★ qty, price, close_price are used for fake mode                  
                    # status: 0(ok),  8(retry error), 9(empty dataframe or internal error), ?(misc error)                
                    sleep(1)    # sleep 1 sec     
    
                    res_df = pd.DataFrame(res_df)   # cast dataframe
    
                    # not real mode and debug (stop loss or cancel pending orders) ?
                    ######################################################################################### DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug1: 
                            if res_df.shape[0] > 0:
                                # ★ create two sell order subs for each sell order header ★
                                # update_get_price_response_for_debug1_2(res_df: pd.DataFrame, sell_order_sub_seq: int) -> pd.DataFrame
                                res_df = self.trade.update_get_price_response_for_debug1_2(res_df, buy_order_sub_seq)  # 1, 2, 3, ...
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug1_2({buy_order_sub_seq=}) ▶ {res_df.shape=} ")  
                    ######################################################################################## DEBUG(1_2) DEBUG(1_2) DEBUG(1_2) : update_order()        
    
    
                    ####################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()
                    if not self.coin.real_mode:
                        if self.coin.debug_mode_debug2: 
                            # ★ create outstanding buy order sub depending on get_price_count ★
                            # get buy 'get_price_count'
                            find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)   # PK(buy_time + buy_order_id) 
                            dfx = buy_sub_df[find_mask]
                            # found buy order sub ?
                            if dfx.shape[0] > 0:
                                ix = dfx.index[0]                                    
                                get_price_count = buy_sub_df.loc[ix, 'get_price_count'] # buy_sub_df.loc[find_mask, 'get_price_count'].values[0]   # 0,1,2,3,4,5,...
                                # update_get_price_response_for_debug2(res_df: pd.DataFrame, get_price_count: int) -> pd.DataFrame:
                                res_df = self.trade.update_get_price_response_for_debug2(res_df, get_price_count)                                
                                if res_df.shape[0] == 0:
                                    status = 9                          # set internal status code
                                    msg_code = 'ERR-999'                # null => ERR-999
                                    msg_str = 'DataFrame is empty'      # null => DataFrame is empty  
                                else:
                                    status = 0
                                    msg_code = 'OK'
                                    msg_str = 'Normal Return'                                                            
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} update_get_price_response_for_debug2({get_price_count=}) ▶ {status=}, {res_df.shape=} ")                                                 
                    ###################################################################################### DEBUG(2) DEBUG(2) DEBUG(2) : update_order()              
    
                    # get_price() error ?
                    if status != 0:     
                        if status == 9: # pending exe_buy_close_order_limit() request ?                     
                            # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.WARNING}get_price({sub_buy_price:,.{_p_}f}){Bcolors.ENDC} ▶ {status=} pending exe_buy_close_order_limit() request: continue to next buy order sub... ") 
                            status = 0  # reset status code
                            continue    # continue to next buy order sub                  
                        else: # status == 1 'ERR-5008' Timestamp for this request is too late
                            self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} get_price({sub_buy_price:,.{_p_}f}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str} {Bcolors.ENDC} ")                               
                            continue    # continue to next buy order sub 
                        # end of if status == 9: 
                    else:   # normal return => executed exe_buy_close_order_limit() request                
                        pass    # self.debug.sound_alert(1) 
                    # end of if status != 0: 
    
                    ### no error : res_df.shape[0] >= 1 (single or multiple rows)     
    
                    ##############################################################################################################################
                    ### 【5】ADD A BUY ORDER CHILD (buy_time, buy_order_id, real_qty, real_price, real_fee) + (sell_time, sell_order_id)
                    ##############################################################################################################################
    
                    # response df for get_price(): exe_buy_close_order_limit()
                    for k in range(len(res_df)):
                        buy_order_id = res_df.loc[k, 'orderId']      # buy order id (unique id)
                        position_id  = res_df.loc[k, 'positionId']   # position id (non unique id => same id)
                        real_qty     = res_df.loc[k, 'size']         # real qty
                        real_price   = res_df.loc[k, 'price']        # real price
                        real_fee     = res_df.loc[k, 'fee']          # real fee         
    
                        if not self.coin.real_mode:
                            position_id = sub_position_id                        
        
                        ### write a buy order child ★
                        # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:              
                        _qty_ = real_qty; _price_ = real_price
                        self.csv.write_buy_order_child(sub_buy_time, buy_order_id, sub_sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)    
                    # end of for k in range(len(res_df)):     
    
                    # continue to next buy order sub
                # end of for j in range(len(buy_subx_df)):
       
                # continue to next buy order... ★
            # end of for i in range(len(buy_df)):
                        
            return
    
        ####################################################### 
        def update_order_profit(self, first_pass=True) -> None:
            """
            
            """
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
    
            self.gvar.callers_method_name = 'update_order_profit():'
            order_completed = self.trade.get_order_status(exclude=False)   # include stop_loss, cancelled, closed order info
            self.debug.trace_write(f".... {self.gvar.callers_method_name} first_pass({first_pass}), last order completed({order_completed})  ")   
    
            ###############################################################################################################################################################################
            ### 【1】IMPORT SELL ORDER SUB INFO & APPEND/UPDATE ORDER CSV FILE (sell_time, sell_order_id, sell_real_qty, sell_real_price, sell_real_fee) & (buy_time, buy_order_id)
            ###############################################################################################################################################################################
    
            ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
            ord_df = self.csv.get_order_csv()               
            
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv
            sell_sub_df = self.csv.get_sell_order_sub()         
    
            ### import buy order sub file       
    
            # get all sell order sub
            for i in range(len(sell_sub_df)):
                ### get sell sub order info
                sub_sell_time            = sell_sub_df.loc[i, 'sell_time']           # ★ (non unique)
                sub_buy_time             = sell_sub_df.loc[i, 'buy_time']            # ★ (non unique)
                sub_sell_order_id        = sell_sub_df.loc[i, 'sell_order_id']       # ★ 'S9999-999999-999999' (unique)
                sub_buy_order_id         = sell_sub_df.loc[i, 'buy_order_id']        # ★ 'B9999-999999-999999;B9999-999999-999999;B9999-999999-999999' (list of buy_order_ids) : sell sub(one) <= buy sub(many)
                sub_sell_qty             = sell_sub_df.loc[i, 'qty']
                sub_sell_price           = sell_sub_df.loc[i, 'price']
                sub_sell_real_qty        = sell_sub_df.loc[i, 'real_qty']                
                sub_sell_real_price      = sell_sub_df.loc[i, 'real_price']
                sub_sell_real_fee        = sell_sub_df.loc[i, 'real_fee']
                sub_sell_get_price_count = sell_sub_df.loc[i, 'get_price_count']    
                sub_sell_stop_loss       = sell_sub_df.loc[i, 'stop_loss']
                sub_sell_order_cancelled = sell_sub_df.loc[i, 'cancelled']
                sub_sell_order_closed = True if sub_sell_qty == sub_sell_real_qty else False                  
           
                # fake order sub ? => SKIP
                if sub_sell_order_id.startswith('Fake'): 
                    # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake sell order sub({sub_sell_order_id})... ") 
                    continue             
                    
                # empty order csv ?
                if ord_df.shape[0] == 0:
                    dfx = pd.DataFrame()
                else:   # not empty dataframe => find the order csv by sell_order_id
                    find_mask = ord_df['sell_order_id'] == sub_sell_order_id  # S9999-999999-999999
                    dfx = ord_df[find_mask]
                
                # not found order csv ?
                if dfx.shape[0] == 0:                 
                    ### append order csv ★IMPORTANT★ DO NOT APEND 'Null' Value => buy_order_id = 'B9999-999999-999999'
                    columns = Gmo_dataframe.ORDER_CSV 
                    _buy_real_qty_=0.0; _buy_real_price_=0.0; _buy_real_fee_=0.0; _real_profit_=0.0 
                    _accumulated_leverage_fee_=0.0; _close_count_=0; _close_update_date_=dt.datetime.now()
                    _order_closed_ = False; _stop_loss_=sub_sell_stop_loss; _cancelled_=sub_sell_order_cancelled
                    _order_open_date_ = dt.datetime.now(); _order_close_date_ = dt.datetime.now(); _log_time_=dt.datetime.now()
                    data = [[sub_buy_time, sub_sell_time, sub_buy_order_id, sub_sell_order_id, _buy_real_qty_, sub_sell_real_qty, _buy_real_price_, sub_sell_real_price, _buy_real_fee_, sub_sell_real_fee, _real_profit_, _accumulated_leverage_fee_, _close_count_, _close_update_date_, _order_closed_, _stop_loss_, _cancelled_, _order_open_date_, _order_close_date_, _log_time_]] 
                    new_df = pd.DataFrame(data, columns=columns)                           
    
                    # convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
                    new_df['buy_time']  = pd.to_datetime(new_df['buy_time'], utc=True)          # object => utc time (aware)
                    new_df['sell_time'] = pd.to_datetime(new_df['sell_time'], utc=True)         # object => utc time (aware)   
                    new_df['close_update_date']  = pd.to_datetime(new_df['close_update_date'])  # object => ns time (jp time)   
                    new_df['order_open_date']    = pd.to_datetime(new_df['order_open_date'])    # object => ns time (jp time) 
                    new_df['order_close_date']   = pd.to_datetime(new_df['order_close_date'])   # object => ns time (jp time)                    
                    new_df['log_time']  = pd.to_datetime(new_df['log_time'])                    # object => ns time (jp time)    
    
                    new_df['buy_order_id']  = new_df['buy_order_id'].astype(str)                # int64 => string
                    new_df['sell_order_id'] = new_df['sell_order_id'].astype(str)               # int64 => string  
    
                    # append a new row (order info row)
                    ord_sell_order_id = sub_sell_order_id
                    ord_sell_real_qty = sub_sell_real_qty
                    ord_sell_real_price = sub_sell_real_price
                    ord_sell_real_fee = sub_sell_real_fee
                    ord_order_closed = False                
                    ord_df = ord_df.append(new_df, ignore_index=True)                                      
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} write_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee:,.0f}, {ord_order_closed=}) ")                              
                else: # found order csv => update order csv
                    # get buy_real_qty and sell_real_qty
                    ix = dfx.index[0]   # get index of the first row
                    # ord_buy_order_id  = ord_df.loc[ix, 'buy_order_id']
                    # ord_buy_real_qty  = ord_df.loc[ix, 'buy_real_qty']
                    # ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']               
                    # ord_order_closed = ord_df.loc[ix, 'order_closed'] 
    
                    ord_buy_order_id    = ord_df.loc[ix, 'buy_order_id']
                    ord_sell_order_id   = ord_df.loc[ix, 'sell_order_id']
                    ord_buy_real_qty    = ord_df.loc[ix, 'buy_real_qty']
                    ord_sell_real_qty   = ord_df.loc[ix, 'sell_real_qty']      
                    ord_buy_real_price  = ord_df.loc[ix, 'buy_real_price']
                    ord_sell_real_price = ord_df.loc[ix, 'sell_real_price']      
                    ord_buy_real_fee    = ord_df.loc[ix, 'buy_real_fee']
                    ord_sell_real_fee   = ord_df.loc[ix, 'sell_real_fee']                          
                    ord_order_closed   = ord_df.loc[ix, 'order_closed']                    
                                                    
                    # completed order csv file ?
                    if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):
                       if not ord_order_closed:
                            # update order csv file 
                            ord_order_closed = True
                            ord_df.loc[find_mask, 'order_closed']     = ord_order_closed      
                            ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now() 
                            ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()    
    
                            ord_df['order_close_date']   = pd.to_datetime(ord_df['order_close_date'])  # object => ns time (jp time)                         
                            ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])    # object => ns time (jp time)     
                            self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee=:,.0f}, {ord_order_closed=}) ")                                          
                    else: # incomplete order csv => update order csv                                        
                        # update order csv 
                        ord_sell_time = sub_sell_time
                        ord_sell_order_id = sub_sell_order_id
                        ord_sell_real_qty = sub_sell_real_qty
                        ord_sell_real_price = sub_sell_real_price
                        ord_sell_real_fee = sub_sell_real_fee
    
                        ord_df.loc[find_mask, 'sell_time']       = ord_sell_time      
                        ord_df.loc[find_mask, 'sell_order_id']   = ord_sell_order_id
                        ord_df.loc[find_mask, 'sell_real_qty']   = ord_sell_real_qty                
                        ord_df.loc[find_mask, 'sell_real_price'] = ord_sell_real_price
                        ord_df.loc[find_mask, 'sell_real_fee']   = ord_sell_real_fee 
                        ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()
    
                        ### update order_closed flag
                        
                        # get buy_real_qty and sell_real_qty
                        ix = dfx.index[0]   # get index of the first row
                        # ord_buy_order_id = ord_df.loc[ix, 'buy_order_id']
                        # ord_buy_real_qty = ord_df.loc[ix, 'buy_real_qty']
                        # ord_sell_real_qty = ord_df.loc[ix, 'sell_real_qty']               
                        # order_closed = ord_df.loc[ix, 'order_closed']          
                        if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_):       
                            ord_order_closed = True
                            ord_df.loc[find_mask, 'order_closed'] = ord_order_closed                         
    
                        # convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
                        ord_df['buy_time']  = pd.to_datetime(ord_df['buy_time'], utc=True)      # object => utc time (aware)
                        ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True)     # object => utc time (aware)    
                        ord_df['close_update_date']  = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)   
                        ord_df['order_open_date']    = pd.to_datetime(ord_df['order_open_date'])   # object => ns time (jp time) 
                        ord_df['order_close_date']   = pd.to_datetime(ord_df['order_close_date'])  # object => ns time (jp time)                       
                        ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])                # object => ns time (jp time)    
    
                        ord_df['buy_order_id']  = ord_df['buy_order_id'].astype(str)            # int64 => string
                        ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str)           # int64 => string                   
        
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell({ord_sell_order_id=}, {ord_sell_real_qty=:,.{_q_}f}, {ord_sell_real_price=:,.{_p_}f}, {ord_sell_real_fee=:,.0f}, {ord_order_closed=}) ")                              
                    # end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:                             
                # end of if dfx.shape[0] == 0:
                # continue to next sell order sub
            # end of for i in range(len(sell_sub_df)):	           
    		
            #######################################################################################################################################
            ### 【2】IMPORT BUY ORDER SUB INFO & UPDATE ORDER CSV FILE (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee)
            #######################################################################################################################################
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv
            buy_sub_df = self.csv.get_buy_order_sub()	
    
            ### import buy order sub file      
    
            for i in range(len(buy_sub_df)):
                ### get buy order sub info
                sub_buy_time             = buy_sub_df.loc[i, 'buy_time']         # ★ (non unique)
                sub_sell_time            = buy_sub_df.loc[i, 'sell_time']        # ★ (non unique)
                sub_buy_order_id         = buy_sub_df.loc[i, 'buy_order_id']     # ★ 'B9999-999999-999999' (non unique id) : sell sub(many) => buy_sub(one)
                sub_sell_order_id        = buy_sub_df.loc[i, 'sell_order_id']    # ★ 'S9999-999999-999999' (unique id)
                sub_buy_qty              = buy_sub_df.loc[i, 'qty']
                sub_buy_price            = buy_sub_df.loc[i, 'price']
                sub_buy_real_qty         = buy_sub_df.loc[i, 'real_qty']
                sub_buy_real_price       = buy_sub_df.loc[i, 'real_price']
                sub_buy_real_fee         = buy_sub_df.loc[i, 'real_fee']
                sub_sell_get_price_count = buy_sub_df.loc[i, 'get_price_count']     
                sub_buy_stop_loss        = buy_sub_df.loc[i, 'stop_loss']         
                sub_buy_order_cancelled  = buy_sub_df.loc[i, 'cancelled']
    
                sub_buy_order_open = False
                sub_buy_order_closed = False
                sub_buy_order_incomplete = False
    
                if sub_buy_real_qty == 0:
                    sub_buy_order_open = True
                elif sub_buy_qty == sub_buy_real_qty:
                    sub_buy_order_closed = True
                else:
                    sub_buy_order_incomplete = True       
    
                # fake buy order sub ? => SKIP
                if sub_buy_order_id.startswith('Fake'): 
                    # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id})... ") 
                    continue             
                                             
                # empty order csv ?           
                if ord_df.shape[0] == 0:
                    self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} - order csv is empty: continue... ")
                    self.debug.sound_alert(3)  
                    continue
    
                # find the order csv by sell_order_id        
                find_mask = ord_df['sell_order_id'] == sub_sell_order_id
                dfx = ord_df[find_mask]
                # not found order csv ?
                if dfx.shape[0] == 0:
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}SYSTEM ERROR{Bcolors.ENDC} order csv not found: sell_order_id={sub_sell_order_id} ")  
                    self.debug.sound_alert(3)  
                    continue                
    
                ### found order csv file
                
                # get buy_real_qty and sell_real_qty
                ix = dfx.index[0]   # get index of the first row
                ord_buy_order_id    = ord_df.loc[ix, 'buy_order_id']
                ord_sell_order_id   = ord_df.loc[ix, 'sell_order_id']
                ord_buy_real_qty    = ord_df.loc[ix, 'buy_real_qty']
                ord_sell_real_qty   = ord_df.loc[ix, 'sell_real_qty']      
                ord_buy_real_price  = ord_df.loc[ix, 'buy_real_price']
                ord_sell_real_price = ord_df.loc[ix, 'sell_real_price']      
                ord_buy_real_fee    = ord_df.loc[ix, 'buy_real_fee']
                ord_sell_real_fee   = ord_df.loc[ix, 'sell_real_fee']                          
                ord_order_closed    = ord_df.loc[ix, 'order_closed']                   
                        
                # completed order csv ?
                if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_): 
                    if not ord_order_closed:
                        # update order csv 
                        ord_order_closed = True 
                        ord_df.loc[find_mask, 'order_closed'] = ord_order_closed        
                        ord_df.loc[find_mask, 'order_close_date'] = dt.datetime.now() 
                        ord_df.loc[find_mask, 'log_time'] = dt.datetime.now()    
    
                        ord_df['order_close_date']   = pd.to_datetime(ord_df['order_close_date'])  # object => ns time (jp time)                         
                        ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])    # object => ns time (jp time)          
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")        
                else: # incomplete order csv => update order csv
                    ### get a list of buy_order_id from the buy sub orders ★ sell_order_id ★
                    filter_mask = buy_sub_df['sell_order_id'] == sub_sell_order_id
                    dfx = buy_sub_df[filter_mask]    
                    buy_order_id_list = dfx.groupby('sell_order_id')['buy_order_id'].apply(list)[0]     
                    buy_order_ids_by_semicolon = ''
                    for k, order_id in enumerate(buy_order_id_list):
                        if k == 0:
                            buy_order_ids_by_semicolon += order_id 
                        else:
                            buy_order_ids_by_semicolon += ';' + order_id 
    
                    ### calculate total sell qty / fee and mean price ★ sell_order_id ★
                    buy_sum_real_qty = dfx.groupby('sell_order_id')['real_qty'].sum().values[0]    
                    buy_sum_real_fee = dfx.groupby('sell_order_id')['real_fee'].sum().values[0] 
                    buy_mean_real_price = dfx.groupby('sell_order_id')['real_price'].mean().values[0]                           
    
                    ### update buy csv (buy_time, buy_order_id, buy_real_qty, buy_real_price, buy_real_fee) 
                    ord_buy_real_qty = round(buy_sum_real_qty, _q_)                           # round(999.12, _q_)  
                    ord_buy_real_price = round(buy_mean_real_price, _p_)                      # round(999999.123, _p_)  
                    ord_buy_real_fee = round(buy_sum_real_fee, 0)                             # zero(0)  
    
                    # order completed ?
                    if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and round(ord_buy_real_qty, _q_) == round(ord_sell_real_qty, _q_): 
                        ord_order_closed = True   
                        ord_df.loc[find_mask, 'order_closed'] = ord_order_closed  
    
                    ord_df.loc[find_mask, 'buy_time']       = sub_buy_time    
                    ord_df.loc[find_mask, 'buy_order_id']   = buy_order_ids_by_semicolon        # 'OrderId1;OrderId2;OrderId3'                       
                    ord_df.loc[find_mask, 'buy_real_qty']   = ord_buy_real_qty                  # round(buy_sum_real_qty, _q_)               
                    ord_df.loc[find_mask, 'buy_real_price'] = ord_buy_real_price                # round(buy_mean_real_price, _p_)     
                    ord_df.loc[find_mask, 'buy_real_fee']   = ord_buy_real_fee                  # round(buy_sum_real_fee, 0)           
    
                    # convert datatypes (buy_time, sell_time, log_time, buy_order_id, sell_order_id)
                    ord_df['buy_time']  = pd.to_datetime(ord_df['buy_time'], utc=True)          # object => utc time (aware)
                    ord_df['sell_time'] = pd.to_datetime(ord_df['sell_time'], utc=True)         # object => utc time (aware)   
                    ord_df['close_update_date'] = pd.to_datetime(ord_df['close_update_date'])   # object => ns time (jp time)  
                    ord_df['order_open_date']   = pd.to_datetime(ord_df['order_open_date'])     # object => ns time (jp time) 
                    ord_df['order_close_date']  = pd.to_datetime(ord_df['order_close_date'])    # object => ns time (jp time)                         
                    ord_df['log_time']  = pd.to_datetime(ord_df['log_time'])                    # object => ns time (jp time)    
    
                    ord_df['buy_order_id']  = ord_df['buy_order_id'].astype(str)                # int64 => string
                    ord_df['sell_order_id'] = ord_df['sell_order_id'].astype(str)               # int64 => string               
    
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_buy({ord_buy_order_id=}, {ord_buy_real_qty=:,.{_q_}f}, {ord_buy_real_price=:,.{_p_}f}, {ord_buy_real_fee=:,.0f}, {ord_order_closed=}) ")                              
    
                # end of if ord_buy_real_qty > 0 and ord_sell_real_qty > 0 and ord_buy_real_qty == ord_sell_real_qty:    
                # continue to next buy order sub
            # end of for i in range(len(buy_sub_df)):
    
            #######################################################################################################################################
            ### 【3】SAVE ORDER CSV TO CSV/EXCEL FILES
            #######################################################################################################################################
    
            ### CLOSE (save to the order csv file) : order(BTC_JPY).csv
            if ord_df.shape[0] > 0:
                ### CLOSE (order csv file)
    
                csv_file = self.folder_trading_type + f'order({symbol}).csv'  
                # desktop-pc/bot/sell_buy/order(BTC_JPY).csv   
                ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file  
         
                ### SAVE (order csv excel file)
    
                # save to the order excel file
                excel_file = self.folder_trading_type + f'order({symbol}).xlsx'
                # desktop-pc/bot/sell_buy/order(BTC_JPY).xlsx   
    
                excel_df = ord_df.copy()
    
                # (1) convert time to string              
                excel_df['buy_time']  = excel_df['buy_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))  
                excel_df['sell_time'] = excel_df['sell_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))      
                excel_df['close_update_date']  = excel_df['close_update_date'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))     
                excel_df['log_time']  = excel_df['log_time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S'))  
    
                # (2) drop real_profit columns
                excel_df = excel_df.drop(['real_profit'], axis=1)            
    
                # (3) add new columns
                excel_df['buy(SUM)']    = '=ROUND(E2*G2,3)'                     # add a new column
                excel_df['sell(SUM)']   = '=ROUND(F2*H2,3)'                     # add a new column
                excel_df['profit(AMT)'] = '=IF((H2-G2)>0,ROUNDDOWN((H2 - G2) * F2, 0),ROUNDUP((H2 - G2) * F2, 0))'  # add a new column : rounddown(profit), roundup(loss)
                excel_df['profit(%)']   = '=M2/K2'                              # add a new column
    
                # (4) update excel formula
    
                # get all excel order info  
                row = 2
                for i in range(len(excel_df)):
                    order_closed = excel_df.loc[i, 'order_closed']              # True or False
                    stop_loss = excel_df.loc[i, 'stop_loss']                    # True or False
                    cancelled = excel_df.loc[i, 'cancelled']                    # True or False                
                    if order_closed or stop_loss or cancelled:                
                        cell = str(row)
                        excel_df.loc[i, 'buy(SUM)']     = f'=ROUND(E{cell}*G{cell},3)'                     
                        excel_df.loc[i, 'sell(SUM)']    = f'=ROUND(F{cell}*H{cell},3)'                     
                        excel_df.loc[i, 'profit(AMT)']  = f'=IF((H{cell}-G{cell})>0,ROUNDDOWN((H{cell} - G{cell}) * F{cell}, 0),ROUNDUP((H{cell} - G{cell}) * F{cell}, 0))'  
                        excel_df.loc[i, 'profit(%)']    = f'=M{cell}/K{cell}'                   
                    else:
                        excel_df.loc[i, 'buy(SUM)']     = ''                  
                        excel_df.loc[i, 'sell(SUM)']    = ''                    
                        excel_df.loc[i, 'profit(AMT)']  = '' 
                        excel_df.loc[i, 'profit(%)']    = ''                     
    
                    row += 1
                # end of for j in range(len(excel_df)):   
    
    
                # (5) re-order columns
                columns = [
                    'buy_time','sell_time','buy_order_id','sell_order_id','buy_real_qty','sell_real_qty','buy_real_price','sell_real_price','buy_real_fee','sell_real_fee',
                    'buy(SUM)','sell(SUM)','profit(AMT)','profit(%)',
                    'accumulated_leverage_fee','close_count','close_update_date',
                    'order_closed','stop_loss','cancelled','order_open_date','order_close_date','log_time'
                    ]    
                       
                excel_df = excel_df[columns]
                 
                # print('excel_df.info()\n', excel_df.info())
    
                # ------------------------------------------------------------------------------------------
                #  #   Column                Dtype
                # ------------------------------------------------------------------------------------------
                #  1   time(B)               datetime64[utc]    => Convert to string
                #  2   time(S)               datetime64[utc]    => Convert to string
                #  3   order(B)              object  
                #  4   order(S)              object    
                #  5   qty(B)                float64    
                #  6   qty(S)                float64                
                #  7   price(B)              float64
                #  8   price(S)              float64
                #  9   fee(B)                float64
                # 10   fee(S)                float64
                #      real_profit           float64             => REMOVE
                # 11   buy(SUM)              float64             => ADD =ROUNDUP(E2*G2,3)+I2
                # 12   sell(SUM)             float64             => ADD =ROUNDDOWN(F2*H2,3)+J2
                # 13   profit(AMT)           float64             => ADD =L2-K2             
                # 14   profit(%)             float64             => ADD =M2/K2  
                # 15   fee(L)                float64                       
                # 16   count(L)              int                 
                # 17   close(L)              datetime64[ns]            
                # 18   order_closed          bool                 
                # 19   stop                  bool
                # 20   cancel                bool
                # 21   open_date             datetime64[ns]      
                # 22   close_date            datetime64[ns]      
                # 23   log_time              datetime64[ns]      => Convert to string
                # ----------------------------------------------------------------------------------------                             
    
                # (5) rename column names & export to excel file order_by(BTC_JPY).xlsx
                excel_df.columns = Gmo_dataframe.ORDER_XLSX
                excel_df.to_excel(excel_file, sheet_name='GMO Orders', index=False) 
            # end of if ord_df.shape[0] > 0:
    
            return    
      
        ############################################ 
        def stop_loss(self, method_id: int) -> None:
            """
            
            """
    
            if method_id <= 1:
                eval(f'self.stop_loss_{str(method_id)}()')    # call 'self.stop_loss_?() method
            else:
                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} -  skip stop_loss() ")
                self.debug.sound_alert(3)          
    
            # if method_id == 0:
            #     self.stop_loss_0()  # stop loss method 0
            # elif method_id == 1:
            #     self.stop_loss_1()  # stop loss method 1
            # else:   #
            #     self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} ▶ {Bcolors.FAIL}Invalid stoploss_method_id {method_id=} {Bcolors.ENDC} -  skip stop_loss() ")
            #     self.debug.sound_alert(3)  
    
            return
    
        ############################## 
        def stop_loss_0(self) -> None:
            """
            
            """
         
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            self.gvar.callers_method_name = 'stop_loss(0):'
            df = self.coin.master2_df
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
       
            self.debug.trace_write(f".... {self.gvar.callers_method_name} mas_df.shape({mas_df.shape[0]}), loss cut: 18%({self.coin.sell_buy_action14}), 15%({self.coin.sell_buy_action13}), 10%({self.coin.sell_buy_action12}) ")         
    
            # ### OPEN (load the order csv csv file) : order(BTC_JPY).csv 
            # ord_df = self.csv.get_order_csv()                             
         
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)  
           
            ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER 
            #################################################    
    
            # define buy order header list
            header_real_qty_list = []
            header_real_price_list = []
            header_real_fee_list = []        
    
            ### get all buy order   
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']              # ★ unique time  
                sell_time      = buy_df.loc[i, 'sell_time']             # ★ unique time  
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True  
    
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        ix = dfx.index[0]               
                        buy_order_id = dfx.loc[ix, 'buy_order_id']    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
                        else:
                            pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({buy_order_id=}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass #self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    # end of if dfx.shape[0] > 0:
                    continue    # skip closed order                                                            
                # end of if buy_order_closed:
                    
                #################################################################
                ### 【2】READ BUY ORDER SUB (open or incomplete status)
                #################################################################
                         
                ### for buy order sub (open or incomplete status)
    
                # define buy order sub list
                sub_real_qty_list = []
                sub_real_price_list = []
                sub_real_fee_list = []         
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order 
                for j in range(len(buy_subx_df)):  # ★ buy_subx_df ★   
                    ### get buy order sub info
                    sub_buy_time        = buy_subx_df.loc[j, 'buy_time']           # non unique time                
                    sub_sell_time       = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id    = buy_subx_df.loc[j, 'buy_order_id']       # B9999-999999-999999 ★ (non unique id) : buy sub(many) => sell sub(one)
                    sub_sell_order_id   = buy_subx_df.loc[j, 'sell_order_id']      # S9999-999999-999999 ★ (unique id)     
                    sub_position_id     = buy_subx_df.loc[j, 'position_id']        # ★ (unique id)
                    sub_qty             = buy_subx_df.loc[j, 'qty']                
                    sub_price           = buy_subx_df.loc[j, 'price']            
                    sub_real_qty        = buy_subx_df.loc[j, 'real_qty']               
                    sub_real_price      = buy_subx_df.loc[j, 'real_price']            
                    sub_real_fee        = buy_subx_df.loc[j, 'real_fee']         
                    sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']   
                    sub_stop_loss       = buy_subx_df.loc[j, 'stop_loss']
                    sub_cancelled       = buy_subx_df.loc[j, 'cancelled']  
    
                    sub_order_open = False
                    sub_order_closed = False
                    sub_order_incomplete = False
    
                    if sub_real_qty == 0:
                        sub_order_open = True
                    elif sub_qty == sub_real_qty:
                        sub_order_closed = True
                    else:
                        sub_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP
                    if sub_buy_order_id.startswith('Fake') :
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id=}) ")  
                        continue   
    
                    # buy order sub has been stop loss ? => SKIP
                    if sub_stop_loss: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss: {sub_stop_loss=} ")  
                        continue
    
                    # buy order sub has been cancelled ? => SKIP
                    if sub_cancelled: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ")  
                        continue
    
                    # closed buy order sub ? => SKIP
                    if sub_order_closed:
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id}) has been closed: {sub_order_closed=} ")  
                        continue  
    
                    ##########################################################################################
                    ### 【3】CHECK STOP LOSS FOR EACH BUY ORDER SUB (open or incomplte buy sub order)
                    ##########################################################################################
    
                    ### open or incomplete buy order sub             
                     
                    stop_loss_detected = False
    
                    while True:
    
                        #####################################################
                        ### Check stop loss debug mode
                        #####################################################
                        if self.coin.debug_mode_stop_loss:
                            stop_loss_detected = True
                            break # exit while true loop 
    
                        #####################################################
                        ### Unconditional stop loss
                        #####################################################                    
    
                        ### 1) check loss cut price : sell => buy 
    
                        ### calculate stop loss amount
    
                        stop_loss_amt = 0
                        latest_buy_price = 0
    
                        filter_mask = mas_df['side'] == 'BUY'
                        dfx = mas_df[filter_mask]        
                        if dfx.shape[0] > 0:
                            # get latest buy price from the GMO master dataframe
                            buy_price = dfx.iloc[-1]['price']
                            latest_buy_price = buy_price
    
                            # get last sell qty & price from the sell order header
                            qty = sell_df.iloc[-1]['qty']
                            sell_price = sell_df.iloc[-1]['real_price'] # get a real sell price
    
                            # calculate a stop loss amount : stop_loss_amt = math.floor((sell_price - buy_price) * qty)
                            stop_loss_amt = math.floor((sell_price - buy_price) * qty)  # positive => profit, negative => loss
                            stop_loss_amt = math.floor(stop_loss_amt) # positive => profit, negative => loss     
    
    
                        ### sell_buy : buy position => calculate upper limit for 5%, 10%, 15%, 18%, 20%, 23%, 25%(GMO loss cut rate)
    
                        # calculate loss cut price from the sell sub order  : loss_cut_price = last_sell_real_price + (last_sell_real_price * 0.20)                              
     
                        last_sell_real_price = sell_sub_df.iloc[-1]['real_price']
                        loss_cut_price_05 = last_sell_real_price + (last_sell_real_price * 0.05) # 0.25 => 0.05 
                        loss_cut_price_10 = last_sell_real_price + (last_sell_real_price * 0.10) # 0.25 => 0.10                    
                        loss_cut_price_15 = last_sell_real_price + (last_sell_real_price * 0.15) # 0.25 => 0.15
                        loss_cut_price_18 = last_sell_real_price + (last_sell_real_price * 0.18) # 0.25 => 0.18
                        loss_cut_price_20 = last_sell_real_price + (last_sell_real_price * 0.20) # 0.25 => 0.20 BOT LOSS CUT
                        loss_cut_price_23 = last_sell_real_price + (last_sell_real_price * 0.23) # 0.25 => 0.23 DO NOT USE THIS
                        loss_cut_price_25 = last_sell_real_price + (last_sell_real_price * 0.25) # 0.25 => 0.25 GMO LOSS CUT
            
                        # gmo master latest buy price is greater than zero ?
                        if latest_buy_price > 0:
                            if latest_buy_price >= loss_cut_price_25:   # >= 125
                                self.coin.sell_buy_lc25 += 1
                                # self.debug.trace_warn(f".... DEBUG: loss cut 25%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_25%({loss_cut_price_25:,.{_p_}f}) ")                                          
                            elif latest_buy_price >= loss_cut_price_23: # >= 120
                                self.coin.sell_buy_lc23 += 1         
                                # self.debug.trace_warn(f".... DEBUG: loss cut 23%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_23%({loss_cut_price_23:,.{_p_}f}) ")    
                            elif latest_buy_price >= loss_cut_price_20: # >= 115
                                self.coin.sell_buy_lc20 += 1   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 20%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_20%({loss_cut_price_20:,.{_p_}f}) ")  
                            elif latest_buy_price >= loss_cut_price_18: # >= 113
                                self.coin.sell_buy_lc18 += 1                                                   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 18%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_18%({loss_cut_price_18:,.{_p_}f}) ")                           
                            elif latest_buy_price >= loss_cut_price_15: # >= 112
                                self.coin.sell_buy_lc15 += 1   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 15%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_15%({loss_cut_price_15:,.{_p_}f}) ")  
                            elif latest_buy_price >= loss_cut_price_10: # >= 110
                                self.coin.sell_buy_lc10 += 1   
                                # self.debug.trace_warn(f".... DEBUG: loss cut 10%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_10%({loss_cut_price_10:,.{_p_}f}) ")  
                            elif latest_buy_price >= loss_cut_price_05: # >= 105
                                self.coin.sell_buy_lc5 += 1               
                                # self.debug.trace_warn(f".... DEBUG: loss cut 5%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_5%({loss_cut_price_05:,.{_p_}f}) ")                                                                                                                                                               
    
                            ### check upper limit based on sell price
    
                            # GMO latest buy price is greater than or equal to loss cut price (20%) ? => EXECUTE STOP LOSS 
                            if latest_buy_price >= loss_cut_price_20:
                                self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}STOP LOSS DETECTED REAL(1-5) EXECUTE STOP LOSS {Bcolors.ENDC} loss cut 20%: latest_buy_price({latest_buy_price:,.{_p_}f}) >= loss_cut_20({loss_cut_price_20:,.{_p_}f})  ")                   
                                stop_loss_detected = True          
                                break # exit while true loop                             
    
                        # end of if latest_buy_price > 0:      
                                           
                        break # exit while true loop     
                    # end of while True:
    
                    if not stop_loss_detected: continue
    
                    #######################################################
                    # ★ STOP LOSS DETECTED ★ #                                        
                    #######################################################
        
                    # increment stop loss count
                    self.gvar.stop_loss_count += 1
                    self.coin.stop_loss_count += 1  
    
                    ######################################################
                    ### 【4】UPDATE PARTIALLY CLOSED ORDER
                    ######################################################                
    
                    if sub_order_incomplete:
                        ### add partially closed sell order child
                        self.update_partially_closed_order()
                        self.gvar.callers_method_name = 'stop_loss(0):'                    
                   
                    ######################################################
                    ### 【5】CANCEL CLOSED SELL ORDER SUB
                    ######################################################
    
                    ### cancel closed sell order   
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ") 
    
                    # increment buy cancel count
                    self.gvar.sell_cancel_count += 1   # cancel closed sell order            
                    self.coin.sell_cancel_count += 1   # cancel closed sell order
    
                    ### set a cancelled flag for sell order sub : PK(sell_time + sell_order_id) ★
                    find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
                    sell_sub_df.loc[find_mask, 'cancelled'] = True   
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub(sell_order_id={sub_sell_order_id}, cancelled=True) ")  
    
                    ######################################################
                    ### 【6】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
                    ######################################################
    
                    ### for open or incomplete buy order sub     
    
                    # cancel open/incomplete buy sub order                                                    
                    status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id)    # ★  
    
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id}): exe_cancel_order() ▶ {status=} ") 
    
                    sleep(0.5)    # sleep 0.5 sec
    
                    ###########################################################################################
                    ### 【7】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET (FAK)
                    ###########################################################################################
    
                    ### rebuy open/incomplete buy order sub with market price (stop loss price)
    
                    outstanding_qty = sub_qty - sub_real_qty         
                    new_buy_qty = outstanding_qty                                   
                    qty = new_buy_qty
    
                    if sub_real_price > 0:
                        new_buy_price = sub_real_price + sub_real_fee                                                                                     
                    else:                    
                        new_buy_price = sub_price                                                   
                                     
                    ### get the latest close price from the GMO master
    
                    # get a column position of the GMO master                               
                    mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO)              
    
                    status = 0            
                    loop_count = 0 
    
                    while True:
                        loop_count += 1    
    
                        ### rebuy buy sub order outstanding qty
                        # exe_buy_close_order_market_wait(position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)
                        status, res_df, msg_code, msg_str = self.api.exe_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price)  # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC 
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ") 
            
                        # error ?
                        if status != 0:      
                            if status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while true loop and continue to read next buy sub orders     
                            else: # misc error
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while tue loop and continue to read next buy sub orders  
                            # end of if status == 8 and msg_code == 'ERR-888': 
                        # end of if status != 0:        
    
                        ### no error : res_df.shape[0] >= 1 (single or multiple rows)   
    
                        ######################################################################################################
                        ### 【8】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
                        ######################################################################################################
    
                        # self.debug.trace_warn(f".... DEBUG: print res.df")
                        # print('-'*200)
                        # print(res_df)
                        # print('-'*200)
    
                        # res_df = get_price() response for exe_buy_close_order_market_wait()
                        # ----------------------------------------------------------------
                        #  #   Column       Dtype         
                        # ----------------------------------------------------------------         
                        #  0   executionId  int64         
                        #  1   fee          float64       
                        #  2   lossGain     float64                               
                        #  3   orderId      object(string)   
                        #  4   posiitonId   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]     
                        # ----------------------------------------------------------------                   
    
                        ### IMPORTANT ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
    
                        ### append a new record into the buy order child (★ this is the rebuy version of buy order ★)
    
                        # define buy order child list
                        order_id_list = []
                        position_id_list = []
                        real_qty_list = []
                        real_price_list = []
                        real_fee_list = []  
    
                        # response df for get_price for exe_buy_close_order_market() 
                        for k in range(len(res_df)):
                            order_id    = res_df.loc[k, 'orderId']          # orderId (same orderId): rebuy version of orderId
                            position_id = res_df.loc[k, 'positionId']       # positionId (same positionId)
                            real_qty    = res_df.loc[k, 'size']             # real qty 
                            real_price  = res_df.loc[k, 'price']            # real price
                            real_fee    = res_df.loc[k, 'fee']              # real fee        
    
                            order_id_list.append(order_id)                  # B9999-999999-999999
                            position_id_list.append(position_id)            # P9999-999999-999999
                            real_qty_list.append(real_qty)
                            real_price_list.append(real_price)
                            real_fee_list.append(real_fee)
    
                            ### write a buy order child (rebuy version) ★    
                            # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:                       
                            _qty_ = real_qty; _price_ = real_price
                            self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)    
                        # end of for k in range(len(res_df)):              
      
                        sum_real_qty = round(sum(real_qty_list), _q_)   # round(999.12, _q_)
                        sum_real_fee = round(sum(real_fee_list), 0)     # zero(0)          
    
                        if sum(real_price_list) > 0:
                            mean_real_price = round(statistics.mean(real_price_list), _p_) # round(999999.123, _p_)
                        else:
                            mean_real_price = 0                       
    
                        sub_real_qty_list.append(sum_real_qty)             
                        sub_real_price_list.append(mean_real_price)
                        sub_real_fee_list.append(sum_real_fee)
    
                        ### check exit condition:      
    
                        sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)     # round(999.12, _q_) 
                        new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty    #  
                   
                        # ####################################################### TEMP TEMP TEMP
                        # temp_qty = round(sub_qty, _q_)
                        # temp_real_qty = round(new_sub_sum_real_qty, _q_)
                        # self.debug.trace_warn(f".... DEBUG: {temp_qty=:,.{_q_}f}, {temp_real_qty=:,.{_q_}f} ")
                        # ####################################################### TEMP TEMP TEMP  
    
                        if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):                                                
                            # increment buy cancel count
                            self.gvar.buy_cancel_count += 1  # cancel open/incomplete buy order            
                            self.coin.buy_cancel_count += 1  # cancel open/incomplete buy order
                            break   # exit while true loop 
                        else:       # outstanding buy_qty exists                    
                            pass    # skip (do nothing)                   
                        # end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
    
                        ### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
                            
                        remaining_qty = new_buy_qty - new_sub_sum_real_qty         
                        qty = remaining_qty
    
                        # continue exe_buy_close_order_market_wait() ★                                                             
                    # end of while True:                            
                      
                    #################################################################################
                    ### 【9】UPDATE BUY ORDER SUB (real_qty, real_price, real_fee, stop_loss)
                    #################################################################################
    
                    sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)   # round(999.12, _q_)                                
                    sub_sum_real_fee = round(sum(sub_real_fee_list), 0)     # zero(0)
    
                    if sum(sub_real_price_list) > 0:
                        sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_)  # round(999999.123, _p_)
                    else:
                        sub_mean_real_price = 0                   
    
                    header_real_qty_list.append(sub_sum_real_qty)
                    header_real_price_list.append(sub_mean_real_price)
                    header_real_fee_list.append(sub_sum_real_fee)
    
                    ### update the buy order sub (real_qty, real_price, real_fee, stop_loss) ★
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL} buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub => update
                        buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty      # add real_qty (rebuy version)                           
                        buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price  # update real_price (rebuy version)
                        buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee      # add real_fee (rebuy version)
                        buy_sub_df.loc[find_mask, 'stop_loss'] = True
                        ix = dfx.index[0]
                        new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']
                        new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']                    
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub(buy_order_id={sub_buy_order_id}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, stop_loss=True) ")                     
                    # end of if dfx.shape[0] == 0:  
    
                    #################################################################################
                    ### 【10】UPDATE ORDER CSV FILE (stop_loss, order_closed)
                    #################################################################################
    
                    ### update order info (stop_loss=True, order_closed=True) ★   
              
                    # update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['stop_loss'], col_value_list=[True])                                                                           
    
                    # reset buy order sub list values
                    sub_real_qty_list = []
                    sub_real_price_list = []
                    sub_real_fee_list = [] 
    
                    # continue to next buy order sub ... ★ 
                # end of for j in range(len(buy_sub_df)):
    
                #################################################################################
                ### 【11】SAVE BUY/SELL ORDER SUB TO CSV FILES
                #################################################################################
    
                ### CLOSE (save the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/buy_order_sub(BTC_JPY).csv  
                buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                
                            
                ### CLOSE (save the sell order sib csv file) : sell_order_sub(BTC_JPY).csv 
                csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
                sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                                              
    
                #################################################################################
                ### 【12】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
                #################################################################################
    
                if len(header_real_qty_list) > 0:
                    ### update sell order (real_qty, real_price, real_fee) ★
                    header_sum_real_qty = round(sum(header_real_qty_list), _q_)     # round(999.12, _q_)        
                    header_sum_real_fee = round(sum(header_real_fee_list), 0)       # zero(0)            
                    
                    if sum(header_real_price_list) > 0:
                        header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)  # round(999999.123, _p_)
                    else:
                        header_mean_real_price = 0            
    
                    find_mask = buy_df['buy_time'] == buy_time
                    dfx = buy_df[find_mask]
                    # not found buy order ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order => update
                        buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty      # add real_qty (rebuy version)                          
                        buy_df.loc[find_mask, 'real_price'] = header_mean_real_price  # update real_price (rebuy version) 
                        buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee      # add real_fee (rebuy version)
                        ix = dfx.index[0]
                        new_header_sum_real_qty = buy_df.loc[ix, 'real_qty'] 
                        new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ") 
                    # end of if dfx.shape[0] == 0:                   
                # end of if len(header_real_qty_list) > 0:
    
                # reset list values
                header_real_qty_list = []
                header_real_price_list = []
                header_real_fee_list = []     
    
                # continue to next buy order 
            # end of for i in range(len(buy_df)):
    
            #################################################################################
            ### 【13】SAVE BUY/SELL ORDER HEADER TO CSV FILES 
            #################################################################################
    
            ### CLOSE (save the buy order csv file) : buy_order(BTC_JPY).csv         
            csv_file = self.folder_trading_type + f'buy_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/buy_order(BTC_JPY).csv  
            buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode  
    
            ### CLOSE (save the sell order csv file) : sell_order(BTC_JPY).csv      
            csv_file = self.folder_trading_type + f'sell_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order(BTC_JPY).csv   
            sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode     
    
            return
      
        ############################## 
        def stop_loss_1(self) -> None:  
            """
    
            """     
    
            return
    
               
        ########################################   
        def cancel_pending_order(self) -> None:
            """        
    
            """   
    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal
            self.gvar.callers_method_name = 'cancel_pending_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} ") 
    
            df = self.coin.master2_df  
            mas_df = pd.DataFrame(df)   # cast df (master dataframe)
           
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)  
              
            ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            #################################################
            ### 【1】READ ALL BUY ORDER HEADER
            #################################################
    
            # define buy order header list
            header_real_qty_list = []
            header_real_price_list = []
            header_real_fee_list = []        
    
            ### get all buy order 
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']    # ★ unique time 
                sell_time      = buy_df.loc[i, 'sell_time']   # ★ unique time                                   
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True         
            
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        ix = dfx.index[0]                
                        buy_order_id = dfx.loc[ix, 'buy_order_id']    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    # end of if dfx.shape[0] > 0:
    
                    continue    
                # end of if buy_order_closed:  
    
                #################################################################
                ### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
                #################################################################
    
                ### for buy order sub (open or incomplete status)
    
                # define buy order sub list
                sub_real_qty_list = []
                sub_real_price_list = []
                sub_real_fee_list = [] 
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    ### get buy order sub info
                    sub_buy_time        = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time       = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id    = buy_subx_df.loc[j, 'buy_order_id']       # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
                    sub_sell_order_id   = buy_subx_df.loc[j, 'sell_order_id']      # ★ 'S9999-999999-999999' (unique id)           
                    sub_position_id     = buy_subx_df.loc[j, 'position_id']        # ★ (unique id)
                    sub_qty             = buy_subx_df.loc[j, 'qty']                                  
                    sub_price           = buy_subx_df.loc[j, 'price']               
                    sub_real_qty        = buy_subx_df.loc[j, 'real_qty']                        
                    sub_real_price      = buy_subx_df.loc[j, 'real_price']            
                    sub_real_fee        = buy_subx_df.loc[j, 'real_fee']                  
                    sub_stop_loss       = buy_subx_df.loc[j, 'stop_loss']
                    sub_cancelled       = buy_subx_df.loc[j, 'cancelled']                
            
                    sub_order_open = False
                    sub_order_closed = False
                    sub_order_incomplete = False
    
                    if sub_real_qty == 0:
                        sub_order_open = True
                    elif sub_qty == sub_real_qty:
                        sub_order_closed = True
                    else:
                        sub_order_incomplete = True                             
    
                    # fake buy sub order ? => SKIP    
                    if sub_buy_order_id.startswith('Fake') :
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id=}) ") 
                        continue               
    
                    # buy sub order has been stopped loss ? => SKIP
                    if sub_stop_loss: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")  
                        continue
    
                    # cancelled buy sub order ? => SKIP    
                    if sub_cancelled: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ") 
                        continue           
    
                    # closed buy sub order ? => SKIP    
                    if sub_order_closed: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ") 
                        continue  
    
                    ### open or incomplete buy order sub    
    
                    ######################################################
                    ### 【3】UPDATE PARTIALLY CLOSED ORDER
                    ######################################################                
    
                    if sub_order_incomplete:
                        ### add partially closed sell order child
                        self.update_partially_closed_order()
                        self.gvar.callers_method_name = 'cancel_pending_order():'                  
    
                    ##############################################################################################
                    ### 【4】CANCEL CLOSED SELL ORDER SUB : ★ buy order sub (many) => sell order sub (one) ★
                    ##############################################################################################
    
                    ### for closed sell order sub                              
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}CANCEL{Bcolors.ENDC} closed sell order sub({sub_sell_order_id=}) ") 
    
                    # increment sell cancel count
                    self.gvar.sell_cancel_count += 1   # cancel closed sell order             
                    self.coin.sell_cancel_count += 1   # cancel closed sell order 
    
                    ### set a cancelled flag for sell order sub : ★ PK(sell_time + sell_order_id) ★
                    find_mask = (sell_sub_df['sell_time'] == sub_sell_time) & (sell_sub_df['sell_order_id'] == sub_sell_order_id)
                    sell_sub_df.loc[find_mask, 'cancelled'] = True                
                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_sell_order_sub({sub_sell_order_id=}, cancelled=True) ")                                                              
                                       
                    ######################################################
                    ### 【5】CANCEL OPEN/INCOMPLETE BUY ORDER SUB
                    ######################################################
    
                    ### cancel open or incomplete buy order sub  
    
                    ### cancel buy order sub                    
                    status, msg_code, msg_str = self.api.exe_cancel_order(sub_buy_order_id)    # ★        
                    self.debug.print_log(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}CANCEL{Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_cancel_order() ▶ {status=} ")   
                    sleep(0.5)  # sleep 0.5 sec                       
    
                    ##################################################################################################
                    ### 【6】REBUY OUTSTANDING BUY ORDER SUB QTY : EXECUTE BUY CLOSE ORDER MARKET WAIT (FAK)
                    ##################################################################################################
    
                    ### rebuy open or incomplete buy order sub with market price 
    
                    outstanding_qty = sub_qty - sub_real_qty    # calculate outstanding qty 
                    new_buy_qty = outstanding_qty                                       
                    qty = new_buy_qty
    
                    if sub_real_price > 0:
                        new_buy_price = sub_real_price + sub_real_fee                              
                    else:
                        new_buy_price = sub_price               
    
                    ### get the latest close price from the GMO master
    
                    # get a column position of the GMO master                              
                    mas_latest_close_price = mas_df.iloc[-1]['close']   # get latest close price (GMO)                             
    
                    status = 0
                    loop_count = 0
    
                    while True:
                        loop_count += 1    
    
                        # exe_buy_close_order_market_wait(position_id: str, qty: float, price: float, latest_close_price=0.0) -> tuple:  # return (status, df, msg_code, msg_str)
                        status, res_df, msg_code, msg_str = self.api.exe_buy_close_order_market_wait(sub_position_id, qty, new_buy_price, mas_latest_close_price)  # ★ FAK(Fill and Kill): new_buy_price, mas_latest_close_price are used for fake mode ; 4BTC; 1BTC               
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}REBUY({loop_count}){Bcolors.ENDC} open/incomplete buy order sub({sub_buy_order_id=}): exe_buy_close_order_market_wait() ▶ {status=} ") 
            
                        # error ?
                        if status != 0:                                 
                            if status == 8 and msg_code == 'ERR-888': # get_price() time out error ?
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) time out error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while true loop and continue to read next buy sub orders 
                            else: # misc error
                                self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} exe_buy_close_order_market_wait({sub_position_id=}) misc error: ▶ {Bcolors.FAIL} {status=}, {msg_code} - {msg_str}  {Bcolors.ENDC} ")
                                break   # exit while true loop and continue to read next buy sub orders                    
                            # end of if status == 8 and msg_code == 'ERR-888': 
                        # end of if status != 0:  
         
                        ### no error : res_df.shape[0] >= 1 (single or multiple rows)
    
                        ##################################################################################################
                        ### 【7】ADD BUY ORDER CHILD ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
                        ##################################################################################################
    
                        # res_df = response for exe_buy_close_order_market_wait() 
                        # -----------------------------------------------------------
                        #  #   Column       Dtype         
                        # -----------------------------------------------------------         
                        #  0   executionId  int64         
                        #  1   fee          float64       
                        #  2   lossGain     float64                               
                        #  3   orderId      object(string)  (non unique)  
                        #  4   positionId   object(string)  (non unique)     
                        #  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]  
                        # ----------------------------------------------------------
    
                        ### IMPORTANT ★ DO NOT CREATE REBUY VERSION OF NEW BUY ORDER SUB ★ 
                        
                        ### append a new record into the buy order child  (★ this is the rebuy version of buy order child ★)
    
                        # define buy order child list
                        order_id_list = []
                        position_id_list = []
                        real_qty_list = []
                        real_price_list = []
                        real_fee_list = []   
    
                        # response for exe_buy_close_order_market_wait()
                        for k in range(len(res_df)):
                            order_id    = res_df.loc[k, 'orderId']          # orderId (same orderId) : rebuy version of orderId         
                            position_id = res_df.loc[k, 'positionId']       # positionId (same positionId)               
                            real_qty    = res_df.loc[k, 'size']             # real qty     
                            real_price  = res_df.loc[k, 'price']            # real price
                            real_fee    = res_df.loc[k, 'fee']              # real fee
    
                            order_id_list.append(order_id)                  # B9999-999999-999999
                            position_id_list.append(position_id)            # P9999-999999-999999
                            real_qty_list.append(real_qty)                  # 
                            real_price_list.append(real_price)
                            real_fee_list.append(real_fee)
    
                            ### write a buy order child (rebuy version) ★    
                            # write_buy_order_child(buy_time: utcnow, buy_order_id: str, sell_time: utcnow, sell_order_id: str, position_id: str , qty=0.0, price=0.0, real_qty=0.0, real_price=0.0, real_fee=0.0) -> None:                       
                            _qty_ = real_qty; _price_ = real_price
                            self.csv.write_buy_order_child(buy_time, order_id, sell_time, sub_sell_order_id, position_id, _qty_, _price_, real_qty, real_price, real_fee)                             
                        # end of for k in range(len(res_df)):                      
    
                        sum_real_qty = round(sum(real_qty_list), _q_)                                                          
                        sum_real_fee = round(sum(real_fee_list), 0)                             
    
                        if sum(real_price_list) > 0:
                            mean_real_price = round(statistics.mean(real_price_list), _p_)        
                        else:
                            mean_real_price = 0    
    
                        sub_real_qty_list.append(sum_real_qty)                                  
                        sub_real_price_list.append(mean_real_price)
                        sub_real_fee_list.append(sum_real_fee)
                                          
                        ### check exit condition:      
                         
                        sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)                       
                        new_sub_sum_real_qty = sub_real_qty + sub_sum_real_qty          
                 
                        # no outstanding buy_qty ?
                        if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):            
                            # increment buy cancel count
                            self.gvar.buy_cancel_count += 1  # cancel open/incomplete buy order            
                            self.coin.buy_cancel_count += 1  # cancel open/incomplete buy order
                            break   # exit while true loop
                        else:       # outstanding buy_qty exists
                            pass    # skip (do nothing)                 
                        # end of if round(sub_qty, _q_) == round(new_sub_sum_real_qty, _q_):
    
                        ### partial exe_buy_close_order_market_wait() = continue to buy remaining qty
                            
                        remaining_qty = new_buy_qty - new_sub_sum_real_qty        
                        qty = remaining_qty
    
                        # continue exe_buy_close_order_market_wait() ★
                    # end of while True:   
                     
                    #################################################################################
                    ### 【8】UPDATE BUY ORDER SUB (real_qty, real_price, real_fee, cancelled)
                    #################################################################################                                  
        
                    sub_sum_real_qty = round(sum(sub_real_qty_list), _q_)                                                   
                    sub_sum_real_fee = round(sum(sub_real_fee_list), 0)          
    
                    if sum(sub_real_price_list) > 0:
                        sub_mean_real_price = round(statistics.mean(sub_real_price_list), _p_) 
                    else:
                        sub_mean_real_price = 0                      
    
                    header_real_qty_list.append(sub_sum_real_qty)                            
                    header_real_price_list.append(sub_mean_real_price)
                    header_real_fee_list.append(sub_sum_real_fee)
    
                    ### update the buy order sub (real_qty, real_price, real_fee, cancelled) 
                    find_mask = (buy_sub_df['buy_time'] == sub_buy_time) & (buy_sub_df['buy_order_id'] == sub_buy_order_id)
                    dfx = buy_sub_df[find_mask]
                    # not found buy order sub ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order_sub({sub_buy_order_id=}) ▶ {Bcolors.FAIL}buy order sub not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order sub => update 
                        buy_sub_df.loc[find_mask, 'real_qty'] += sub_sum_real_qty      # add real_qty (rebuy version)                                  
                        buy_sub_df.loc[find_mask, 'real_price'] = sub_mean_real_price  # update real_price (rebuy version)
                        buy_sub_df.loc[find_mask, 'real_fee'] += sub_sum_real_fee      # add real_fee (rebuy version)
                        buy_sub_df.loc[find_mask, 'cancelled'] = True
                        ix = dfx.index[0]
                        new_sub_sum_real_qty = buy_sub_df.loc[ix, 'real_qty']          
                        new_sub_sum_real_fee = buy_sub_df.loc[ix, 'real_fee']
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order_sub({sub_buy_order_id=}, real_qty={new_sub_sum_real_qty:,.{_q_}f}, real_price={sub_mean_real_price:,.{_p_}f}, real_fee={new_sub_sum_real_fee:,.0f}, cancelled=True) ")                                    
                    # end of if dfx.shape[0] == 0:   
                        
                    #################################################################################
                    ### 【9】UPDATE ORDER CSV FILE (cancelled, order_closed)
                    #################################################################################                        
                        
                    ### update order csv (cancelled=True, order_closed=True)  ★  
    
                    # update_order_csv_sell(sell_time: dt.datetime.utcnow, sell_order_id: str, col_name_list: list, col_value_list: list) -> None:
                    self.csv.update_order_csv_sell(sub_sell_time, sub_sell_order_id, col_name_list=['cancelled'], col_value_list=[True]) # PK(sell_time + sell_order_id)                                                                                                                    
    
                    # reset buy order sub list values
                    sub_real_qty_list = []
                    sub_real_price_list = []
                    sub_real_fee_list = [] 
    
                    # continue to next buy order sub
                # end of for j in range(len(buy_subx_df)):     
    
                #################################################################################
                ### 【10】SAVE BUY/SELL ORDERS SUB TO CSV FILES
                #################################################################################
    
                ### CLOSE (save the buy order sub csv file)  
                csv_file = self.folder_trading_type + f'buy_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/buy_order_sub(BTC_JPY).csv  
                buy_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode   
                                         
                ### CLOSE (save the sell order sub csv file) 
                csv_file = self.folder_trading_type + f'sell_order_sub({symbol}).csv' 
                # desktop-pc/bot/sell_buy/sell_order_sub(BTC_JPY).csv  
                sell_sub_df.to_csv(csv_file, header=False, index=False) # OverWrite mode                                             
    
                #################################################################################
                ### 【11】UPDATE BUY ORDER HEADER (real_qty, real_price, real_fee)
                #################################################################################
                    
                if len(header_real_qty_list) > 0:    
                    ### update buy order (real_qty, real_price, real_fee) ★
                    header_sum_real_qty = round(sum(header_real_qty_list), _q_)                            
                    header_sum_real_fee = round(sum(header_real_fee_list), 0)                                  
                    
                    if sum(header_real_price_list) > 0:
                        header_mean_real_price = round(statistics.mean(header_real_price_list), _p_)  
                    else:
                        header_mean_real_price = 0    
    
                    find_mask = buy_df['buy_time'] == buy_time
                    dfx = buy_df[find_mask]
                    # not found buy order ?
                    if dfx.shape[0] == 0:
                        self.debug.print_log(f".... {Bcolors.WARNING}{self.gvar.callers_method_name}{Bcolors.ENDC} update_buy_order() ▶ {Bcolors.FAIL}buy order({buy_time=:%Y-%m-%d %H:%M:%S}) not found {dfx.shape=}{Bcolors.ENDC} ")   
                    else: # found buy order => update
                        buy_df.loc[find_mask, 'real_qty'] += header_sum_real_qty     # add real_qty (rebuy version)                            
                        buy_df.loc[find_mask, 'real_price'] = header_mean_real_price # update real_price (rebuy version) 
                        buy_df.loc[find_mask, 'real_fee'] += header_sum_real_fee     # add real_fee (rebuy version) 
                        ix = dfx.index[0]
                        new_header_sum_real_qty = buy_df.loc[ix, 'real_qty'] 
                        new_header_sum_real_fee = buy_df.loc[ix, 'real_fee']                
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} update_buy_order({buy_time=:%Y-%m-%d %H:%M:%S}, real_qty={new_header_sum_real_qty:,.{_q_}f}, real_price={header_mean_real_price:,.{_p_}f}, real_fee={new_header_sum_real_fee:,.0f}) ")                                                    
                    # end of if dfx.shape[0] == 0:          
                # end of if len(header_real_qty_list) > 0: 
    
                # reset buy header list values
                header_real_qty_list = []
                header_real_price_list = []
                header_real_fee_list = []     
    
                # continue to next buy order... ★              
            # end of for i in range(len(buy_df)):  
    
            #################################################################################
            ### 【12】SAVE BUY/SELL ORDER HEADER TO CSV FILES 
            #################################################################################        
    
            ### CLOSE (save the buy order csv file)       
            csv_file = self.folder_trading_type + f'buy_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/buy_order(BTC_JPY).csv  
            buy_df.to_csv(csv_file, header=False, index=False) # OverWrite mode  
    
            ### CLOSE (save the sell order csv file)      
            csv_file = self.folder_trading_type + f'sell_order({symbol}).csv' 
            # desktop-pc/bot/sell_buy/sell_order(BTC_JPY).csv   
            sell_df.to_csv(csv_file, header=False, index=False) # OverWrite mode 
    
            return
    
        #######################################  
        def close_pending_order(self) -> None: 
            """        
         
            """    
            symbol = self.coin.symbol; _q_ = self.coin.qty_decimal; _p_ = self.coin.price_decimal     
            self.gvar.callers_method_name = 'close_pending_order():'
            self.debug.trace_write(f".... {self.gvar.callers_method_name} ") 
    
            # df = self.coin.master2_df
            # mas_df = pd.DataFrame(df)   # cast df (master dataframe)
           
            ### OPEN (load the order info csv file) : order_by(BTC_JPY).csv 
            ord_df = self.csv.get_order_csv()  
    
            ### OPEN (load the buy order csv file) : buy_order(BTC_JPY).csv 
            buy_df = self.csv.get_buy_order() # PK(buy_time)        
    
            ### OPEN (load the buy order sub csv file) : buy_order_sub(BTC_JPY).csv 
            buy_sub_df = self.csv.get_buy_order_sub() # PK(buy_time + order_id)  
    
            # ### OPEN (load the buy order child csv file) : buy_order_child(BTC_JPY).csv 
            # buy_ch_df = self.csv.get_buy_order_child() # PK(buy_time + buy_order_id + position_id)          
              
            # ### OPEN (load the sell order csv file) : sell_order(BTC_JPY).csv 
            # sell_df = self.csv.get_sell_order() # PK(sell_time)        
    
            # ### OPEN (load the sell order sub csv file) : sell_order_sub(BTC_JPY).csv 
            # sell_sub_df = self.csv.get_sell_order_sub() # PK(sell_time + order_id)   
    
            ### OPEN (load the sell order child csv file) : sell_order_child(BTC_JPY).csv 
            sell_ch_df = self.csv.get_sell_order_child() # PK(sell_time + sell_order_id + position_id)          
    
            #################################################
            ### 【1】READ ALL BUY ORDER HEADERS
            #################################################
     
            ### get all buy order headers 
            for i in range(len(buy_df)):
                ### get buy order info
                buy_time       = buy_df.loc[i, 'buy_time']    # ★ unique time 
                sell_time      = buy_df.loc[i, 'sell_time']   # ★ unique time                                   
                buy_qty        = buy_df.loc[i, 'qty']
                buy_price      = buy_df.loc[i, 'price']
                buy_real_qty   = buy_df.loc[i, 'real_qty']
                buy_real_price = buy_df.loc[i, 'real_price']
                buy_real_fee   = buy_df.loc[i, 'real_fee']
    
                buy_order_open = False
                buy_order_closed = False
                buy_order_incomplete = False
    
                if buy_real_qty == 0:
                    buy_order_open = True
                elif buy_qty == buy_real_qty:
                    buy_order_closed = True
                else:
                    buy_order_incomplete = True         
            
                # closed buy order ? => SKIP    
                if buy_order_closed: 
                    # find the buy order sub
                    find_mask = buy_sub_df['buy_time'] == buy_time
                    dfx = buy_sub_df[find_mask]
                    # found the buy order sub ?
                    if dfx.shape[0] > 0:
                        ix = dfx.index[0]                
                        buy_order_id = dfx.loc[ix, 'buy_order_id']    
                        if buy_order_id.startswith('Fake'):
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({buy_order_id=})... ")
                        else:
                            pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_order_id=}) has been closed: {buy_order_closed=} ") 
                    else:
                       pass # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order({buy_time=:%Y-%m-%d %H:%M:%S}) has been closed: {buy_order_closed=} ") 
                    # end of if dfx.shape[0] > 0:
    
                    continue    
                # end of if buy_order_closed:  
    
                #################################################################
                ### 【2】READ ALL BUY ORDER SUB (OPEN OR INCOMPLETE STATUS)
                #################################################################
    
                ### for buy order sub (open or incomplete status)
    
                # filter the buy order sub by buy_time
                filter_mask = buy_sub_df['buy_time'] == buy_time
                buy_subx_df = buy_sub_df[filter_mask]
                if buy_subx_df.shape[0] > 0:
                    buy_subx_df.reset_index(inplace=True)
    
                # get all buy order sub for this buy order
                for j in range(len(buy_subx_df)):   # ★ buy_subx_df ★
                    ### get buy order sub info
                    sub_buy_time        = buy_subx_df.loc[j, 'buy_time']           # non unique time
                    sub_sell_time       = buy_subx_df.loc[j, 'sell_time']          # non unique time
                    sub_buy_order_id    = buy_subx_df.loc[j, 'buy_order_id']       # ★ 'B9999-999999-999999' (non unique id) : buy sub(many) => sell sub(one)
                    sub_sell_order_id   = buy_subx_df.loc[j, 'sell_order_id']      # ★ 'S9999-999999-999999' (unique id)           
                    sub_position_id     = buy_subx_df.loc[j, 'position_id']        # ★ (unique id)
                    sub_qty             = buy_subx_df.loc[j, 'qty']                                  
                    sub_price           = buy_subx_df.loc[j, 'price']               
                    sub_real_qty        = buy_subx_df.loc[j, 'real_qty']                        
                    sub_real_price      = buy_subx_df.loc[j, 'real_price']            
                    sub_real_fee        = buy_subx_df.loc[j, 'real_fee']        
                    sub_get_price_count = buy_subx_df.loc[j, 'get_price_count']            
                    sub_stop_loss       = buy_subx_df.loc[j, 'stop_loss']
                    sub_cancelled       = buy_subx_df.loc[j, 'cancelled']                
            
                    sub_order_open = False
                    sub_order_closed = False
                    sub_order_incomplete = False
    
                    if sub_real_qty == 0:
                        sub_order_open = True
                    elif sub_qty == sub_real_qty:
                        sub_order_closed = True
                    else:
                        sub_order_incomplete = True                             
    
                    # fake buy order sub ? => SKIP    
                    if sub_buy_order_id.startswith('Fake') :
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} fake buy order sub({sub_buy_order_id=}) ") 
                        continue               
    
                    # buy order sub has been stopped loss ? => SKIP
                    if sub_stop_loss: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been stopped loss ▶ {sub_stop_loss=} ")  
                        continue
    
                    # cancelled buy order sub ? => SKIP    
                    if sub_cancelled: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been cancelled: {sub_cancelled=} ") 
                        continue           
    
                    # closed buy order sub ? => SKIP    
                    if sub_order_closed: 
                        # self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.OKCYAN}SKIP{Bcolors.ENDC} buy order sub({sub_buy_order_id=}) has been closed: {sub_order_closed=} ") 
                        continue                 
                 
                    ### open or incomplete buy order sub  
                     
                    #################################################################
                    ### 【3】CALCULATE LEVERAGE FEE AND UPDATE ORDER CSV FILE
                    #################################################################
    
                    ### calculate leverage fee 
    
                    # get a sell order child 
    
                    find_mask = (sell_ch_df['sell_time'] == sub_sell_time) & (sell_ch_df['sell_order_id'] == sub_sell_order_id) & (sell_ch_df['position_id'] == sub_position_id) 
                    dfx = sell_ch_df[find_mask]
                    # not found sell order child ?
                    if dfx.shape[0] == 0:
                        self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found sell order child{Bcolors.ENDC} : {sub_sell_time=:%Y-%m-%d %H:%M:%S}, {sub_sell_order_id=}, {sub_position_id=}  ") 
                    else: # found sell order child 
                        ix = dfx.index[0]
                        ch_sell_real_qty = dfx.loc[ix, 'real_qty']
                        ch_sell_real_price = dfx.loc[ix, 'real_price']
                        ch_leverage_fee = math.ceil((ch_sell_real_price * ch_sell_real_qty) * 0.0004)   # leverage fee (0.04%)
    
                        ### update order csv (accumulated_leverage_fee, close_count, close_update_date)
    
                        # not empty order csv ?
                        if ord_df.shape[0] > 0:
                            find_mask = ord_df['sell_order_id'] == sub_sell_order_id  # S9999-999999-999999
                            dfx = ord_df[find_mask]
                            # not found order csv ?
                            if dfx.shape[0] == 0:   
                                self.debug.trace_write(f".... {self.gvar.callers_method_name} {Bcolors.FAIL}not found order csv{Bcolors.ENDC} : {sub_sell_order_id=}  ") 
                            else: # found order csv 
                                update_order_csv = False
                                ix = dfx.index[0]
                                close_count = ord_df.loc[ix, 'close_count']
                                close_update_date = ord_df.loc[ix, 'close_update_date']
                                accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
                                if close_count == 0:
                                    update_order_csv = True                             
                                else: # close_count > 0
                                    now_date_str = f"{dt.datetime.now():%Y%m%d}"            # YYYYMMDD    
                                    close_update_date_str = f"{close_update_date:%Y%m%d}"   # YYYYMMDD                                
                                    if close_update_date_str != now_date_str:
                                        update_order_csv = True
                                    # self.debug.trace_warn(f"DEBUG: update_order_csv={update_order_csv}, close_count={close_count}, now_date_str={now_date_str}, close_update_date_str={close_update_date_str}, leverage_fee={accumulated_leverage_fee:,.0f} ")    
                                    # update_order_csv = True  # DEBUG DEBUG DEBUG
                                # end of if close_count == 0:
    
                                if update_order_csv:
                                    # update order csv 
                                    ord_df.loc[find_mask, 'accumulated_leverage_fee'] += ch_leverage_fee      
                                    ord_df.loc[find_mask, 'close_count'] += 1
                                    ord_df.loc[find_mask, 'close_update_date'] = dt.datetime.now()           
                                    ord_df.loc[find_mask, 'close_update_date'] = dt.datetime.now() 
    
                                    ord_df['close_update_date']  = pd.to_datetime(ord_df['close_update_date']) # object => ns time (jp time)  
                                    new_accumulated_leverage_fee = ord_df.loc[ix, 'accumulated_leverage_fee']
                                    new_close_count = ord_df.loc[ix, 'close_count']                               
                                    new_close_update_date = ord_df.loc[ix, 'close_update_date']
                                    self.debug.trace_write(f".... {self.gvar.callers_method_name} update_order_csv_sell(): sell_order_id={sub_sell_order_id}, leverage_fee={new_accumulated_leverage_fee:,.0f}, close_count={new_close_count}, close_update_date={new_close_update_date:%Y-%m-%d %H:%M:%S}  ") 
                                                        
                        # end of if ord_df.shape[0] > 0:
                    # end of if dfx.shape[0] == 0:                 
             
                    # continue to next buy order sub...
                # end of for j in range(len(buy_subx_df)):                                
    
                # continue to next buy order... ★              
            # end of for i in range(len(buy_df)): 
    
            #################################################################################
            ### 【4】SAVE ORDER CSV TO CSV FILE 
            #################################################################################        
    
            ### CLOSE (save to the order csv file) : order(BTC_JPY).csv
            if ord_df.shape[0] > 0:
                ### CLOSE (order csv file)
    
                csv_file = self.folder_trading_type + f'order({symbol}).csv'  
                # desktop-pc/bot/sell_buy/order(BTC_JPY).csv   
                ord_df.to_csv(csv_file, header=False, index=False) # overWrite the order file  
    
            return