Bot

仮想通貨botの開発を本格的に始めてみる#34(2023/11/23)「サーキットブレーカーBotを作ってみる①」

2023年11月23日

前回の記事に引き続き、今回も仮想通貨botの開発状況をまとめていきます。

今回は、番外編として「サーキットブレーカーBot」を作ってみました。

参考にしたのはこちらの記事【コード公開】絶対に勝てる仮想通貨botはCBbot【勝率99.9%サーキットブレーカー】

ロジックや考え方を参考にさせて頂きながら、自分で工夫を加えてChatGPTで修正を加えました。

※このコードは試作版です。修正版のコードは「サーキットブレーカーbotの開発②」に載せてあります。

問題点の解消と対策

CBBotの課題をChatGPTに検証させました。

以下は、提供されたコードの問題点の指摘です。

  1. APIキーとシークレットの扱い:
    • APIキーとシークレットはコード内にハードコーディングされています。これはセキュリティ上のリスクとなります。コードを公開する前に、これらの情報は適切に保護された方法で管理されるべきです。例えば、環境変数や設定ファイルから取得するようにしましょう。
  2. 無限ループ:
    • プログラムは無限ループで実行されていますが、適切な終了条件がありません。無限ループ内でプログラムを正しく終了できるように、例えばキーボード割り込みを検知するなどの終了条件を追加することが望ましいです。
  3. 例外処理:
    • API呼び出しやデータの取得など、エラーが発生する可能性がある箇所での例外処理が不足しています。API呼び出しには常にエラーが発生する可能性があるため、適切な例外処理を追加してください。
  4. 注文作成前の価格の確認:
    • 注文を作成する前に、取引履歴から得た価格が現在の市場価格と一致するかどうかを確認するべきです。市場が急激に変動する場合、過去の価格が現在の価格と大きく異なることがあります。
  5. トレード履歴の制限:
    • 取引履歴の取得時にlimit=500が指定されていますが、これは取引履歴が過去10分の間に500回以上発生している場合に問題を引き起こす可能性があります。APIの制限に留意し、適切な方法で履歴を取得するようにしましょう。
  6. 注文が執行される前に待機時間を設ける:
    • 注文が発注されてから次の注文が発注されるまでの待機時間が短すぎます。注文が執行されるまでに時間がかかる場合があり、即座に次の注文を発注すると問題が生じる可能性があります。もう少し長い待機時間を考慮すると良いでしょう。

これらの問題点を修正することで、より堅牢で安全なトレードプログラムを構築できるはずです。

これに加えて、以下の質問をぶつけたり指示を出したりしながら、コードの修正をさせました。

Yodaka

「# 過去の価格と現在の価格が大きく異なる場合は注文を作成しない」という記述があるが、そのロジックでは、サーキットブレーカー戦略の利点を活かしきれないのではないか?

Yodaka

上記のコードでは 、注文を発注した後で手仕舞いするロジックが不足している。利益を最大にするための戦略を可能な限り提案しなさい。

Yodaka

上記のコードでは「# 注文の手仕舞い(利益確定)を試みる」時に、無条件で手仕舞いが行われている。 注文を出した後で利益がX%出た場合に手仕舞いをするように修正しなさい。

サーキットブレーカーBotのコード

主な変更点は「エラーメッセージを表示させる」「手仕舞いのロジック追加」です。

import ccxt
import datetime
import time

# APIキーとAPIシークレットを直接設定
api_key = 'YOUR_API_KEY'
api_secret = 'YOUR_API_SECRET'

bitflyer = ccxt.bitflyer({
    'apiKey': api_key,
    'secret': api_secret,
})

symbol = 'FX_BTC_JPY'

def get_current_price():
    ticker = bitflyer.fetch_ticker(symbol)
    return ticker['last']

def close_order(order_id):
    try:
        bitflyer.cancel_order(order_id, symbol)
        print(f"注文 {order_id} が手仕舞いされました。")
    except ccxt.NetworkError as e:
        print(f"手仕舞いエラー(ネットワークエラー): {str(e)}")
    except ccxt.ExchangeError as e:
        print(f"手仕舞いエラー(取引所エラー): {str(e)}")
    except Exception as e:
        print(f"手仕舞いエラー(その他のエラー): {str(e)}")

