先人様の知恵がPubnub仕様で作成されており現在動かなくなっていたので改造
約定情報の出来高を利用したものがbitflyerの情報提供の終了により動かなくなっていたので改造してみました
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 |
import websocket import json import pandas as pd import pybitflyer import sys from datetime import datetime, timezone, timedelta #設定==================================================== #取得したAPIキーを入力 api_key = "" sec_key = "" #注文するロット数(最低0.01以上) lot = 0.01 # 判断に使うデータを保持する秒数 store_time_sec = 30 # BUYとSELLの乖離が何%でエントリーするか triger_percent = 200 CHANNEL = "lightning_executions_FX_BTC_JPY" #======================================================== # bitFlyer API setting public_api = pybitflyer.API() api = pybitflyer.API(api_key=api_key, api_secret=sec_key) # プログラム起動時のbitFlyerのポジション情報取得 bf_positions = pd.DataFrame(api.getpositions(product_code='FX_BTC_JPY')) local_position = 'NONE' local_position_price = 0 if not(bf_positions.empty): local_position = bf_positions.ix[[0], ['side']].values.flatten() local_position_price = int(bf_positions.ix[[0], ['price']].values.flatten()) sum_profit = 0 # dataframe for executions df_all = pd.DataFrame(index=['datetime'], columns=['id', 'side', 'price', 'size', 'exec_date', 'buy_child_order_acceptance_id', 'sell_child_order_acceptance_id']) # close buy or sell position def close(side, order_size, ex_price): oposit_side = 'NONE' if side == 'BUY': oposit_side = 'SELL' elif side == 'SELL': oposit_side = 'BUY' bf_positions = pd.DataFrame(api.getpositions(product_code='FX_BTC_JPY')) #オーダーがあればポジションを取らない if not(bf_positions.empty): bf_position = bf_positions.ix[[0], ['side']].values.flatten() bf_position_price = int(bf_positions.ix[[0], ['price']].values.flatten()) if bf_position == side: print('[' + side + ' Close]') callback = api.sendchildorder(product_code='FX_BTC_JPY', child_order_type='MARKET', side=oposit_side, size=order_size) print(callback) if not(callback.get('status')): ordered_profit = 0 if side == 'BUY': ordered_profit = (ex_price - bf_position_price) * order_size elif side == 'SELL': ordered_profit = -(ex_price - bf_position_price) * order_size print('Order Complete!', 'ex_price:', ex_price, 'pos_price:', bf_position_price, 'profit:', format(ordered_profit, '.2f')) return 'NONE', ordered_profit else: return side, 0 # entry buy or sell position def entry(side, order_size, ex_price): print('[' + side + ' Entry]') callback = api.sendchildorder(product_code='FX_BTC_JPY', child_order_type='MARKET', side=side, size=order_size) print(callback) if not(callback.get('status')): print('Order Complete!') return side else: return 'NONE' # calc buy and sell volume from lightning_executions_FX_BTC_JPY message def store_executions(message, store_time_sec): global df_all # メッセージデータ取得 params_message = message["params"]["message"] df_new = pd.DataFrame(params_message) # print(df_new) # 約定時間を日本時間に修正 df_new['exec_date'] = pd.to_datetime(df_new['exec_date']) + timedelta(hours=9) # 取得したメッセージを追加 [future error訂正] df_all = df_all.append(df_new,sort=True) # 取得したメッセージ分含め、全てのインデックス値を約定時間の値に更新 df_all.index = df_all['exec_date'] # 最後の約定時間を現在時刻として取得 date_now = df_all.index[len(df_all) - 1] # 現在時間から指定秒数分前までのmessageデータ取得 df_all = df_all.ix[df_all.index >= (date_now - timedelta(seconds=store_time_sec))] # ロングポジションの取引のみに絞って、取引高を合計 buy_vol = df_all[df_all.apply(lambda x: x['side'], axis=1) == 'BUY']['size'].sum(axis=0) #print("buy_vol: %s" % buy_vol) # ショートポジションの取引のみに絞って、取引高を合計 sell_vol = df_all[df_all.apply(lambda x: x['side'], axis=1) == 'SELL']['size'].sum(axis=0) #print("sell_vol: %s" % sell_vol) # 直前の約定金額 ex_price = int(df_all.ix[[len(df_all) - 1], ['price']].values.flatten()) #print("ex_price: %s" % ex_price) return df_all, buy_vol, sell_vol, ex_price def received_message_task(message): global local_position global local_position_price global sum_profit # 注文量 order_size = lot # 新規エントリーを決定する取引高差分(%) entry_triger = triger_percent df, buy_vol, sell_vol, ex_price = store_executions(message, store_time_sec) # 利益と利益率の計算 order_profit = 0 if local_position == 'BUY': order_profit = (ex_price - local_position_price) * order_size elif local_position == 'SELL': order_profit = -(ex_price - local_position_price) * order_size order_profit_rate = order_profit / (ex_price * order_size) # 取引高の反転を見て、ポジションを閉じる if (local_position == 'BUY') and (buy_vol < sell_vol): local_position, ordered_profit = close('BUY', order_size, ex_price) sum_profit = sum_profit + ordered_profit elif (local_position == 'SELL') and (buy_vol > sell_vol): local_position, ordered_profit = close('SELL', order_size, ex_price) sum_profit = sum_profit + ordered_profit # 新規エントリー ポジション無+履歴500以上溜まる if (local_position == 'NONE') and len(df) >= 500: if ((buy_vol / sell_vol) * 100 >= entry_triger): local_position = entry('BUY', order_size, ex_price) if local_position == 'BUY': local_position_price = ex_price elif ((sell_vol / buy_vol) * 100 >= entry_triger): local_position = entry('SELL', order_size, ex_price) if local_position == 'SELL': local_position_price = ex_price # summary print(df.index[len(df) - 1].strftime('%H:%M:%S'), len(df), 'BUY/SELL', format(buy_vol, '.2f'), format(sell_vol, '.2f'), 'PRICE', ex_price, local_position, format(order_profit, '.2f'), format(order_profit_rate, '.4f'), 'SUM_PROFIT', format(sum_profit, '.2f')) def on_message(ws, message): message = json.loads(message) received_message_task(message) def on_open(ws): ws.send(json.dumps({"method": "subscribe", "params": {"channel": CHANNEL}})) def main(): ws = websocket.WebSocketApp("wss://ws.lightstream.bitflyer.com/json-rpc", on_message=on_message, on_open=on_open) ws.run_forever() if __name__ == "__main__": try: main() except KeyboardInterrupt: sys.exit() |
wsに繋げる処理はもっと簡素に書けるかもしれません
色々試したのですが、
__main__部分にws.run_forever()を書いているとCtrl+Cでループを抜けられなくなります
変更点と追加部分
API設定
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#設定==================================================== CHANNEL = "lightning_executions_FX_BTC_JPY" #取得したAPIキーを入力 api_key = "" sec_key = "" #注文するロット数(最低0.01以上) lot = 0.01 # 判断に使うデータを保持する秒数 store_time_sec = 30 # BUYとSELLの乖離が何%でエントリーするか triger_percent = 200 CHANNEL = "lightning_executions_FX_BTC_JPY" #======================================================== |
設定部分を手前に出しました
APIキーを直書きするのは本当は良くないですね
1ファイルだけで試せるようにあえて変更しています
キーを別ファイルに分ける場合は
1 2 3 4 5 6 7 8 9 |
# bitFlyer API setting public_api = pybitflyer.API() api = pybitflyer.API(api_key=api_key, api_secret=sec_key) ↓ # bitFlyer API setting bitFlyer_keys = json.load(open('bitFlyer_keys.json', 'r')) api = pybitflyer.API(api_key=bitFlyer_keys['key'], api_secret=bitFlyer_keys['secret']) |
と変更し、
bitFlyer_keys.json
1 2 3 4 |
{ "key": "", "secret": "" } |
を用意して同じディレクトリに置いてください
※コンマ[,]を消さないようにご注意ください
エントリー設定
1 2 |
# BUYとSELLの乖離が何%でエントリーするか triger_percent = 200 |
元々は売りと買いのボリュームが逆転するタイミングでエントリーするようになっていました
調整方法も用意してあり、ボリュームの差を引き算して[〇BTC分離れたら]という設定でした
10BTCにとっての20BTCと、90BTCにとっての100BTCは意味合いが違うと思ったのでパーセンテージの計算に変更してみました
計算方法は単純で、買いの場合は[BUY/SELL*100]で買いが多ければ100以上の数値になり、逆なら0に近づきます
仮に買いが20,売りが10なら200になり、[triger_percent = 200]の設定ならば
現在ポジションが無ければ買いのエントリーサインになります
基準は100になります
pandasエラーの修正(FutureWarning)
1 2 |
# 取得したメッセージを追加 [future error訂正] df_all = df_all.append(df_new,sort=True) |
pandasでappend(データの追加)をする時にこのようなメッセージが
FutureWarning:
Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
To accept the future behavior, pass ‘sort=False’.
To retain the current behavior and silence the warning, pass ‘sort=True’.
将来的な警告:
非連結軸が揃っていないためソートしています。将来のバージョン
パンダの数はデフォルトで並べ替えないように変更されます。
将来の振る舞いを受け入れるには、 ‘sort = False’を渡してください。
現在の動作を保持して警告を黙らせるには、 ‘sort = True’を渡します。
問題無く動きますが、毎回Warningを見るのは嫌ですのでsortを追加しました
エントリー開始をデータがある程度集まるまで遅らせる
1 2 3 4 5 6 7 8 9 10 |
# 新規エントリー ポジション無+履歴500以上溜まる if (local_position == 'NONE') and len(df) >= 500: if ((buy_vol / sell_vol) * 100 >= entry_triger): local_position = entry('BUY', order_size, ex_price) if local_position == 'BUY': local_position_price = ex_price elif ((sell_vol / buy_vol) * 100 >= entry_triger): local_position = entry('SELL', order_size, ex_price) if local_position == 'SELL': local_position_price = ex_price |
len(df) >= 500の部分です
これ無しでテストすると、
動かし初めのデータがあまり溜まっていない状態でも売り買いをしようとするため
防止のために追加しています
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# summary print(df.index[len(df) - 1].strftime('%H:%M:%S'), len(df), 'BUY/SELL', format(buy_vol, '.2f'), format(sell_vol, '.2f'), 'PRICE', ex_price, local_position, format(order_profit, '.2f'), format(order_profit_rate, '.4f'), 'SUM_PROFIT', format(sum_profit, '.2f')) |
それに伴いデータ情報にも現在どの程度データが溜まっているのかを表示するようにしています
数時間動かしてみて
このままではもちろん負けます
ただ、プラス収益になるタイミングもあり何かを工夫すれば稼げるBOTになる感じがします
たとえば、基準になるパーセンテージを変動させてみる、IFD,OCOもしくはその両方で注文してみる
等々アイディア次第だと思います
1時間間違いなく数円稼げるものができればあとはロットをあげて回すだけです
ちょっと試したこと
1 2 3 4 5 |
callback = api.sendchildorder(product_code='FX_BTC_JPY', child_order_type='MARKET', side=side, size=order_size) ↓ callback = api.sendchildorder(product_code='FX_BTC_JPY', child_order_type='LIMIT', price=ex_price, side=side, size=order_size) |
指値にしてみる
約定しない事がたびたび出てくるのでその処理を考えなければいけません
入りを指値、クローズは成り行き等組み合わせても良いかもしれませんね
成り行きはとても滑ります
参考サイト
bitflyer-realtime-apiのjson-rpcを使ってみる
以上です