仮想通貨BOTを作るために、これまでに以下の様な事を検討したり、実際にプログラムを作成したりしてきました。
■検討した事(抜粋)
・仮想通貨BOTの全体像
・BOTのソースの管理方法
・システムトレードの考え方の基礎
■作成したプログラム(抜粋)
・OHLCデータを取得するプログラム
・取引所の注文関連の操作(注文、手仕舞い、約定確認等)をするプログラム
・シグナルを判定するプログラム
さて、ここからはこれまでに検討した内容や、プログラムをパズルの様に組み合わせて、「仮想通貨BOTの全体像」を作成しましょう。
ここからは、プログラムの全体像を考えながら、まとめていく部分ですね。今回のプログラムが完成すれば簡単なバックテストも可能になります。
※バックテスト結果の集計まではしません
プログラムをバラバラに作成して、最終的に一つに組み合わせるというのは、システムエンジニアの現場でもよく行われる事です。さすがにもっともっと大きな規模ですが^^;
コンテンツ
仮想通貨BOT(全体像)の検討
これまでに作成したプログラムを組み合わせていきます。
プログラムを組み合わせる上で大事なポイントを挙げておきます。
重要なポイント
・注文、ポジション有無、手仕舞いなどの処理ステータスを管理する「変数」を定義
・シグナル判定処理、データ取得処理などの処理はまとめて「関数化」する
・main関数の中で「ループ」処理を行う(一定時間毎に処理を繰り返す)
処理の流れ
実際に処理の流れを考えていきましょう。
・必要ライブラリ、モジュールの読み込み
・初期化処理(変数の定義、値の設定等)
・ループ(1回分開始)↓
・初期化処理(変数の定義、値の設定等)
・データ取得(APIを使って口座情報や、OHLC情報の取得)※今回の例ではループの外で取得
・シグナルの判定(売買のポイントがあるか?)
・(シグナルが出たら)注文を入れる
・(注文が入っている場合)注文が通ったかを確認
・(注文が通っている場合)約定したかどうかを確認
・(約定している場合)手仕舞いの判定
・(手仕舞いのシグナルが出たら)手仕舞い注文をする
・ループ(1回分終了)↑
ざっくりと言うと、この様な流れでしょうか。
もちろん細かい部分を言えば、手を入れなければならないポイントはたくさんありますが、現状は大まかな処理の流れを考えておけばOKです。
課題
また、今回実装するかどうかに関わらず、以下の様な課題も洗い出しておきましょう。
・購入時の価格はどの様に決めるのか?
・成行/指値は使い分けるのか?
・サーバーエラーが発生した際はどうするか?
・取引所はどこを使うか?
・連続して注文が通らなかった場合はどうするか?
・約定せずに残ったままの注文はどうするか?
この様な課題なども踏まえて、今使わなくとも、後で必要そうな関数は全て作成しておきましょう。
「シグナル判定&売買」の作成!
あんまり考え過ぎてもいけませんので、現状できる範囲でプログラムを作成していきましょう。
と、その前にプログラムを作る上での前提を決めておきましょう。すぐに全ての事は実現できませんので、必要な箇所から実装する形でOKです。
プログラムの着地点
今回は実際に売買などは行わずに、cryptowatchから取得したデータに対して、シグナルの判定をしたり、注文を出したり、という事をシュミレートしていきたいと思います。
そのため、以下の様な制約を設けてプログラムを作ります。
■制約
・注文関連の処理(注文、手仕舞い、約定確認等)はコメント化する
・注文関連のエラーチェックは行わない。そのため、注文が通らなかった場合、約定しなかった場合はそのまま処理を継続する。決済注文の場合も同じ考え方とする。
・cryptowatchからの読み込みはプログラムの一番最初に1回だけ行う。ループの中では行わないため、実際に稼働させるプログラムとは記載が若干異なる。
■前提条件
・bitFlyerの過去のデータをcryptowatch APIから取得する
・2018/3/1~2018/5/31のデータを対象とする
・ローソク足は日足の情報を基にしてシグナル判定を行う
プログラムを部分的に作成する
では実際にプログラムを作成しましょう。
全体的なプログラムを作る前に、部分的にプログラムの解説をします。
これまでに作成している内容もありますので、その部分はおさらいだけしておきます。
ライブラリ、モジュールの読み込み
プログラムの先頭で必要ライブラリ・モジュールの読み込みを行います。
この点はプログラムの内容と照らし合わせて、必要なライブラリ等をimportすればOKです。
変数の定義
trade_infoという変数を定義して、その中で、
・注文のシグナルが出ているか
・ポジションを持っているか
・決済注文のシグナルが出ているか
などを定義します。
※今回は使用しませんが、注文の有無状態を保持する変数も定義しています。
データの取得
今回は口座情報の取得は行いません。
cryptowatchからOHLCデータ(日足)のみ取得を行います。
シグナルの判定
今回は以前作成した、チャネルブレイクアウトのシグナルを使います。
チャネルブレイクアウトでは「ドテン買い、ドテン売り」というテクニックがあります。
ドテン買い・売りというのは
・過去○○期間の高値を超えたら買い、さらに売りポジションを持っている場合は手仕舞い
・過去○○期間の安値を超えたら売り、さらに買いポジションを持っている場合は手仕舞い
という手法になります。
そのため、新規注文の場合と、手仕舞い(決済)のタイミングが同じになります。
注文売買処理
売買処理(注文・手仕舞い)を行う際には、注文が通っているかの確認、約定しているかの確認が必要というお話をしたと思います。
そのため、一定の確率で注文に失敗、注文が通ったけど約定していないなどのケースを盛り込む事にしました。テスト用の関数をいくつか作成して、実際のBOTの処理が多少はイメージできる様に工夫しています。
注文の成功/失敗の有無はどれくらいの確率で発生するかは稼働させてみなければ分かりません。ここでは乱数を発生(テスト用関数名:test_rand_OK)させて約10%の確率で注文に失敗、約定していない処理の流れも作成しています。
以下が買い注文を行う際の処理の流れです。
1)買い注文を行う
2)注文が通ったかどうかを判定
3)注文が通ったら約定しているかどうかを判定
4)約定したら、買いポジションを持つ(trade_info[“position_buy_flag”]=1にする)
■Pythonコード(買い注文関連)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#買い注文を入れる関数 def test_func_buy(trade_info): #注文が通っているか確認 #買い注文が約定しているか確認 return trade_info #買い注文が約定しているか確認する関数 def test_fung_buy_contract(trade_info): if test_rand_OK() == 1: #注文が約定した場合 trade_info["position_buy_flag"] = 1 else: #注文が約定しなかった場合 return trade_info #乱数を発生させる関数 def test_rand_OK(): if random.random() <= 0.9: #random()は0.0~1.0未満の値を発生させる res = 1 else: res = 0 return res #注文が通っているか確認する関数 def test_func_ck_order(): if test_rand_OK() == 1: #注文が入った場合 res = 1 else: #注文が入らなかった場合 res = 0 return res |
全体処理
最終的に上記の処理を組み合わせていきます。
1回だけで済む処理はループの外で行います。
ローソク足を読み込むたびに実施する処理はループの中で行っていきます。
また、何度も実施する処理は、ある程度の処理をまとめて関数化して呼び出す様にします。
プログラムを組み合わせてBOTの全体像を作ろう!
以下が部分的なプログラムを組み合わせた結果になります。
これまでのコード量と比べると、かなり長~いプログラムになっていますね^^
■Pythonコード(全体像)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
import requests from datetime import datetime from datetime import timedelta import pandas as pd import random #トレードの状態を保持する変数 trade_info = { "order_buy_flag": 0, "order_sell_flag": 0, "position_buy_flag": 0, "position_sell_flag": 0, "close_buy_flag": 0, "close_sell_flag": 0, "no_order_flag": 0 } #OHLCデータ取得関数 def get_data(min, before=0, after=0): #パラメータ設定する params = {"periods" : min } if before != 0: params["before"] = before if after != 0: params["after"] = after #リクエスト送信 response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcjpy/ohlc",params) data = response.json() return data #過去20日分の高値と安値を算出する関数 def judge_high_and_low(df,ref_date,period_day): for j in range(len(df)): if str(df['Time'][j]) == str(ref_date): #DataFrameの中に「基準日」を見つけた! s_idx = j - (period_day-1) #20日前の日のインデックス ※基準日を含むため-1 if s_idx >= 0 : #DataFrameの範囲内(最低限の判定) return df[s_idx:j]['high'].max(), df[s_idx:j]['low'].min()#過去20日分の高値と安値 break #終値を算出する関数 def get_close_price(df,ref_date): for j in range(len(df)): if str(df['Time'][j]) == str(ref_date): #DataFrameの中に「基準日」を見つけた! return df['close'][j] #終値を返す #【新規の注文に関するテスト用関数】 #【テスト用】買い注文を入れる関数 def test_func_buy(trade_info): print("買い注文を入れます。") if test_func_ck_order() == 1: trade_info = test_fung_buy_contract(trade_info) return trade_info #【テスト用】売り注文を入れる関数 def test_func_sell(trade_info): print("売り注文を入れます。") if test_func_ck_order() == 1: trade_info = test_fung_sell_contract(trade_info) return trade_info #【テスト用】買い注文が約定しているか確認する関数 def test_fung_buy_contract(trade_info): print("買い注文の約定確認をします") if test_rand_OK() == 1: print("買い注文が約定しました。") trade_info["position_buy_flag"] = 1 else: print("買い注文が約定しませんでした。") return trade_info #【テスト用】売り注文が約定しているか確認する関数 def test_fung_sell_contract(trade_info): print("売り注文の約定確認をします") if test_rand_OK() == 1: print("売り注文が約定しました。") trade_info["position_sell_flag"] = 1 else: print("売り注文が約定しませんでした。") return trade_info #【手仕舞い注文に関するテスト用関数】 #【テスト用】買い注文を入れる関数 def test_func_buy_2(trade_info): print("買い注文(売り決済)を入れます。") if test_func_ck_order() == 1: trade_info = test_fung_buy_contract_2(trade_info) return trade_info #【テスト用】売り注文を入れる関数 def test_func_sell_2(trade_info): print("売り注文(買い決済)を入れます。") if test_func_ck_order() == 1: trade_info = test_fung_sell_contract_2(trade_info) return trade_info #【テスト用】買い注文が約定しているか確認する関数 def test_fung_buy_contract_2(trade_info): print("買い注文(売り決済)の約定確認をします") if test_rand_OK() == 1: print("買い注文(売り決済)が約定しました。") trade_info["position_sell_flag"] = 0 else: print("買い注文(売り決済)が約定しませんでした。") return trade_info #【テスト用】売り注文が約定しているか確認する関数 def test_fung_sell_contract_2(trade_info): print("売り注文(買い決済)の約定確認をします") if test_rand_OK() == 1: print("売り注文(買い決済)が約定しました。") trade_info["position_buy_flag"] = 0 else: print("売り注文(買い決済)が約定しませんでした。") return trade_info #【その他のテスト用関数】 #【テスト用】乱数を発生させる def test_rand_OK(): if random.random() <= 0.9: #random()は0.0~1.0未満の値を発生させる res = 1 else: res = 0 return res #【テスト用】注文が通っているか確認する関数 def test_func_ck_order(): print("注文が通っているか確認します。") if test_rand_OK() == 1: print("注文が入りました。") res = 1 else: print("注文が入りませんでした。") res = 0 return res #main処理 #変数の定義 min = 86400 #1日(86400秒) after = datetime(2018, 3, 1).strftime('%s') #OHLCの取得開始日 before = datetime(2018, 5, 31).strftime('%s') #OHLCの取得終了日 ref_date = datetime(2018, 3, 20) #判定開始日 period_day = 20 #チャネルブレイクのレンジ close_price_work = 0 #終値の処理用変数 #OHLCデータの取得(テストなので1回だけ取得する) out_data = get_data(min,before,after) Time_Data, Open_price, High_price, Low_price, Close_price = [],[],[],[],[] for ohlc in out_data["result"][str(min)]: Time_Data.append(datetime.fromtimestamp(ohlc[0])) Open_price.append(ohlc[1]) High_price.append(ohlc[2]) Low_price.append(ohlc[3]) Close_price.append(ohlc[4]) df = pd.DataFrame({'Time':Time_Data, 'open':Open_price, 'high':High_price, 'low':Low_price, 'close':Close_price}) #ループ開始 while ref_date <= datetime(2018, 5, 31): #シグナル判定に必要な情報の収集 high_val,low_val = judge_high_and_low(df,ref_date,period_day) #過去20日間の高値と安値を取得 close_price_work = get_close_price(df,ref_date) #基準日の終値を取得 print(str(ref_date) + " : 過去" + str(period_day) +"日間の高値:" + str(high_val) + " 安値:" + str(low_val)) #シグナルの判定 #買い注文のシグナル判定 if high_val < close_price_work: print("終値が過去20日間の高値を上抜け。終値は:" + str(close_price_work)) trade_info["order_buy_flag"] = 1 #この後の処理で買い注文を出す if trade_info["position_sell_flag"] == 1: #売りポジションを持っている trade_info["close_sell_flag"] = 1 #この後の処理で売りポジションを決済する #売り注文のシグナル判定 elif low_val > close_price_work: print("終値が過去20日間の安値を下抜け。終値は:" + str(close_price_work)) trade_info["order_sell_flag"] = 1 #この後の処理で売り注文を出す if trade_info["position_buy_flag"] == 1: #買いポジションを持っている trade_info["close_buy_flag"] = 1 #この後の処理で買いポジションを決済する #売買注文を行う #買い注文のシグナルが出ている場合 if trade_info["order_buy_flag"] == 1: test_func_buy(trade_info) #買い注文を入れる trade_info["order_buy_flag"] = 0 #注文が通る通らないに関わらず、注文要求は終了 #売り注文のシグナルが出ている場合 elif trade_info["order_sell_flag"] == 1: test_func_sell(trade_info) #売り注文を入れる trade_info["order_sell_flag"] = 0 #注文が通る通らないに関わらず、注文要求は終了 #手仕舞いの売買注文を行う #買い注文の手仕舞いシグナルが出ている場合 if trade_info["close_buy_flag"] == 1: test_func_sell_2(trade_info) #売り注文を入れる trade_info["close_buy_flag"] = 0 #注文が通る通らないに関わらず、注文要求は終了 #売り注文の手仕舞いシグナルが出ている場合 elif trade_info["close_sell_flag"] == 1: test_func_buy_2(trade_info) #買い注文を入れる trade_info["close_sell_flag"] = 0 #注文が通る通らないに関わらず、注文要求は終了 #次の日のデータを読み込み ref_date = ref_date + timedelta(days=1) |
■実行結果(抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#売り注文が約定した場合 2018-03-30 00:00:00 : 過去20日間の高値:1062040.0 安値:777870.0 終値が過去20日間の安値を下抜け。終値は:750130.0 売り注文を入れます。 注文が通っているか確認します。 注文が入りました。 売り注文の約定確認をします 売り注文が約定しました。 #買い注文が約定しない、売り注文の決済(買い注文)が約定した場合 2018-04-16 00:00:00 : 過去20日間の高値:884400.0 安値:682660.0 終値が過去20日間の高値を上抜け。終値は:900005.0 買い注文を入れます。 注文が通っているか確認します。 注文が入りました。 買い注文の約定確認をします 買い注文が約定しませんでした。 買い注文(売り決済)を入れます。 注文が通っているか確認します。 注文が入りました。 買い注文(売り決済)の約定確認をします 買い注文(売り決済)が約定しました。 #買い注文が通らない場合 2018-04-22 00:00:00 : 過去20日間の高値:956220.0 安値:700372.0 終値が過去20日間の高値を上抜け。終値は:960000.0 買い注文を入れます。 注文が通っているか確認します。 注文が入りませんでした。 #買い注文が約定した場合 2018-04-25 00:00:00 : 過去20日間の高値:989057.0 安値:700372.0 終値が過去20日間の高値を上抜け。終値は:1065195.0 買い注文を入れます。 注文が通っているか確認します。 注文が入りました。 買い注文の約定確認をします 買い注文が約定しました。 #売り注文が約定、買い注文の決済(売り注文)が約定に失敗した場合 2018-05-12 00:00:00 : 過去20日間の高値:1083035.0 安値:920001.0 終値が過去20日間の安値を下抜け。終値は:913999.0 売り注文を入れます。 注文が通っているか確認します。 注文が入りました。 売り注文の約定確認をします 売り注文が約定しました。 売り注文(買い決済)を入れます。 注文が通っているか確認します。 注文が入りました。 売り注文(買い決済)の約定確認をします 売り注文(買い決済)が約定しませんでした。 |
まとめ
これまでに比べるとコード量、情報量が増えているのでこんがらがってしまうかもしれませんが、一つ一つの処理はシンプルなものです。
さて、今回シグナルの判定と、実際の売買注文(イメージ)を組み合わせてみました。
実は、ここまでで仮想通貨BOTの全体像は大体完成です。まだ埋まってない処理があるよと思うかもしれませんが、必要な処理を追加したり、削除したりというのは、永遠にやっていかなければならない作業です。
それに開発者からすれば、実際に使用するコードよりも、処理フローを理解する事の方がずっと大切です。処理フローはしっかりと押さえておきましょう。