見出し画像

[Puppeteer] Bybitのリアル口座をBotで運用するまでにわかったAPI/Websocket諸事情

Bybit版warlockをTestNetで運転してきて、ある程度順調に運転できるようになったので、そろそろMainNet(リアル口座)にて運転しようということで小ロットで運転させてみました。
その過程で分かったことを書いていきます。

APIでの最小発行ロット数に制限が無い?

実際のところ、よく分かっていません。
APIDocや資料を探してみましたが、そのような記述が見つけられず。
BitMEXの場合は

スクリーンショット 2020-05-16 10.14.08

のように

0.0025XBT未満の価格の注文は、自動的に非表示注文になります
・非表示注文は、オーダーブックに表示されず、常にテイカー手数料がかかります。
・Post-Onlyによるスパム注文は、非表示注文になる代わりに拒否されることになります。
・スパム注文が多すぎると、アカウントが一時的にトレーディングを拒否される場合があります。

と記載されていました。
よってBitMEXの場合はどんなに資産が少なくても一つの注文の最小発行ロット数に制約が出てきます。

Bybitの場合は、APIの説明に

スクリーンショット 2020-05-16 10.11.29

とあるだけ。
うーん。最大値を超えるようなことはしないんだけどなぁ。
詳しい方が居たら、コメントお願いいたします。

Websocketがちょくちょく切断される

RESTAPIではデータ取得のために毎回クエリをかける必要がありますが、Websocketの場合は変化通知を受けることが出来ますので、板情報や最新価格を素早く受信するのに好都合です。

しかし、BitMEXに比べてWebsocketの安定度が低いのか、よく接続が切れます。

WebsocketのAPI説明には

スクリーンショット 2020-05-16 10.28.51

とあったので、1分毎にPingを送信しても、ある一定の頻度でWebsocketが切断されてしまうようです。
エラー内容は

[Errno 104] Connection reset by peer

です。
それもOnErrorで撃ち込まれるので、Websocketを再生成しなくてはなりません。
この現象についてはBybitのITサポートに問い合わせ中です。
詳細がわかったら、またnoteにてお知らせします。

Websocket通知は変化分しか送られてこない

これは、APIを設計・実装している人から見れば「当然でしょ?」って思われるかも知れませんが、利用者側からすると非常に不親切な設計です。

ポジション情報を取得するときに「変化分だけ」を送信されても、一番最初がどういう状態だったのかが分かっていないと元データを更新しようが無いからです。

BybitのWebsocketでは、板情報と最新価格のデータは初回に「スナップショット」として現在の状態のコピーが送られてくるのですが、その他の資産、ポジションなどの情報は「値に変化があったら」送られてくるので、変化が発生しない場合は、Botとしては最初は「空」で動き出してしまいます。
これはちょっと不便ですね。
BitMEXみたいに「初期値」を最初に送信してくれたら、いちいちRESTAPIで初期値を確保してくて済むのに。

APIは有効期限がデフォルト3ヶ月に設定(※1)

API管理にてAPIキーを発行すると、有効期限がデフォルトで3ヶ月に設定されます。
これを変更する方法は分かっていません。
3ヶ月の有効期限に近づいたら、APIキーを作り直す必要があるようです。
(セキュリティ的な意味合いなんでしょうが)

(※1)ottimistaさんから有用な情報を頂きました。

BybitでのAPIキーの有効期限は無期限にできます。
Bybit APIキー管理画面より引用
 > ユーザーはIPアドレスと各APIキーを紐付けるすることをおすすめします。
 > 各キーには最大4つのIPアドレスを紐付けることが可能です。
 > 複数のIPアドレスを紐付けるには、各IPアドレスの間にコンマを入力してください。
 > 入力例:192.168.1.1,192.168.1.2,192.168.1.3
IPアドレスを紐付けすると失効日時ステータスが['永久']になります。既存キーの編集でも可能です。

ottimistaさん!ありがとうございます。

