Bybitで仮想通貨の自動売買

# はじめに
日々、仮想通貨界隈は新コインやNFTゲームの登場でお祭り騒ぎ状態ですね。
今回はBybitでトレードができるPybitと呼ばれるトレード用ライブラリを軽く紹介します。

この記事を書いている2022年4月現在、インバース無期限(Inverse Perpetual)のサンプルコードはGithubや公式サイトにわりとあるのですが、デリバティブのUSDT無期限(USDT Perpetual)の情報がないように思ったのでこちらについて頑張って書いていきます。

また、私は株・FX・仮想通貨に疎いので専門用語の使い方など間違っていることもあるかと思います。

# 開発の準備

トレードプログラムを開発するにあたって、自分のお金を掛けてデバッグを繰り返していてはすぐに散財してしまいます。BybitにはTestNetとよばれるテスト環境があり、テストコインを用いてテストトレードが出来るようになっています。まずは[BybitTestnet](https://testnet.bybit.com/ja-JP/)に登録しテストコインをたくさん貰いましょう。登録後に資金がゼロの場合はお問い合わせフォームから入金してもらえます。

トレード画面は[こちら](https://testnet.bybit.com/trade/usdt/BTCUSDT)。初めての方はまずは手動のデモトレードで売り／買いの練習して慣れた方が良いかと思います。

「メニューバー -> API -> 新しいキーの作成」からAPIキーとシークレットキーを作成してください。作成したキーはPythonのコードで使用します。


次にpipでライブラリをインストールします。
```shell
$ pip3 install pybit
```

開発時の参考資料は👇
* Github リポジトリ: https://github.com/bybit-exchange/pybit
* ドキュメント: https://bybit-exchange.github.io/docs/linear/#t-introduction

# 軽く用語説明
### 成行き注文
価格を指定せずに売り買いすることを成行き注文というそうです。
価格はお任せでいいから今すぐ買いたいって時に使えますが、普通は急がずに指値注文をするそうです。

### 指値注文
価格を指定して売り買いすることを指値注文というそうです。
成行とは違い買いたい価格を自分で決めて注文をして、一旦予約状態になります。
その後、価格が指定した価格になった時に自動で約定してくれます。


### 条件付き注文
ちょっと何言ってるかわかんない💦

# ウェブソケット編
では、実際にコードを書いていきます。
以下のコードを```python3 websocket_sample.py```で実行すると、画面にデータが垂れ流しになるので確認してみてください。トレード、板、ポジション、ローソク足の色々な情報がこれでリアルタイムに取得できます。じっくりと観察するだけで楽しい。

```python:websocket_sample.py
from time import sleep
from pybit import usdt_perpetual

# 自分のトークンを設定してください。
API_KEY    = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
API_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

# ウェブソケットの初期化
ws = usdt_perpetual.WebSocket(
    test=True,
    api_key=API_KEY,
    api_secret=API_SECRET,
    # domain="bytick"  # the default is "bybit"
)

# トレード情報のコールバック関数
def handle_tradeinfo(message):
    print('-----Trade Message-----')
    print(message)

# 板情報のコールバック関数
def handle_orderbook(message):
    print('-----OrderBook Message-----')
    print(message)

# ポジションのコールバック関数
def handle_position(message):
    print('-----Position Message-----')
    print(message)

# ローソク足のコールバック関数
def handle_kline(message):
    print('-----Kline Message-----')
    print(message)


def main():
    # ウェブソケットのトピック登録
    ws.trade_stream(handle_tradeinfo, 'BTCUSDT')        # トレード情報を受信してみる。
    ws.orderbook_25_stream(handle_orderbook, 'BTCUSDT') # 板情報を受信してみる。
    ws.position_stream(handle_position)                 # ポジション情報を受信してみる。
    ws.kline_stream(handle_kline, 'BTCUSDT', 30)        # ローソク足情報を受信してみる。(1 3 5 15 30 60 120 240 360(秒), or D W M)

    # メインループ
    while True:
        # トレードのメインロジック等はここに書けます。
        sleep(60) 

if __name__ == "__main__":
    main()

```

# HTTP編
実際に売り買い注文を出すコードを書いてみます。
まずは、成行注文から。
実行したら[トレード画面](https://testnet.bybit.com/trade/usdt/BTCUSDT)の下に約定したポジションが表示されているかと思います。
```python:market_order.py
from pybit import usdt_perpetual

API_KEY    = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
API_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

session = usdt_perpetual.HTTP(
    endpoint='https://api-testnet.bybit.com',
    api_key=API_KEY,
    api_secret=API_SECRET,
)

# 成行注文
session.place_active_order(
    symbol       = "BTCUSDT",
    order_type   = "Market",
    side         = "Buy",  # or "Sell" 
    qty          = 0.1,    # 購入量(BTC)
    # price      = 38000,   #成行なので注文価格は不要
    take_profit  = 49000.50,  #利食い価格
    stop_loss    = 37000.00,  #損切り価格
    reduce_only  = False,     # 利食/損切を設定する場合はFalse
    close_on_trigger = False, # 
    time_in_force    = "GoodTillCancel", #取引が成立するまで無期限で保有
    # order_link_id="cus_order_id_1",    # カスタムID: 任意で一意な36文字以内の文字列
    )
```

次に、指値注文をしてみます。
実行したら一旦予約状態になるため、「アクティブな注文」の方に追加されたかと思います。現在価格が指値価格になったら自動的に約定してくれます。
```python:limit_order.py
from pybit import usdt_perpetual

API_KEY    = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
API_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

session = usdt_perpetual.HTTP(
    endpoint='https://api-testnet.bybit.com',
    api_key=API_KEY,
    api_secret=API_SECRET,
)

# 指値注文
session.place_active_order(
    symbol       = "BTCUSDT",
    order_type   = "Limit",
    side         = "Buy",  # or "Sell" 
    qty          = 0.1,    # 購入量(BTC)
    price        = 38000.00,   #注文価格
    take_profit  = 49000.50,   #利食い価格
    stop_loss    = 37000.00,   #損切り価格
    reduce_only        = False,     # 利食/損切を設定する場合はFalse
    close_on_trigger   = False,     # 
    time_in_force      = "GoodTillCancel", #取引が成立するまで無期限で保有
    # order_link_id="cus_order_id_1",      # カスタムID: 任意で一意な36文字以内の文字列
    )

```

# 簡単な自動トレードのサンプル

ここまで意外とあっさり理解できたかと思います。
儲けれるかは一旦置いといて、10分毎に自動的に成行で売り買い注文を出すことを考えてみましょう。
チャートが上がっていれば買い注文、チャートが下がっていれば売り注文を出してみるとします。
利食い損切り設定は現在価格±50ドルにしてみようか。

```python:trade_sample.py
from time import sleep
from pybit import usdt_perpetual

import logging
logging.basicConfig(filename="pybit.log", level=logging.DEBUG,
                    format="%(asctime)s %(levelname)s %(message)s")

# Your API Token
API_KEY    = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
API_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

session = usdt_perpetual.HTTP(
    endpoint='https://api-testnet.bybit.com',
    api_key=API_KEY,
    api_secret=API_SECRET,
)

ws = usdt_perpetual.WebSocket(
    test=True,
    api_key=API_KEY,
    api_secret=API_SECRET,
)

price      = -1 # 現在の価格(トレード情報から取得)
price_buy  = -1 # 現在価格(板情報から取得)
price_sell = -1 # 現在価格(板情報から取得)
side = None     # 'Buy' or 'Sell' (買い or 売り)
tick = None     # 価格の変化

# トレード情報のコールバック関数
def handle_tradeinfo(message):
    global price, side, tick
    data = message["data"]
    label = {'PlusTick': '価格上昇', 'ZeroPlusTick': '同価格', 'MinusTick':'価格下落', 'ZeroMinusTick':'同価格'}
    for d in data:
        price = float(d['price'])
        side = d['side']
        tick = label[d['tick_direction']]


# 板情報のコールバック関数
def handle_orderbook(message):
    global price_sell, price_buy
    orderbook_data = message["data"]
    orderbook_data = sorted(orderbook_data, key=lambda x:x['id'], reverse=True) # idでソート    
    price_sell = float(orderbook_data[24]['price'])
    price_buy = float(orderbook_data[25]['price'])


# ポジションのコールバック関数
def handle_position(message):
    print('-----Position Message-----')
    print(message)

# ローソク足のコールバック関数
def handle_kline(message):
    print('-----Kline Message-----')
    print(message)


def my_order():    
    print(price, price_buy, price_sell, side, tick)

    if side=='Buy': #チャートが上の時に買う
        order_buy()
    elif side=='Sell': #チャートが下の時に売る
        order_sell()

    
# 買い注文
def order_buy():
    try:
        session.place_active_order(
            symbol       = "BTCUSDT",
            order_type   = "Market",
            side         = "Buy",
            qty          = 0.5, # BTC
            # price      = price_sell,    #指値の価格
            take_profit  = price_sell + 50,   #利食い
            stop_loss    = price_sell - 50,   #損切り
            reduce_only        = False,
            close_on_trigger   = False,
            time_in_force      = "GoodTillCancel",
        )
    except:
        import traceback
        traceback.print_exc()    
        print('Error: order_buy')

# 売り注文
def order_sell():
    try:
        session.place_active_order(
            symbol       = "BTCUSDT",
            order_type   = "Market",
            side         = "Sell",
            qty          = 0.5, # BTC
            # price      = price_buy,       #指値の価格
            take_profit  = price_buy - 10,    #利食い
            stop_loss    = price_buy + 10,    #損切り
            reduce_only        = False,     
            close_on_trigger   = False,
            time_in_force      = "GoodTillCancel",
        )
    except:
        import traceback
        traceback.print_exc()    
        print('Error: order_sell')


def main():
    ws.trade_stream(handle_tradeinfo, 'BTCUSDT')        # トレード情報
    ws.orderbook_25_stream(handle_orderbook, 'BTCUSDT') # 板情報
    ws.position_stream(handle_position)                 # ポジション情報
    # ws.kline_stream(handle_kline, 'BTCUSDT', 30)      # ローソク足情報

    sleep(10) #念のため開始前に10秒待ち

    while True:
        my_order() # 注文
        sleep(10*60) # 10分待ち

if __name__ == "__main__":
    main()

```

# まとめ
PybitのデリバティブUSDT無期限の使い方について調べまとめました。
~~後は、勝てるロジックを考えて実装するだけで億り人間違いなし。~~
くれぐれも本番環境でやらかさないように！
投資は自己責任でお願いします👋