【コピペで完成】簡単な仮想通貨自動売買BOTを作ってみた【シストレ入門】

## 概要

シストレ絶賛勉強中の @kazama1209 です。

最近また仮想通貨が盛り上がってきている事もあり、自動売買BOTが作れたら良いなぁと思い色々試行錯誤してみた結果、簡単なロジックのものであれば何とか稼働するところまで持っていく事ができたので、復習がてらメモ書きしていきたい思います。

## 本記事で目指す構成

![Untitled Diagram(7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/3b554352-2047-fa5d-e270-3465074f6e5f.png)

- 自動売買BOT
  - Docker × Python
- 仮想通貨取引所
  - bybit（デモ版）
- LINE通知機能
  - LINE Notify

”再現性”を何よりも重要視しているため、環境構築はDockerを使って行っていきます。

取引所は「bybit」を採用しました。理由としては、口座開設 → APIキー発行までのハードルが低すぎる（本人確認書類などの提出も不要で必要なのはGoogle2段階認証くらい）のと、デモ口座での取引も可能なので初心者に安心だと思うからです。もちろんビットコインも取引できます。

また、LINE通知機能についてはお好みでといった感じですが、リアルタイムで逐一取引情報を教えてくれるのは便利だと思うのでオマケで付けておきました。

**ポイント**

- Dockerを使った環境構築なので誰でも同じ結果が得られる（基本コピペでOK）
- 口座開設 → APIキー発行までがスピーディなので記事を読んですぐに実践できる
- デモトレードが可能なので初心者でも安心

## 前提条件

- Dockerがインストール済みである事
- ターミナルでの簡単な操作ができる事
- Pythonの基礎知識がある事

Dockerのインストール方法に関してはググれば無限に記事が出てくるはずなので、まだの方は先に導入しておいてください。

また、基本的にはコピペで動くようになっているのでPythonについてそこまで詳しくなくても問題ありませんが、全く読めないと万が一不具合が起きた場合のデバッグに困るので、多少はあった方が望ましいかもです。

## 準備編（口座開設やAPIキーの発行など）

Pythonでコードを買いていく前に色々準備する事があるので、まずはそちらを片付けます。

### bybit（デモ版）の口座開設

![スクリーンショット 2021-03-28 2.52.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/42d1efc3-6023-a5ec-84c4-1172bfe27a7a.png)

[https://testnet.bybit.com/ja-JP/](https://testnet.bybit.com/ja-JP/)

bybit（デモ版）の口座開設をしましょう。日本語対応しているので、わざわざ説明しなくても画面の指示に従っていけば何とかなるはずです。

※bybitのデモ版は通常版と完全に別サイトになっているので、間違って通常版へ飛ばないように注意してください。

- 通常版
  - [https://www.bybit.com/ja-JP/](https://www.bybit.com/ja-JP/)
- デモ版
  - [https://testnet.bybit.com/ja-JP/](https://testnet.bybit.com/ja-JP/)

### APIキーを発行

![スクリーンショット 2021-03-28 2.59.36_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/ef72de5d-ef33-53e2-8a4b-5b7356a60520.jpeg)

口座開設が完了したらログインし、右上のメニューから「アカウントセキュリティ」→「API管理」へ進み、APIキーを作成します。


![スクリーンショット 2021-03-28 3.02.47_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/3a029c92-5527-ddcb-75c2-b94a659f6089.jpeg)


- APIトランザクション
- 名前
  - 任意
- IPに接続する
  - 任意（今回はテストなので空欄）
- キー許可
  - アクティブな注文とポジション

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3638383835342f62653538623437642d346266662d313637642d653664352d3666616239333664353231652e6a706567_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/88d14e33-8203-4c9d-3d83-ed683c249e1b.jpeg)

発行に成功したら、２種類のキー（APIキー、シークレットキー）をメモに控えておきましょう。

### 口座に入金する

bybitのデモ版の場合、ライブチャットで中の人に問い合わせる事で入金が可能です。

![スクリーンショット 2021-07-03 23.39.39_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/13d24cbe-b41c-7e1e-6a0a-9b1cd5dbb418.jpeg)


右下のサポートから「入金」と入力してエンターキーを押すと「オンラインチャット」ボタンが出てくるので、

![スクリーンショット 2021-07-03 23.28.30_censored.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/75c32c48-e847-4a38-12e7-d9caa0960206.jpeg)

担当の方にtestnetアカウントへの入金を依頼しましょう。

文章としては以下のような感じ。

> testnetへの入金（$〇〇）をお願いします。ユーザーIDは△△で欲しいコインは□□です。

- 入金額（ドル）
- ユーザーID
- コイン

３点を正確に伝えるとスムーズに話が進むはずです。

その時の状況にもよると思いますが、自分の場合は数分後には反映されていました。（スタッフさんの迅速な対応に感謝）

### LINE Notifyのトークンを取得

[https://notify-bot.line.me/ja/](https://notify-bot.line.me/ja/)

![LINE Notify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/575b10c1-8898-84dd-0031-2ac411a06cb8.png)

右上の「ログイン」から各自必要な情報（LINEアカウントにログインするためのメールアドレス・パスワード）を入力してログインします。

![LINE Notify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/8f2102aa-46dc-f768-1f11-533e9dde8466.png)

ログインに成功したら、右上から「マイページ」へと進み「トークンを発行する」をクリック。

![LINE Notify.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/02474d60-0a60-d49a-5593-fdf855b78f14.jpeg)

適当なトークン名（任意でOK）を入力し、「発行する」をクリックするとトークンが表示されるので、メモなどに控えておきましょう。

![LINE Notify.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/2c84d8db-5866-804a-7a53-4583601172de.jpeg)

## 自動売買BOTを作成

さて、諸々の準備が完了したのでいよいよPythonのコードを買いていきます。

### 環境構築

まずはPythonが動く環境を作るところから。

#### ディレクトリを作成

```
$ mkdir auto-trading-bot-for-cryptocurrency
$ cd auto-trading-bot-for-cryptocurrency
$ mkdir opt
```

作業用のディレクトリを作成し、移動します。

#### 各種ファイルを作成

```
$ touch Dockerfile
$ touch docker-compose.yml
$ touch requirements.txt
```

```dockerfile:./Dockerfile
FROM python:3
USER root

RUN apt-get update
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

ADD . /code
WORKDIR /code

RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install -r requirements.txt
```

```yml:./docker-compose.yml
version: "3"
services:
  python3:
    restart: always
    build: .
    container_name: "python3"
    working_dir: /root/
    tty: true
    volumes:
      - ./opt:/root/opt
```

```:./requirements.txt
pandas
requests
ccxt == 1.45.18
```

※ccxtはバージョンアップがかなりの高頻度で行われているため、既存のコードが動かなくなってしまうなんて事が多々あります。今回のコードは上記のバージョン（1.45.18）で動作確認済み。それ以外のバージョンだと正常な動作は保証できないのでなるべく固定してください。

### コンテナを起動

```
$ docker-compose up -d
```

### コンテナの中へ入る

```
$ docker exec -it python3 /bin/bash
```

### Pythonが動くかどうかテスト

```
$ cd opt
$ touch test.py
```

```python:./opt/test.py
print("test")
```

下記コマンドを実行し、ターミナルに「test」と表示されれば無事環境構築に成功です。

```
$ python test.py
```

### 自動売買BOTのロジックを作成

```
$ touch bb_api.py
$ touch line_notify.py
$ touch start.py
```

今現在、次のようなディレクトリ構成になっていればOKです。

```
auto-trading-bot-for-cryptocurrency
├─ opt
  ├─ bb_api.py       # bybitのAPI設定を行うためのファイル
  ├─ start.py    # BOTを起動させるためのファイル
  ├─ line_notify.py  # LINE通知の設定を行うためのファイル
  ├─ test.py
├─ docker-compose.yml
├─ Dockerfile
├─ requirements.txt
```

```python:./opt/bb_api.py
from pprint import pprint
import ccxt

API_KEY = "先ほど発行したAPIキー"
SECRET = "先ほど発行したシークレットキー"

class BbApi:
    def __init__(self):
        self.bb_api = ccxt.bybit(
            {
                "apiKey": API_KEY,
                "secret": SECRET,
                "urls": {
                    "api": "https://api-testnet.bybit.com/" # testnet（デモ版）を使用する場合はこの記述が必要
                }
            }
        )
    
    # 保有中のポジションを取得
    def get_position(self, symbol):
        self.bb_api.load_markets()
        market = self.bb_api.market(symbol)

        return self.bb_api.v2_private_get_position_list({ "symbol": market["id"] })["result"]
    
    # 注文を作成する
    def create_order(self, symbol, order_type, side, amount):
        order = self.bb_api.create_order(
            symbol,     # 通貨ペア
            order_type, # 成行注文か指値注文か（market: 成行注文、limit: 指値注文）
            side,       # 買いか売りか（buy: 買い、sell: 売り）
            amount,     # 注文量（USD）
            { 
              "qty": amount
            }
        )

        return order
```

今回はデモ版（testnet）を使用するため、初期化の際に渡すデータ内に

```json
"urls": {
    "api": "https://api-testnet.bybit.com/"
}
```


という記述をお忘れなく。

```python:./opt/line_notify.py
import requests

LINE_NOTIFY_TOKEN = "先ほど発行したトークン"

class LineNotify:
    def __init__(self):
        self.line_notify_token = LINE_NOTIFY_TOKEN
        self.line_notify_api = "https://notify-api.line.me/api/notify"
        self.headers = {
          "Authorization": f"Bearer {self.line_notify_token}"
        }

    def send(self, msg):
        msg = { "message": f" {msg}" }
        requests.post(self.line_notify_api, headers = self.headers, data = msg)
```

この辺は特に触れる事も無いでしょう。

```python:./opt/start.py
import pandas as pd
pd.options.display.float_format = '{:.2f}'.format #小数点以下2桁まで表示

import requests
import time
import sys
import traceback
from datetime import datetime
from bb_api import BbApi
from line_notify import LineNotify

# CryptoCompareからBTC/JPYのヒストリカルデータを取得しローソク足を生成
def get_candles(timeframe, limit):
    # timeframe（時間軸）には「minute（1分足）」「hour(1時間足)」「day(日足)」のいずれかが入る
    base_url = f"https://min-api.cryptocompare.com/data/histo{timeframe}"
    
    params = {
        "fsym": "BTC",  # 通貨名(The cryptocurrency symbol of interest)
        "tsym": "USD",  # 通貨名(The currency symbol to convert into)
        "limit": limit, # 取得件数(The number of data points to return)
    }

    res = requests.get(base_url, params, timeout = 10).json()

    time, open, high, low, close = [], [], [], [], []

    for i in res["Data"]:
        time.append(datetime.fromtimestamp(i["time"]))
        open.append(i["open"])
        high.append(i["high"])
        low.append(i["low"])
        close.append(i["close"])
    
    candles = pd.DataFrame({
            "Time": time,  # 時刻
            "Open": open,  # 始値
            "High": high,  # 高音
            "Low": low,    # 安値
            "Close": close # 終値
        }
    )
    
    return candles

# 単純移動平均線を算出
def make_sma(candles, span):
    return pd.Series(candles["Close"]).rolling(window = span).mean()

bb_api = BbApi()
symbol = "BTC/USD" # 通貨ペア
amount = 1         # 注文量(USD)

line_notify = LineNotify()
line_notify.send("Start trading")

print("Start trading")

# Botを起動
while True:
    try:
        candles = get_candles("minute", 1000).set_index("Time")
        sma_5 = make_sma(candles, 5) # 短期移動平均線を作成 ※期間についてはお好み
        sma_13 = make_sma(candles, 13) # 長期移動平均線を作成 ※期間についてはお好み

        # 短期移動平均線 > 長期移動平均線 の状態が3本続いたらゴールデンクロス（騙し防止のために判断まで少し待つ）
        golden_cross = sma_5.iloc[-1] > sma_13.iloc[-1] \
            and sma_5.iloc[-2] > sma_13.iloc[-2] \
            and sma_5.iloc[-3] > sma_13.iloc[-3] \
            and sma_5.iloc[-4] < sma_13.iloc[-4]

        # 短期移動平均線 < 長期移動平均線 の状態が3本続いたらデッドクロス（騙し防止のために判断まで少し待つ）
        dead_cross = sma_5.iloc[-1] < sma_13.iloc[-1] \
            and sma_5.iloc[-2] < sma_13.iloc[-2] \
            and sma_5.iloc[-3] < sma_13.iloc[-3] \
            and sma_5.iloc[-4] > sma_13.iloc[-4]

        position = bb_api.get_position(symbol)

        if position["side"] == "None": # 保有ポジションが無い場合は新規の注文準備に入る
            if golden_cross: # ノーポジかつゴールデンクロスが現れたら新規買い
                order = bb_api.create_order(symbol, "market", "buy", amount)
                price = order["price"]
                line_notify.send(f"Buy on the market {price}")

            elif dead_cross: # ノーポジかつデッドクロスが現れたら新規売り
                order = bb_api.create_order(symbol, "market", "sell", amount)
                price = order["price"]
                line_notify.send(f"Sell on the market {price}")

        elif position["side"] == "Buy" and dead_cross: # 買いポジション保有中かつデッドクロスが現れたらドテン売り
            order = bb_api.create_order(symbol, "market", "sell", amount * 2)
            price = order["price"]
            line_notify.send(f"Stop and sell reversely {price}")

        elif position["side"] == "Sell" and golden_cross: # 売りポジション保有中かつゴールデンクロスが現れたらドテン買い
            order = bb_api.create_order(symbol, "market", "buy", amount * 2)
            price = order["price"]
            line_notify.send(f"Stop and buy reversely {price}")
        
        time.sleep(30)
    except:
        line_notify.send(traceback.format_exc())
        sys.exit() # 何か例外が生じた際はLINE通知を飛ばしてBotを停止する
```

- ヒストリカルデータの取得先
  - https://min-api.cryptocompare.com/documentation
  - CryptoCompareというサービスを利用。CryptoWatchというサービスも有名ですが、そちらは一定時間におけるAPIの使用限度が厳しめなので今回は断念しました。
- 時間軸
  - 1分足を採用。（すぐに自動売買の結果がわかるため）
- 売買ロジック
  - ゴールデンクロス&デッドクロスを採用。（説明やコードを簡単にするため）

今回は自動売買にあたり、最もメジャーといっても過言ではないゴールデンクロスとデッドクロスを売買ルールとしました。

<font color="Red">ゴールデンクロス: 長期の移動平均線を、短期の移動平均線が下から上に突き抜ける現象。</font>
<font color="Red">デッドクロス: 長期の移動平均線を、短期の移動平均線が上から下に突き抜ける現象。</font>

![goldencross.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/5a331998-57ba-67f8-28e9-336d7bebcddf.png)

引用: [OANDA](https://www.oanda.jp/lab-education/dictionary/goldencross/)

移動平均線というのはとある一定期間における平均値を線で結んだものであるため、短期の移動平均線が長期の移動平均線と交差する瞬間というのは、トレンドの転換を表す可能性があるというわけですね。

必ずしも例のように上手く決まるわけではありませんが、説明が簡単ですし、頻繁に起こる現象なのでとりあえず自動売買を体験してみるのにはにうってつけではないかなと。

基本的にはゴールデンクロス（デッドクロス）が現れたら買い（売り）、そこから逆の現象が現れたらドテン売買を行うようにして常にポジションは保有したままの状態にしておきます。

> ドテンとは、持っているポジションを決済し、反対のポジション建てにすることです。たとえば買いのポジションを保有していたときは、決済したあとすぐに売りのポジションに変えます。

引用: [東海東京証券](https://www.tokaitokyo.co.jp/kantan/term/detail_0035.html)

### 自動売買BOTを起動

```
$ python start.py

Start trading
```

BOTの起動に成功すると、ターミナルに「Start trading」と表示されるとともにLINE通知が飛んでいくはず。

![スクリーンショット 2021-03-28 040732.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/d72959ba-e4f3-9162-201f-f53fd3031528.png)

あとはbybitのチャートを眺めながら注文が発生するのを待ちましょう。2本の移動平均線を表示させて、クロスしそうになったらしばらく注目してみてください。


![IMG_2303.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688854/dee2e604-0ed4-661a-3e14-09b3e95ae962.png)

上手くいけばこんな感じで自動売買が繰り返されるようになります。

## 備考

今回作ったBOTのソースコードを置いておくので、もし不具合など生じた場合は自分のコードと比較しておかしい部分は無いか探してみてください。

https://github.com/kazama1209/auto-trading-bot-for-cryptocurrency

なお、自分はAmazon ECSというサービスを使って本番環境で稼働させていますが、各リソースを一括作成するためのTerraformコードも公開しているので良ければ参考にしていただけると。

## あとがき

いかがだったでしょうか。Dockerで環境構築を行っているため、基本的には手順通りにやれば全員同じ結果になったと思います。

もちろん、こんな単純なロジックでは本番の相場で勝つ事なんて無理でしょうし、むしろ手数料の分だけマイナスになる可能性が高いです。

ただ、自動売買BOTを作るまでの流れが少しでも伝われば良いなと思いますし、実際に動くBOTを自分で一から作った経験というのは非常に大きいので、間違いなく今後に役立つはず。

ある程度プログラミングをかじっている方であれば、この記事で紹介しているコードをベースにもっと良いロジックが考えつくと思うので、一読した後は色々試してみてください。

自分もまだまだ初心者の領域なので、これからガンガン勉強していきたいです。LGTM、ストックなど押していただけると今後のアウトプットの励みになります！