while True:
    try:
        closest_diff = 1000000000
        closest_num = 0

        # 現在のUNIXタイムスタンプを取得
        current_time = datetime.datetime.now().timestamp()

        # 10分前のUNIXタイムスタンプを計算
        ten_minutes_ago = current_time - 600

        # 約定履歴を取得
        trades = bitflyer.fetch_trades(symbol, limit=500)
        for i in range(len(trades)):
            time_diff = (trades[i]["timestamp"] - ten_minutes_ago * 1000) / 1000
            if abs(time_diff) < closest_diff:
                closest_diff = abs(time_diff)
                closest_num = i

        past_price = trades[closest_num]["price"]

        current_price = get_current_price()

        # 注文を作成する条件: 過去の価格と現在の価格が大きく異なる場合も注文を作成する
        if True:  # 注文を無条件で作成する場合は条件をTrueに変更
            buy_price = int(past_price * 0.8)
            sell_price = int(past_price * 1.2)

            # 指値注文を発注
            buy_order = bitflyer.create_order(symbol, "limit", "buy", 0.01, buy_price)
            sell_order = bitflyer.create_order(symbol, "limit", "sell", 0.01, sell_price)

            print("注文が発注されました。")

            while True:
                time.sleep(5)  # 注文が発注されてから手仕舞いを試みるまでの待機時間
                current_price = get_current_price()

                # 利益率が一定の閾値を超えた場合に手仕舞いを試みる
                if (current_price - past_price) / past_price > 0.02:  # 例: 利益率が2%以上の場合
                    close_order(buy_order['id'])
                    close_order(sell_order['id'])
                    break  # 手仕舞いが成功したらループを抜ける

    except ccxt.NetworkError as e:
        print(f"ネットワークエラーが発生しました: {str(e)}")
        time.sleep(5)
    except ccxt.ExchangeError as e:
        print(f"取引所エラーが発生しました: {str(e)}")
        time.sleep(5)
    except Exception as e:
        print(f"予期せぬエラーが発生しました: {str(e)}")
        time.sleep(5)

重要なのは、「利益率の設定」ですね。

ここで手数料負けしないように慎重に設定しておく必要があります。

そもそも、「自動で手仕舞いするロジックが必要なのかどうか」も検証する必要がありますね。

セキュリティを高めたい人向け

上記のコードでは、コード内に直接APIキーとAPIシークレットを入力するものになっています。

このコードではAPIキーとAPIシークレットをシェルに設定することで、セキュリティ面に配慮しています。

①環境変数の設定:シェルで以下のように入力。

export YOUR_API_KEY=実際のAPIキー
export YOUR_API_SECRET=実際のAPIシークレット

②確認:設定が正しく行われたか確認するために、次のコマンドを実行する。

echo $YOUR_API_KEY
echo $YOUR_API_SECRET

import ccxt
import datetime
import time
import os

api_key = os.environ.get('BITFLYER_API_KEY')
api_secret = os.environ.get('BITFLYER_API_SECRET')

if not (api_key and api_secret):
    raise ValueError("APIキーとシークレットを環境変数から取得できません。")

bitflyer = ccxt.bitflyer({
    'apiKey': api_key,
    'secret': api_secret,
})

symbol = 'FX_BTC_JPY'

def get_current_price():
    ticker = bitflyer.fetch_ticker(symbol)
    return ticker['last']

def close_order(order_id):
    try:
        bitflyer.cancel_order(order_id, symbol)
        print(f"注文 {order_id} が手仕舞いされました。")
    except ccxt.NetworkError as e:
        print(f"手仕舞いエラー(ネットワークエラー): {str(e)}")
    except ccxt.ExchangeError as e:
        print(f"手仕舞いエラー(取引所エラー): {str(e)}")
    except Exception as e:
        print(f"手仕舞いエラー(その他のエラー): {str(e)}")

while True:
    try:
        closest_diff = 1000000000
        closest_num = 0

        # 現在のUNIXタイムスタンプを取得
        current_time = datetime.datetime.now().timestamp()

        # 10分前のUNIXタイムスタンプを計算
        ten_minutes_ago = current_time - 600

        # 約定履歴を取得
        trades = bitflyer.fetch_trades(symbol, limit=500)
        for i in range(len(trades)):
            time_diff = (trades[i]["timestamp"] - ten_minutes_ago * 1000) / 1000
            if abs(time_diff) < closest_diff:
                closest_diff = abs(time_diff)
                closest_num = i

        past_price = trades[closest_num]["price"]

        current_price = get_current_price()

        # 注文を作成する条件: 過去の価格と現在の価格が大きく異なる場合も注文を作成する
        if True:  # 注文を無条件で作成する場合は条件をTrueに変更
            buy_price = int(past_price * 0.8)
            sell_price = int(past_price * 1.2)

            # 指値注文を発注
            buy_order = bitflyer.create_order(symbol, "limit", "buy", 0.01, buy_price)
            sell_order = bitflyer.create_order(symbol, "limit", "sell", 0.01, sell_price)

            print("注文が発注されました。")

            while True:
                time.sleep(5)  # 注文が発注されてから手仕舞いを試みるまでの待機時間
                current_price = get_current_price()

                # 利益率が一定の閾値を超えた場合に手仕舞いを試みる
                if (current_price - past_price) / past_price > 0.02:  # 例: 利益率が2%以上の場合
                    close_order(buy_order['id'])
                    close_order(sell_order['id'])
                    break  # 手仕舞いが成功したらループを抜ける

    except ccxt.NetworkError as e:
        print(f"ネットワークエラーが発生しました: {str(e)}")
        time.sleep(5)
    except ccxt.ExchangeError as e:
        print(f"取引所エラーが発生しました: {str(e)}")
        time.sleep(5)
    except Exception as e:
        print(f"予期せぬエラーが発生しました: {str(e)}")
        time.sleep(5)

まとめ

このままでは証拠金不足で注文が通らないので、証拠金を多めに準備して稼働状況を確かめます。

確認したいのは「利益率」「手仕舞いのロジックが正常に稼働するかどうか」です。

今後も一つずつ、勉強と検証を重ねていきます。

次回の記事はこちら。「サーキットブレーカーBotの開発②」へ続く。

-Bot