Python {Article112}

ようこそ「Python」へ...

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

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

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

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

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

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

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

BOTのBaseクラスを作成する

  1. Bcolorsクラスを作成する

    ここではDebugクラス等でメッセージをカラーで表示するためのクラス「Bcolors」を作成します。 なお、ここではWindowsのエスケープシーケンス(ESC)を使用して文字をカラー表示します。 Mac, UnixなどWindows以外のOSを使用するときは、それぞれの環境に合わせて書き換える必要があります。 Windowsのエスケープシーケンスの一覧は、 「こちら」で参照してください。

    たとえば、赤の前景色は「ESC[91m」、赤の背景色は「ESC[101m」のように記述します。 エスケープシーケンスをリセットするには「ESC[0m」を使用します。 Pythonでエスケープシーケンス「ESC」を記述するときは「\033」のようにバックスラッシュを使います。

    base.py:
    ############## Bot colors class
    class Bcolors:
        HEADER = '\033[95m'     # bright magenta
        OKBLUE = '\033[94m'     # bright blue
        OKCYAN = '\033[96m'     # bright cyan
        OKGREEN = '\033[92m'    # bright green
        WARNING = '\033[93m'    # bright yellow
        FAIL = '\033[91m'       # bright red
        ENDC = '\033[0m'        # reset
        BOLD = '\033[1m'        # bold
        UNDERLINE = '\033[4m'   # underline

    click image to zoom!
    図1-1
    図1-1はBcolorsクラスを使用してテキスト文字を表示したときの画面です。


    click image to zoom!
    図1-2
    図1-2はデモプログラムを作成して背景色を表示したときの画面です。 前景色は自動的に調整されます。


  2. Gvarクラスを作成する

    ここではGvarクラスを作成します。 Gvarは「Global Variables」の略で、BOTが使用するグローバル変数を定義しています。 Gvarの各種プロパティ(Pythonでは属性と呼びますが、 本記事ではプロパティと呼びます)を書き換えることによりBOTの処理をカスタマイズすることができます。 たとえば、自動トレードを「リアルトレード」にするか「仮想トレード」にするかは、 「real_mode」プロパティに「True/False」を設定することにより切り替えます。 Gvarクラスでは「__str__()」メソッドを追加しているので、 「print(gvar)」のようにGvarクラスのプロパティを表示することができます。 「__str__()」メソッドの使い方については、 「記事(Article087)」で解説しています。

    base.py:
    ########### Global variables
    class Gvar:    
        def __init__(self) -> None:
            self.callers_method_name = '_main_:'
            self.domain = 'desktop-pc'  # desktop_pc or note_pc
            self.exchange = 'bot'            
            self.folder_gmo =  f'{self.domain}/{self.exchange}/'    # desktop-pc/bot/ 
            self.real_mode = False    
            self.reset_mode = True
            self.debug_mode = True        
    
            self.coin_list = [Coin()]                              
    
            :::
    
        def __str__(self) -> str:
            return f"Gvar({self.domain=}, {self.exchange=}, {self.folder_gmo=}, {self.real_mode=}, {self.reset_mode=}, {self.stop_loss=}, {self.debug_mode=}, ...)"

    click image to zoom!
    図2
    図2ではGvarクラスのインスタントを生成してデフォルトのプロパティを表示しています。 そして、各種プロパティを書き換えた後に確認のために再度Gvarのプロパティを表示しています。


  3. Coinクラスを作成する

    ここではCoinクラスを作成します。 Coinクラスには自動トレードする仮想通貨の情報を格納します。 今回作成するBOTは、Coinクラスを使用しているので簡単に新規の仮想通貨を追加することができます。

    Coinクラスには、仮想通貨のシンボル(BTC_JPY)、数量、利益率等の情報が格納されます。 Coinクラスのプロパティは「print(coin)」で表示することができます。

    Coinクラスのプロパティ「master_df」と「master2_df」には「@property」「@setter」のディレクティブが定義されています。 これらのディレクティブを使用するとプロパティを参照・設定するときにエラーチェックを行うことができます。 なお、ディレクティブの使い方については、 「記事(Article089)」で解説しています。

    行34-45ではGvarと同様、「__str__()」メソッドを追加してCoinクラスのプロパティを表示できるようにしています。

    行37-43では「@property(@master_df.getter, @master2_df.getterという意味)」ディレクティブを追加して「master_df」と「master2_df」のプロパティを定義しています。 「@property」を追加するとプロパティを参照したときにgetterと呼ばれる関数「master_df()」「master2_df()」が呼び出されます。 getterの関数ではCoinクラスからプロパティを取得して返します。

    行45-59では「@master_df.setter」「@master2_df.setter」ディレクティブを追加してプロパティのsetterを定義しています。 「@setter」を追加するとプロパティを設定するときにsetterと呼ばれる関数「master_df()」「master2_df()」が呼び出されます。 ちなみに、getterとsetterの関数名は同じ名称になっています。 setterの関数では、引数に指定されたPandasのDataframeの内容をチェックしてエラーを検出したときは「raise」でKeyErrorを発生させています。 なぜ、このようなチェックを行っているかと言えば、ユーザーミスを防ぐためです。

    実はPandasのDataFrameをCoinクラスの「master_df」プロパティに保存するとき、 「self._master_df = master_df」のように記述するとPythonはDataFrameのポインターを設定します。 つまり、DataFrameは共有されます。この不都合を回避するには、 「 self._master_df = master_df.copy()」のように「copy()」メソッドを使う必要があります。 プロパティの「getter/setter」を使うとプロパティの参照・設定を一元管理できるのでユーザーミスを回避できます。

    base.py:
    ########### Coin class
    class Coin:
        def __init__(self) -> None:
            self.symbol = 'BTC_JPY'              
            self.suspend = False  
            self.master_csv_not_found = False      
    
            self.master_df = pd.DataFrame()     # df.copy()
            self.master2_df = pd.DataFrame()    # df.copy()
    
            self.real_mode = False              
            self.fake_or_real = 'fake'          # 'fake' or 'real'
            self.trade_type = 'buy_sell'        # 'buy_sell' or 'sell_buy'
            self.suspend_new_order = False
    
            self.qty = 0.01
    
            self.round_timestamp_freq = '1S'    # freq: H-hour, T-minute, S-seconds, L-milliseconds : new version 0X - skip round timestamp
           
            self.profit_rate = 1.0015           
            self.leverage_fee_rate = 0.0004    
            self.stop_loss_rate = 0.0200      
    
            self.trend_search_count = 3         
            self.close_count_limit = 7          
    
            self.qty_decimal = 2
            self.price_decimal = 0          
    
            :::
    
        def __str__(self) -> str:
            return f"Coin({self.symbol=}, {self.suspend=}, {self._master_df.shape=}, {self._master2_df.shape=}, {self.round_timestamp_freq=}, {self.trade_type=}, ...)"
    
        @property
        def master_df(self) -> pd.DataFrame:
            return self._master_df
    
        @property
        def master2_df(self) -> pd.DataFrame:
            return self._master2_df
    
        @master_df.setter
        def master_df(self, master_df: pd.DataFrame) -> None:
            if master_df.shape[0] > 0:
                cols = master_df.columns 
                if not 'timestamp' in cols:
                    raise KeyError(f"master_df['timestamp'] column not found: {cols=}")         
            self._master_df = master_df.copy()
    
        @master2_df.setter
        def master2_df(self, master2_df: pd.DataFrame) -> None:
            if master2_df.shape[0] > 0:
                cols = master2_df.columns         
                if not 'time' in cols:
                    raise KeyError(f"master2_df['time'] column not found: {cols=}")         
            self._master2_df = master2_df.copy()

    click image to zoom!
    図3-1
    図3-1ではCoinクラスのインスタントを生成してデフォルトのプロパティを表示しています。 さらにCoinクラスの「master_df」プロパティを設定しています。 「master_df.shape」の内容が(0,0)から(1,2)に変わっています。 ちなみに、shapeではDataFrameのレコード件数(行数)とカラム数(列数)が表示されます。

    ここでは意図的にDataFrameのカラム名を「timestamp」から「timestamp test」に書き換えて、 「master_df」プロパティにDataFrameを設定しています。 この場合、setterでエラーを検出してraiseで「KeyError」を発生させています。


    click image to zoom!
    図3-2
    図3-2ではCoinクラスの「master2_df」プロパティに対して図3-1と同じ処理をしています。 ちなみにに、master_dfプロパティにはGMOコインから取得した取引データが格納されているPandasのDataFrameを保存します。 そして、master2_dfプロパティには取引データを加工したあとのDataFrameを保存します。


  4. Gmo_dataframeクラスを作成する

    ここでは、BOTで使用するさまざまなファイルの構造を定義しているGmo_dataframeクラスを作成します。 Gmo_dataframeクラスには、Csvクラスで作成するCSVファイル、 BuySellクラス、SellBuyクラスで作成するExcelファイルなどの構造が定義されています。 ちなみに、BOTが作成するCSVファイルにはヘッダーが含まれていません。 なので、Pandasのread_csv()メソッドでCSVファイルを取り込むときはヘッダーなし「header=none」を指定します。

    base.py:
    #################### GMO dataframe class
    class Gmo_dataframe: 
    
        ### GMO Master DataFrame
        GMO_MASTER = [
            'price',
            'side',
            'size',
            'timestamp'        
        ]
    
        ### GMO Maser DataFrame2
        GMO_MASTER2 = [
            'time',
            'open',
            'close',
            'low',
            'high',
            'price',
            'buy_price',
            'sell_price',
            'side',
            'size',
            'symbol', 
            'trend',
            'pct_change'        
        ]   
        
        :::

    click image to zoom!
    図4
    図4では、Pandasのread_csv()メソッドで「buy_order(BTC_JPY).csv」ファイルを取り込んでDataFrameに格納しています。 このCSVファイルにはヘッダーがないので、Gmo_dataframeクラスの「BUY_ORDER」の内容でDataFrameのカラム名(列名)を書き換えています。 これで通常のヘッダー付きのCSVファイルを取り込んだのと同じ状態になります。


  5. Baseクラスの全てを掲載

    
    # base.py
    
    """
    Bcolors class
    Gvar class
    Coin class
    Gmo_dataframe class
    """
    
    import numpy as np
    import datetime as dt
    import pandas as pd
    
    ############## Bot colors class
    class Bcolors:
        HEADER = '\033[95m'     # bright magenta
        OKBLUE = '\033[94m'     # bright blue
        OKCYAN = '\033[96m'     # bright cyan
        OKGREEN = '\033[92m'    # bright green
        WARNING = '\033[93m'    # bright yellow
        FAIL = '\033[91m'       # bright red
        ENDC = '\033[0m'        # reset
        BOLD = '\033[1m'        # bold
        UNDERLINE = '\033[4m'   # underline
    
    ########### Global variables
    class Gvar:    
        ###########################
        def __init__(self) -> None:
            self.callers_method_name = '_main_:'
            self.domain = 'desktop-pc'  # desktop_pc or note_pc
            self.exchange = 'bot'       # 'bot' or 'bot1' or 'bot2' ....       
            self.folder_gmo =  f'{self.domain}/{self.exchange}/'    # desktop-pc/bot/ 
            self.real_mode = False    
            self.reset_mode = True
            self.debug_mode = True        
    
            self.coin_list = [Coin()]  
    
            self.althorithm_id = 0      
            self.stoploss_method_id = 0
            self.buysell_condition = 0         
            self.sellbuy_condition = 0                            
    
            self.close_pending_orders_freq = 0
            self.close_pending_orders = False  
            self.close_pending_orders_request = False        
            self.cancel_pending_orders = False        
    
            self.buy_count = 0     
            self.sell_count = 0  
            self.stop_loss_count = 0     
            self.buy_cancel_count = 0            
            self.sell_cancel_count = 0
                         
            self.stop_loss = False                                    
    
            self.debug_mode_debug1 = False      
            self.debug_mode_debug2 = False           
    
        #########################
        def __str__(self) -> str:
            return f"Gvar({self.domain=}, {self.exchange=}, {self.folder_gmo=}, {self.real_mode=}, {self.reset_mode=}, {self.stop_loss=}, {self.debug_mode=}, ...)"
    
    ########### Coin class
    class Coin:
        ###########################
        def __init__(self) -> None:
            self.symbol = 'BTC_JPY'            
            self.suspend = False  
            self.master_csv_not_found = False      
    
            self.master_df = pd.DataFrame()     
            self.master2_df = pd.DataFrame()      
    
            self.real_mode = False              # True of False
            self.fake_or_real = 'fake'          # 'fake' or 'real'
            self.trade_type = 'buy_sell'        # 'buy_sell' or 'sell_buy'
            self.suspend_new_order = False
    
            self.qty = 0.01
    
            self.round_timestamp_freq = '1S'    # freq: H-hour, T-minute, S-seconds, L-milliseconds : new version 0X - skip round timestamp
            self.api_retry_limit_count = 6      # GMO api retry limit count
            self.api_market_timeout_limit = 100 # 100 seconds
        
            self.profit_rate = 1.0015           # default profit rate (0.0015 + 1)     
            self.leverage_fee_rate = 0.0004     # default fee leverage rate 0.0004
            self.stop_loss_rate = 0.0200        # default stop loss rate 0.0200   
    
            self.trend_search_count = 3         
            self.close_count_limit = 7          
    
            self.qty_decimal = 2
            self.price_decimal = 0          
    
            self.cancel_pending_orders = False
    
            self.debug_mode = False
            self.debug_mode_stop_loss = False 
            self.debug_mode_cancel_pending_orders = False
    
            self.buy_count = 0  
            self.sell_count = 0  
            self.stop_loss_count = 0   
            self.buy_cancel_count = 0  
            self.sell_cancel_count = 0
    
            self.buy_position_list = []
            self.sell_position_list = []
    
            self.position_count_0_10 = 0        
            self.position_count_11_30 = 0       
            self.position_count_31_50 = 0      
            self.position_count_51_70 = 0      
            self.position_count_71_90 = 0      
            self.position_count_91_999 = 0        
    
            self.buy_sell_lc5   = 0
            self.buy_sell_lc10  = 0
            self.buy_sell_lc15  = 0
            self.buy_sell_lc18  = 0
            self.buy_sell_lc20  = 0        
            self.buy_sell_lc23  = 0   
            self.buy_sell_lc25  = 0                   
    
            self.sell_buy_lc5   = 0
            self.sell_buy_lc10  = 0
            self.sell_buy_lc15  = 0
            self.sell_buy_lc18  = 0
            self.sell_buy_lc20  = 0   
            self.sell_buy_lc23  = 0 
            self.sell_buy_lc25  = 0                                 
    
            self.buy_sell_action12 = False
            self.buy_sell_action13 = False
            self.buy_sell_action14 = False
            self.buy_sell_action15 = False
            self.buy_sell_action21 = False
            self.buy_sell_action22 = False
            self.buy_sell_action23 = False
            self.buy_sell_action24 = False
            self.buy_sell_action25 = False
            self.buy_sell_action26 = False
            self.buy_sell_action32_1 = False
            self.buy_sell_action32_2 = False
            self.buy_sell_action33_1 = False
            self.buy_sell_action33_2 = False 
            self.buy_sell_action41 = False          
    
            self.sell_buy_action12 = False
            self.sell_buy_action13 = False
            self.sell_buy_action14 = False
            self.sell_buy_action15 = False
            self.sell_buy_action21 = False
            self.sell_buy_action22 = False
            self.sell_buy_action23 = False
            self.sell_buy_action24 = False
            self.sell_buy_action25 = False
            self.sell_buy_action26 = False
            self.sell_buy_action32_1 = False
            self.sell_buy_action32_2 = False
            self.sell_buy_action33_1 = False
            self.sell_buy_action33_2 = False 
            self.sell_buy_action41 = False           
       
        #########################
        def __str__(self) -> str:
            return f"Coin({self.symbol=}, {self.suspend=}, {self._master_df.shape=}, {self._master2_df.shape=}, {self.round_timestamp_freq=}, {self.trade_type=}, ...)"
            
        @property
        def master_df(self) -> pd.DataFrame:
            return self._master_df    
    
        @property
        def master2_df(self) -> pd.DataFrame:
            return self._master2_df
    
        @master_df.setter
        def master_df(self, master_df: pd.DataFrame) -> None:
            if master_df.shape[0] > 0:
                cols = master_df.columns 
                if not 'timestamp' in cols:
                    raise KeyError(f"master_df['timestamp'] column not found: {cols=}")         
            self._master_df = master_df.copy()
    
        @master2_df.setter
        def master2_df(self, master2_df: pd.DataFrame) -> None:
            if master2_df.shape[0] > 0:
                cols = master2_df.columns         
                if not 'time' in cols:
                    raise KeyError(f"master2_df['time'] column not found: {cols=}")         
            self._master2_df = master2_df.copy()
    
    #################### GMO dataframe class
    class Gmo_dataframe: 
    
        ### GMO Master DataFrame
        GMO_MASTER = [
            'price',
            'side',
            'size',
            'timestamp'        
        ]
    
        ### GMO Maser DataFrame2
        GMO_MASTER2 = [
            'time',
            'open',
            'close',
            'low',
            'high',
            'price',
            'buy_price',
            'sell_price',
            'side',
            'size',
            'symbol', 
            'trend',
            'pct_change'        
        ]
       
        ### Order DataFrame
        ORDER_CSV = [
            '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',
            'real_profit',
            'accumulated_leverage_fee',
            'close_count',
            'close_update_date',
            'order_closed',
            'stop_loss',
            'cancelled',
            'order_open_date',
            'order_close_date',
            'log_time'
        ]                   
        
        ORDER_XLSX = [
            'time(B)',
            'time(S)',
            'order(B)',
            'order(S)',
            'qty(B)',
            'qty(S)',
            'price(B)',
            'price(S)',
            'fee(B)',
            'fee(S)',
            'buy(SUM)',
            'sell(SUM)',
            'profit(AMT)',
            'profit(%)',
            'fee(L)',       # leverage fee
            'count(L)',     # leverage fee count
            'close(L)',     # leverage fee close date
            'closed',
            'stop',
            'cancel',
            'open',
            'close',
            'log_time'      # update every time
        ]     
    
        ### Buy Order DataFrame 
        BUY_ORDER = [
            'buy_time',
            'qty',
            'price',
            'real_qty',
            'real_price',
            'real_fee',
            'sell_time',
            'log_time'     
        ]    
    
        ### Buy Order Sub DataFrame 
        BUY_ORDER_SUB = [
            'buy_time',
            'buy_order_id',
            'position_id',
            'qty',
            'price',
            'real_qty',
            'real_price',
            'real_fee',
            'get_price_count',
            'sold',
            'stop_loss',
            'cancelled',
            'sell_time',
            'sell_order_id',
            'log_time'
        ]
    
        ### Buy Order Child DataFrame 
        BUY_ORDER_CHILD = [
            'buy_time',
            'buy_order_id',
            'position_id',
            'qty',
            'price',
            'real_qty',
            'real_price',
            'real_fee',
            'sell_time',
            'sell_order_id',
            'log_time'        
        ]
    
        ### Sell Order DataFrame  
        SELL_ORDER = [
            'sell_time',
            'qty',
            'price',
            'real_qty',
            'real_price',
            'real_fee',
            'buy_time',
            'log_time'        
        ]
    
        ### Sell Order Sub DataFrame  
        SELL_ORDER_SUB = [
            'sell_time',
            'sell_order_id',
            'position_id',
            'qty',
            'price',
            'real_qty',
            'real_price',
            'real_fee',
            'get_price_count',
            'bought',
            'stop_loss',
            'cancelled',
            'buy_time',
            'buy_order_id',
            'log_time'        
        ]
    
        ### Sell Order Child DataFrame 
        SELL_ORDER_CHILD = [
            'sell_time',
            'sell_order_id',
            'position_id',
            'qty',
            'price',
            'real_qty',
            'real_price',
            'real_fee',
            'buy_time',
            'buy_order_id',
            'log_time'        
        ]