この記事では仮想通貨取引Botの素となる抽象クラスの実装について備忘録を兼ねて解説します。
Botを動かす環境
Botを動かす環境の設計思想として、使用するBotのクラスを変えるだけで簡単に戦略を変えることができるようにしています。そのために売買判断など、どのBotに共通する機能は抽象クラスで定義します。環境上ではその抽象クラスを継承したBotを使い、抽象クラスで定義したメソッドだけを呼び出すようにします。
処理のイメージは次の通りです。
bot = BollingerBandBot() # Botを初期化 while True: today_str = datetime.today().strftime("%Y%m%d") # 今日の日付を取得 ohlc = get_ohlc(pair, yyyymmdd) # OHLC情報(始値, 高値, 低値, 終値)を取得 bot.update_info(*ohlc) # 取得した情報でBotの内部情報を更新 action = bot.get_action() # 更新したBotの内部情報に基づいて行動を選択 transaction(action) # 選択した行動に従って売買処理 time.sleep(5*60) # 5分待つ
この記事ではこの環境で動かすBotが継承する抽象クラスについて説明します。
抽象クラス- AbstractBot-
ここではBotに共通するメソッドや変数を抽象クラス「AbstractBot」に定義します。取引判断に使用するBotはこのクラスを継承して使用することになります。以下で詳細な説明をします。
インスタンス変数
抽象クラスAbstractBotにはbitbankのAPIから取得できる基本的な値を保持する変数を持たせます。具体的には次の通りです。
- open_prices: 始値のリスト
- high_prices: 高値のリスト
- low_prices: 低値のリスト
- close_prices: 終値のリスト
- window_size: 上記のリストの長さの最大値(インスタンス生成時に設定)
始値や高値のリストにはAPIから情報を取得するたびに値を追加していきます。
説明した部分の実装は次の通りです。
from abc import ABCMeta, abstractmethod # 抽象クラス class AbstractBot(metaclass=ABCMeta): def __init__(self, window_size=10): self.window_size = window_size self.open_prices = [] self.high_prices = [] self.low_prices = [] self.close_prices = []
クラスメソッド
抽象クラスAbstractBotには次のメソッドを持たせます。
- update_info: 入力した情報をインスタンス変数に追加
- calc_values: インスタンス変数の情報を基に戦略に応じた値を計算
- get_action: インスタンス変数の値に応じて売買判断を実施
これらのメソッドの詳細説明および実装は次で行います。
情報更新メソッド-update_info-
ここではAPIから取得した情報をインスタンス変数に格納するメソッド「update_info」の説明を行います。
このメソッドでは、APIから取得した始値、高値、低値、終値を受け取って、上で紹介したそれぞれのリストに格納します。その際にリストの最大長を超えた場合には一番古い情報を捨てます。
リストの長さが最大長に達したときに、戦略に使用する値を計算するメソッド「calc_values」(詳細は後述)を呼び出します。
class AbstractBot(metaclass=ABCMeta): def __init__(self, window_size=10): # 省略 def update_info(self, open_price, high, low, close_price): """ OHLCデータを取り込み、情報を更新する :param open_price: 始値 :param high: 高値 :param low: 低値 :param close_price: 終値 """ self.open_prices.append(open_price) self.high_prices.append(high) self.low_prices.append(low) self.close_prices.append(close_price) if len(self.high_prices) > self.window_size: # window sizeになったら最初の値は捨てる self.high_prices = self.high_prices[1:] self.low_prices = self.low_prices[1:] self.open_prices = self.open_prices[1:] self.close_prices = self.close_prices[1:] # 戦略用の値を計算 self.calc_values()
売買判断指標計算メソッド-calc_values-
ここでは売買判断を行うための指標を計算するメソッド「calc_values」について説明を行います。
このメソッドはインスタンス変数に格納されたOHLCからテクニカル指標など、戦略に使用する値を計算します。また、このメソッドはOHLCの長さがwindow_sizeに達したときに呼ばれる(「情報更新メソッド-update_info-」を参照)ので、例えばローソク足10本文の移動平均を使った戦略を使いたい場合はwindow_sizeに10を設定して、このメソッドには価格の平均値を計算する処理を書くことになります。ここでは指標を計算するだけなので、計算結果を格納するインスタンス変数を適宜用意しておく必要があります。
この抽象クラスには具体的な戦略を持たせないので、何もしないメソッドを抽象メソッドとして定義します。具体的なBotクラスを定義する際には、戦略に応じた値を計算するメソッドでオーバーライドさせます。
class AbstractBot(metaclass=ABCMeta): def __init__(self, window_size=10): # 省略 def update_info(self, open_price, high, low, close_price): # 省略 @abstractmethod def calc_values(self): """ 戦略に応じた値を計算する """ pass
例えば、Bollinger Bandによる戦略を採用する場合には、売買判断に価格の平均値と標準偏差を使用するので、このメソッドでそれらの計算を行うことになります。実装例は次の通りです。(そのうち詳細を説明した記事を書く予定)
class BollingerBandBot(AbstractBot): def __init__(self, window_size): super(BollingerBandBot, self).__init__(window_size) self.sigma = 0.0 # 終値の標準偏差 self.means = 0.0 # 終値の平均値 def calc_values(self): """ 戦略に応じた値を計算する Bollinger Band用の標準偏差を計算する """ self.sigma = statistics.stdev(self.close_prices) self.means = statistics.mean(self.close_prices)
売買判断メソッド-get_action-
ここでは計算した指標から売買判断を行うメソッド「get_action」について説明を行います。
このメソッドでは上記で説明したメソッドcalc_valuesで計算した値を参照して売買の判断を行い、売買判断結果と数量のペアを返します。売買判断結果は
- buy: 購入する
- sell: 売却する
- pass: 見送る
の3種類のいずれかを設定します。例えばこのメソッドが["buy", 30.0]を返したときには数量30.0だけ購入することを表します。
この抽象クラスでは特定の戦略を持たせないので何もしないメソッドを抽象メソッドとして定義しますが、具体的なBotクラスを定義する際には、戦略に応じた判断基準を書いたメソッドでオーバーライドさせます。
class AbstractBot(metaclass=ABCMeta): def __init__(self, window_size=10): # 省略 def update_info(self, open_price, high, low, close_price): # 省略 @abstractmethod def calc_values(self): # 省略 @abstractmethod def get_action(self): """ 行動を出力する。形式は次の通り [行動, 数量] 行動は"buy", "sell", "pass"のいずれか """ pass
ソースコード
ここでは今回説明した抽象クラス「AbstractBot」のソースコード全体を載せます。
from abc import ABCMeta, abstractmethod # 抽象クラス class AbstractBot(metaclass=ABCMeta): def __init__(self, window_size=10): self.window_size = window_size self.open_prices = [] self.high_prices = [] self.low_prices = [] self.close_prices = [] def update_info(self, open_price, high, low, close_price): """ OHLCデータを取り込み、情報を更新する :param open_price: 始値 :param high: 高値 :param low: 低値 :param close_price: 終値 """ self.open_prices.append(open_price) self.high_prices.append(high) self.low_prices.append(low) self.close_prices.append(close_price) if len(self.high_prices) > self.window_size: # window sizeになったら最初の値は捨てる self.high_prices = self.high_prices[1:] self.low_prices = self.low_prices[1:] self.open_prices = self.open_prices[1:] self.close_prices = self.close_prices[1:] # 戦略用の値を計算 self.calc_values() @abstractmethod def calc_values(self): """ 戦略に応じた値を計算する """ pass @abstractmethod def get_action(self): """ 行動を出力する。形式は次の通り [行動, 数量] 行動は"buy", "sell", "pass"のいずれか """ pass