APIの戻り値が整数だったり実数だったり文字列だったり

結構ハマったポイントがこれです。
値としては絶対に実数のはずの部分で、戻り値が文字列だったりすることが多々あります。

例としてポジションの情報を取得したところ

{
   "ret_code": 0,
   "ret_msg": "OK",
   "ext_code": "",
   "ext_info": "",
   "result": {
       "id": 27913,
       "user_id": 1,
       "risk_id": 1,
       "symbol": "BTCUSD",
       "side": "Buy",
       "size": 5,
       "position_value": "0.0006947",
       "entry_price": "7197.35137469",
       "auto_add_margin": 0,
       "leverage": "1",  //In Isolated Margin mode, the value is set by user. In Cross Margin mode, the value is the max leverage at current risk level
       "effective_leverage": "1", // Effective Leverage. In Isolated Margin mode, its value equals `leverage`; In Cross Margin mode, The formula to calculate:
effective_leverage = position size / mark_price / (wallet_balance + unrealised_pnl)
       "position_margin": "0.0006947",
       "liq_price": "3608",
       "bust_price": "3599",
       "occ_closing_fee": "0.00000105",
       "occ_funding_fee": "0",
       "take_profit": "0",
       "stop_loss": "0",
       "trailing_stop": "0",
       "position_status": "Normal",
       "deleverage_indicator": 4,
       "oc_calc_data": "{\"blq\":2,\"blv\":\"0.0002941\",\"slq\":0,\"bmp\":6800.408,\"smp\":0,\"fq\":-5,\"fc\":-0.00029477,\"bv2c\":1.00225,\"sv2c\":1.0007575}",
       "order_margin": "0.00029477",
       "wallet_balance": "0.03000227",
       "realised_pnl": "-0.00000126",
       "unrealised_pnl": 0,
       "cum_realised_pnl": "-0.00001306",
       "cross_seq": 444081383,
       "position_seq": 287141589,
       "created_at": "2019-10-19T17:04:55Z",
       "updated_at": "2019-12-27T20:25:45.158767Z"
   },
   "time_now": "1577480599.097287",
   "rate_limit_status": 119,
   "rate_limit_reset_ms": 1580885703683,
   "rate_limit": 120
}

みたいに、実数部分は全部文字列として戻されます。

では、小数点付きのデータがすべて文字列で表現されているのか?と思いきや、ウォレット情報を取得してみると、

{
   "ret_code": 0,
   "ret_msg": "OK",
   "ext_code": "",
   "ext_info": "",
   "result": {
       "BTC": {
           "equity": 1002,                         //equity = wallet_balance + unrealised_pnl
           "available_balance": 999.99987471,      //available_balance
           //In Isolated Margin Mode:
           // available_balance = wallet_balance - (position_margin + occ_closing_fee + occ_funding_fee + order_margin)
           //In Cross Margin Mode:
             //if unrealised_pnl > 0:
             //available_balance = wallet_balance - (position_margin + occ_closing_fee + occ_funding_fee + order_margin);
             //if unrealised_pnl < 0:
             //available_balance = wallet_balance - (position_margin + occ_closing_fee + occ_funding_fee + order_margin) + unrealised_pnl
           "used_margin": 0.00012529,              //used_margin = wallet_balance - available_balance
           "order_margin": 0.00012529,             //Used margin by order
           "position_margin": 0,                   //position margin
           "occ_closing_fee": 0,                   //position closing fee
           "occ_funding_fee": 0,                   //funding fee
           "wallet_balance": 1000,                 //wallet balance. When in Cross Margin mod, the number minus your unclosed loss is your real wallet balance.
           "realised_pnl": 0,                      //daily realized profit and loss
           "unrealised_pnl": 2,                    //unrealised profit and loss
               //when side is sell:
               // unrealised_pnl = size * (1.0 / mark_price -  1.0 / entry_price)
               //when side is buy:
               // unrealised_pnl = size * (1.0 / entry_price -  1.0 / mark_price)
           "cum_realised_pnl": 0,                  //total relised profit and loss
           "given_cash": 0,                        //given_cash
           "service_cash": 0                       //service_cash
       }
   },
   "time_now": "1578284274.816029",
   "rate_limit_status": 98,
   "rate_limit_reset_ms": 1580885703683,
   "rate_limit": 100
}

です。
あれれ?ちゃんと実数として戻されてるじゃん!
やれば出来るんじゃないのか?
(このあたりから設計思想が見えなくなりました)

また、Websocketでinstrumentデータを取得したときには

{
   "topic": "instrument_info.100ms.BTCUSD",
   "type": "snapshot",
   "data": {
       "id": 1,
       "symbol": "BTCUSD",                           //instrument name
       "last_price_e4": 81165000,                    //the latest price
       "last_tick_direction": "ZeroPlusTick",        //the direction of last tick:PlusTick,ZeroPlusTick,MinusTick,ZeroMinusTick
       "prev_price_24h_e4": 81585000,                //the price of prev 24h
       "price_24h_pcnt_e6": -5148,                   //the current last price percentage change from prev 24h price
       "high_price_24h_e4": 82900000,                //the highest price of prev 24h
       "low_price_24h_e4": 79655000,                 //the lowest price of prev 24h
       "prev_price_1h_e4": 81395000,                 //the price of prev 1h
       "price_1h_pcnt_e6": -2825,                    //the current last price percentage change from prev 1h price
       "mark_price_e4": 81178500,                    //mark price
       "index_price_e4": 81172800,                   //index price
       "open_interest": 154418471,                   //open interest quantity - Attention, the update is not immediate - slowest update is 1 minute
       "open_value_e8": 1997561103030,               //open value quantity - Attention, the update is not immediate - the slowest update is 1 minute
       "total_turnover_e8": 2029370141961401,        //total turnover
       "turnover_24h_e8": 9072939873591,             //24h turnover
       "total_volume": 175654418740,                 //total volume
       "volume_24h": 735865248,                      //24h volume
       "funding_rate_e6": 100,                       //funding rate
       "predicted_funding_rate_e6": 100,             //predicted funding rate
       "cross_seq": 1053192577,                      //sequence
       "created_at": "2018-11-14T16:33:26Z",         
       "updated_at": "2020-01-12T18:25:16Z",         
       "next_funding_time": "2020-01-13T00:00:00Z",  //next funding time
                                                     //the rest time to settle funding fee
       "countdown_hour": 6                           //the remaining time to settle the funding fee
   },
   "cross_seq": 1053192634,
   "timestamp_e6": 1578853524091081                  //the timestamp when this information was produced
}

みたいに「e4」という指定で明らかなように10の4乗が上乗せされて戻されます。
例)mark_price_e4 → 81178500
  これは、81178500 ではなく、8117.85 ってことですね。

このように「設計した人が違う」「実装上の都合」みたいな仕様のオンパレードです。

TestNetとMainNetでトレンドがほぼ同じ(ように見える)

BitMEXのTestNetはMainNetと違って、最新価格・トレンドともに違った挙動をしていました。
それに比べてBybitの場合は見た目的にはTestNetもMainNetも大して違っていないように見えます(もちろん出来高は全然違いますが)
Bot実装したての初期段階ではTestNetは結構役に立ってくれました。

リアル口座で試運転中のwarlock(Bybit版)

スクリーンショット 2020-05-16 12.45.45

上記は驚異の1ドル指値で運用しているものです。
(原資が少ないので、資産の0.01ロットで運転していると、1ドル指値になってしまいました)

今後

少額ロットを卒業したら、また1BTC -> 10BTC と増額していきたいと思います。
Discordコミュニティはこちらまで

楽しいbotライフを!

ソフトウェア・エンジニアを40年以上やってます。 「Botを作りたいけど敷居が高い」と思われている方にも「わかる」「できる」を感じてもらえるように頑張ります。 よろしくお願い致します。