66
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TISAdvent Calendar 2017

Day 17

海外の仮想通貨取引所が使いにくいので、LINEで話せる美少女コンシェルジュを作る

Last updated at Posted at 2017-12-17

背景

みなさん、最近仮想通貨が熱いですね。
BitcoinだけでなくEthereumやRipple等のアルトコインと呼ばれるものも
たくさん値上がりしています。

kinds.png

【2017年10月】仮想通貨の種類は1000種類!

上記によるともう1000種類あるんですね。はえ~すごい。
が、しかし、国内の取引所を見てみるとどこも数が少なく(20種類弱くらい:2017/12時点)、
金の卵を見つけるには母数が少ないと感じてしまいました。

なので、
今回は有名な通貨もあり、アルトコインもたくさん(200種類以上ある)
97DIMDFd.pngBittrexというサイトを使ってみました。

【使ってみた結果】

  • 全部英語...(当たり前だ)
  • ログインがとてもめんどくさい
    • (ID/Pw -> ワンタイムパスワード -> メール認証 -> ID/Pw -> ワンタイムパスワード)

→心理的障壁が大きく、「買うのめんどくさい、売るのめんどくさい」で機会を逃すことがよくありました。

日頃から気軽に使ってるLINEで取引できたら、いいのになと思ったので、
今回「LINEで話せる美少女コンシェルジュ」を作るに至りました。

作ったもの

特定のメッセージを送ると、情報の照会や取引を行ってくれるLINE Botを作成しました。
美少女要素は画像だけです(*゚ー゚)

【実装した機能一覧】

  1. トレード履歴を取得
  2. 買い注文を出す
  3. 売り注文を出す
  4. 注文をキャンセルする
  5. 注文一覧を取得する
  6. 所有している通貨一覧を取得する

Screenshot.png

何がどう変わったか

価格をチェックしてから取引を行うまでの面倒な一連の処理がLINEのメッセージを送るだけでできるようになりました!!!
Presentation1.jpg

構成

Presentation1.png

  1. Messaging API (通称 LINE BOT) でメッセージを受け、
  2. 受けたメッセージをAWS API gatewayに転送し、
  3. AWS lambdaで処理を判断し、
  4. BittexのAPI経由で、仮想通貨取引・取引情報照会を行います。
  5. 受け取った結果をAWS lambdaで処理し、
  6. Messaging API(通称 LINE BOT)でメッセージをユーザ(スマフォ)に返します。

作り方

1. 環境設定

1-1. LINE Bot用アカウントを作成する

Line Bot用のアカウントを作成します。

  • ページにアクセスし、キャプチャの手順でBotを作成していきます。

Opera スナップショット_2017-12-26_194834_developers.line.me.png

Opera スナップショット_2017-12-26_194858_access.line.me.png

Opera スナップショット_2017-12-26_193642_developers.line.me.png

Opera スナップショット_2017-12-26_193831_developers.line.me.png

Opera スナップショット_2017-12-26_194120_developers.line.me.png

Opera スナップショット_2017-12-26_194257_developers.line.me.png
ちょっとだけ下にスクロールする
Opera スナップショット_2017-12-26_194644_developers.line.me.png

1-2. Bittrexでアカウントを作成する

(直接の入金は手間がかかるので、日本の取引所に入金し、送金することをおすすめします。)

Opera スナップショット_2017-12-26_200639_bittrex.com.png

Opera スナップショット_2017-12-26_200705_bittrex.com.png

->必要事項を入力し、アカウントを作成する

アカウントページへアクセスし、keyとsecretをコピーし、bittrex_keys.jsonへ貼り付ける
WITHDRAWは出金なので、今回は必要でない。
Opera スナップショット_2017-12-26_195207_bittrex.com.png

2. プログラミング

下記構成で作りました。

root/
 ├ [[pipしてきたライブラリたち]]
 ├ bittrex_utils/
 │ ├bittrex_public.py --- bittrexの公開情報を扱うクラス
 │ └bittrex_private.py --- bittrexの個人情報を扱うクラス
 ├ config/
 │ ├bittrex_keys.json --- bittrexのAPIを扱うためのキー
 │ └line_keys.json --- LINEのAPIを扱うためのキー
 ├ define/
 │ ├common_define.py --- 汎用的な単語を管理
 │ ├key_word_define.py --- チャットで使用するキーワードを管理
 │ └message_define.py --- チャットで使用するメッセージを管理
 ├ line_utils/
 │ └line_utils.py --- LINEのメッセージ送信を行うクラス
 ├ utils/
 │ └reply.py --- bittrexの取引を処理し、メッセージを返すクラス
 └ main.py --- 受け取ったメッセージを判別し、適切な処理へ振り分ける
init.pyは省略

pipするパッケージはこちら

python-bittrex==0.2.2
line-bot-sdk
flask

【詳細はGithubをご覧ください】
https://github.com/speedkingg/Bittrex_LineBot

2-1.ソースコード

bittrex_utils/

Bittrex apiをラップして作ったクラス。
取引で使用するメソッドを作成し、まとめてある。

bittrex_public.py
# -*- coding: utf-8 -*-

from bittrex import Bittrex  # Bittrexで取引するためのAPI

class bittrex_public:

    def __init__(self):
        self.bittrex_public = Bittrex(None, None)  # 公開情報を扱うBittrexオブジェクト

    # 取引可能通貨サマリ一覧をList型で返す
    def get_coin_summery_list(self):
        coin_summery_list = []
        response = self.bittrex_public.get_markets()

        for item in response['result']:
            coin_summery = str(item['MarketCurrencyLong'])
            coin_summery_list.append(coin_summery)

        return coin_summery_list

    # 通貨の最小取引単位取得
    def get_min_trade_size(self,market):
        response = self.bittrex_public.get_markets()
        for item in response['result']:
            if item['MarketCurrency'] == market:
                return item['MinTradeSize']

        return False

    # 通貨の終値取得
    def get_last_price(self,market):
        response = self.bittrex_public.get_marketsummary(market)
        return response['result'][0]['Last']

bittrex_private.py
# -*- coding: utf-8 -*-

import json  # jsonを使用するため
from pprint import pprint  # 表示用(jsonをきれいに表示してくれる)
from bittrex import Bittrex  # Bittrexで取引するためのAPI

# 設定ファイル読み込み--------------
bittrex_keys_json = open('config/bittrex_keys.json', 'r')
bittrex_keys = json.load(bittrex_keys_json)

KEY = bittrex_keys["key"]  # アクセスキー読み込み
SECRET = bittrex_keys["secret"]  # シークレットキー読み込み
# -------------------------------

class bittrex_private:
    def __init__(self):
        # self.bittrex_public = Bittrex(None, None)  # 公開情報を扱うBittrexオブジェクト
        self.bittrex_private = Bittrex(KEY, SECRET)  # 個人の情報を扱うBittrexオブジェクト

    # トレード履歴を取得
    def get_order_history(self):
        response = self.bittrex_private.get_order_history()
        pprint(response)
        return response

    # 買い注文を出す
    # marketに通貨ペア、quantityに注文する量、rateに価格を指定
    def buy_alt_coin(self, market, quantity, rate):
        response = self.bittrex_private.buy_limit(market=market, quantity=quantity, rate=rate)
        # 成功なら注文id、失敗ならfalseを返す
        if response['success'] is False:
            print(response)
            return False
        else:
            return response['result']['uuid']

    # 売り注文を出す
    # marketに通貨ペア、quantityに注文する量、rateに価格を指定
    def sell_alt_coin(self, market, quantity, rate):
        response = self.bittrex_private.sell_limit(market=market, quantity=quantity, rate=rate)
        # 成功なら注文id、失敗ならfalseを返す
        if response['success'] is False:
            print(response)
            return False
        else:
            return response['result']['uuid']

    # 注文をキャンセルする
    def order_cancel(self, uuid):
        response = self.bittrex_private.cancel(uuid=uuid)
        # 成功ならtrue、失敗ならfalseを返す
        print(response)
        return response['success']

    # 注文一覧を取得する
    def get_orders(self):
        order_list = []
        response = self.bittrex_private.get_open_orders()
        if response['success'] is False:
            print(response)
            return False
        else:
            # 注文が1件もない場合
            if len(response['result']) == 0:
                return None

            for item in response['result']:
                # 通貨の種類と量、注文IDを抜き出す
                balance = {}
                balance['market'] = item['Exchange']
                balance['quantity'] = item['Quantity']
                balance['uuid'] = item['OrderUuid']
                order_list.append(balance)

        return order_list

    # 所有している通貨をList型で返す
    def get_balances(self):
        balance_list = []
        response = self.bittrex_private.get_balances()
        if response['success'] is False:
            print(response)
            return False
        else:
            for item in response['result']:
                # 利用可能な通貨量が0の場合はスキップする
                if item['Available'] == 0:
                    continue
                # 通貨の種類と量を抜き出す
                balance = {}
                balance['currency'] = item['Currency']
                balance['available'] = item['Available']
                balance_list.append(balance)

        return balance_list

config/

各種APIを利用するためのキーを保存するための設定ファイル

bittrex_keys.json
{
  "key":"<Bittrex APIのアカウントページからコピーする>",
  "secret":"<Bittrex APIのアカウントページからコピーする>"
}
line_keys.json
{
  "channel_access_token" : "<LINEの個人ページからコピーする>"
}

define/

返信用メッセージや処理分岐に使用するキーワードなど、直接処理内容に関係ない部分を抜き出して管理する。

common_define.py
# -*- coding: utf-8 -*-
class COMMON_DEFINE:
    def __init__(self):
        pass

    # 通貨ペアをBTCにするための接頭語
    PREFIX_BTC = "BTC-"

    # 通貨の単位(BTC)
    CURRENCY_UNIT_BTC = "btc"

key_word_define.py
# -*- coding: utf-8 -*-
class KeyWordDefine:
    def __init__(self):
        pass

    # line_search_keyword

    # 買い注文を出す
    ORDER_BUY = ['買う', '買い', '買って']
    # 売り注文を出す
    ORDER_SELL = ['売る', '売り' , '売って']
    # 注文をキャンセルする
    ORDER_CANCEL = ['キャンセル']
    # 注文一覧を取得する
    ORDERS_LIST = ['注文一覧', 'オーダー一覧']
    # 所有している通貨一覧を取得する
    WALLET = ['所有', 'もってるやつ']
    # ヘルプを出す
    HELP = ['ヘルプ', 'へるぷ']

message_define.py
# -*- coding: utf-8 -*-

class MessageDefine:
    def __init__(self):
        pass

    # line_reply_key_word

    TRADE_ID = "取引ID"

    # line_reply_message

    # ヘルプメッセージ
    HELP_MESSAGE = "メッセージフォーマットだよ\n\n"\
                    + "[所有しているコイン一覧]\nkey_word: 所有、もってるやつ\n\n"\
                    + "[買い注文]\nformat: 買い <通貨略称> <btc量>\n\n"\
                    + "[売り注文]\nformat: 売り <通貨略称> <alt_coin量>\n\n" \
                    + "[キャンセル]\nformat: キャンセル<注文ID>\n\n" \
                    + "[注文一覧]\nkey_word: 注文一覧、オーダー一覧\n\n" \
                    + "[ヘルプを出す]\nkey_word: へるぷ"

    # 所有しているコイン一覧を返すときのメッセージ
    OWNED_COIN_SUMMARY_MESSAGE = "今所有しているコインの一覧だよ"

    # 処理に失敗した際に返すメッセージ
    FAILED_TRADE_MESSAGE = "取引に失敗しちゃった。。。"

    # コインの購入申請をしたときに返すメッセージ
    APPLY_FOR_PURCHASE_MESSAGE = "指定したコインを購入申請したよ"

    # 取引中の注文がないときに返すメッセージ
    NO_ORDER_MESSAGE = "取引中の注文はないよ"

    #  取引中の注文を返すメッセージ
    ORDER_LIST_MESSAGE = "取引中の注文一覧だよ"

    # 取引をキャンセルしたときに返すメッセージ
    CANCEL_MESSAGE = "取引をキャンセルしたよ"

    # 取引のキャンセルに失敗したときに返すメッセージ
    FAILED_CANCEL_MESSAGE = "取引のキャンセルに失敗しちゃった。。。"

line_utils/

LINEで、メッセージを返信するときに使用するクラス。
LINE APIに合わせたフォーマットの整形やトークンの付与等を行う。

line_utils.py
# -*- coding: utf-8 -*-

import json
from linebot.api import LineBotApi
from linebot.models import TextSendMessage


# 設定ファイル読み込み--------------
line_keys_json = open('config/line_keys.json', 'r')
line_keys = json.load(line_keys_json)

channel_access_token = line_keys["channel_access_token"]  # シークレットキー読み込み
# -------------------------------


class line_utils:
    def __init__(self):
        # Line返信用オブジェクト作成
        self.line_bot_api = LineBotApi(channel_access_token)

    def line_reply(self,reply_token, reply_message):
        self.line_bot_api.reply_message(reply_token, messages=TextSendMessage(reply_message))

utils/

main処理から呼び出される
Bitrtrexの取引メソッドとLINEのメッセージ返信メソッドを呼び出し、仮想通貨の取引とLINEの返信を行うクラス

reply.py
# -*- coding: utf-8 -*-


from line_utils.line_utils import line_utils # lineでメッセージを送る
from bittrex_utils.bittrex_private import bittrex_private  # bittrexの個人情報を扱う
from bittrex_utils.bittrex_public import bittrex_public  # bittrexの公開情報を扱う
from define.common_define import COMMON_DEFINE
from define.message_define import MessageDefine

# lineメッセージを受け取って起動する
class reply():
    def __init__(self):
        self.line_utils = line_utils()
        self.bittrex_private = bittrex_private()
        self.bittrex_public = bittrex_public()


    # フォーマットをメッセージにして返す
    def match_keyword_help(self,reply_token):
        # 定義からメッセージを読み込む
        reply_message = MessageDefine.HELP_MESSAGE

        # lineメッセージを返す
        self.line_utils.line_reply(reply_token, reply_message=reply_message)


    # 所有しているコイン一覧をメッセージにして返す
    def match_keyword_wallet(self,reply_token):
        # 定義からメッセージを読み込む
        reply_message = MessageDefine.OWNED_COIN_SUMMARY_MESSAGE + "\n\n"
        # 所有している通貨リストを取得する
        balance_list = self.bittrex_private.get_balances()

        # 所有しているコイン一覧を1行ずつメッセージに追記する
        for item in balance_list:
            reply_message += str(item['currency']) + ": " + str(item['available']) + "\n"

        # lineメッセージを返す
        self.line_utils.line_reply(reply_token,reply_message=reply_message)


    # 指定したコインを買う
    # line_message = "買い <通貨略称> <btc量>"
    def match_keyword_buy(self,reply_token, line_message):
        # 受け取ったlineメッセージを要素に分解する
        line_message_list = line_message.split()
        market_name = line_message_list[1]
        btc_quantity = line_message_list[2]

        # 通貨ペアをBTCにする
        currency_pair = COMMON_DEFINE.PREFIX_BTC + market_name

        # 最小取引量取得
        min_trade_size = self.bittrex_public.get_min_trade_size(market_name)

        # 終値取得
        alt_last_price = self.bittrex_public.get_last_price(currency_pair)

        #注文量計算
        order_quantity = float(btc_quantity)/alt_last_price

        # 最小取引量に合わせる
        order_quantity = int(order_quantity/min_trade_size)*min_trade_size

        # アルトコインを購入し、取引IDを受け取る
        uuid = self.bittrex_private.buy_alt_coin(market=currency_pair ,
                                                 quantity=order_quantity,
                                                 rate=alt_last_price)
        # メッセージ作成
        # 取引失敗の場合
        if uuid is False:
            reply_message = MessageDefine.FAILED_TRADE_MESSAGE

        # 取引成功でトレードが完了している場合
        elif uuid == "":
            reply_message = MessageDefine.APPLY_FOR_PURCHASE_MESSAGE + "\n\n" \
                            + currency_pair + ": " + str(btc_quantity) + COMMON_DEFINE.CURRENCY_UNIT_BTC

        # 取引成功でトレードが完了していない場合
        else:
            reply_message = MessageDefine.APPLY_FOR_PURCHASE_MESSAGE + "\n\n" \
                            + currency_pair + ": " + str(btc_quantity) + COMMON_DEFINE.CURRENCY_UNIT_BTC\
                            + "\n" + MessageDefine.TRADE_ID + " : " + uuid

        # lineメッセージを返す
        self.line_utils.line_reply(reply_token, reply_message=reply_message)



    # 指定したコインを売る
    # line_message = "売り <通貨略称> <alt_coin量>"
    def match_keyword_sell(self,reply_token, line_message):
        # 受け取ったlineメッセージを要素に分解する
        line_message_list = line_message.split()
        market_name = line_message_list[1]
        alt_quantity = line_message_list[2]

        # 通貨ペアをBTCにする
        currency_pair = COMMON_DEFINE.PREFIX_BTC + market_name

        # 最小取引量取得
        min_trade_size = self.bittrex_public.get_min_trade_size(market_name)

        # 終値取得
        alt_last_price = self.bittrex_public.get_last_price(currency_pair)

        # 最小取引量に合わせる
        order_quantity = int(float(alt_quantity) / min_trade_size) * min_trade_size

        # アルトコインを購入し、取引IDを受け取る
        uuid = self.bittrex_private.sell_alt_coin(market=currency_pair ,
                                                 quantity=order_quantity,
                                                 rate=alt_last_price)
        # メッセージ作成
        # 取引失敗の場合
        if uuid is False:
            reply_message = MessageDefine.FAILED_TRADE_MESSAGE

        # 取引成功でトレードが完了している場合
        elif uuid == "":
            reply_message = MessageDefine.APPLY_FOR_PURCHASE_MESSAGE + "\n\n" \
                            + currency_pair + ": " + str(alt_quantity) + market_name.lower()

        # 取引成功でトレードが完了していない場合
        else:
            reply_message = MessageDefine.APPLY_FOR_PURCHASE_MESSAGE + "\n\n" \
                            + currency_pair + ": " + str(alt_quantity) + market_name.lower()\
                            + "\n" + MessageDefine.TRADE_ID + " : " + uuid

        # lineメッセージを返す
        self.line_utils.line_reply(reply_token, reply_message=reply_message)


    # 取引中の注文一覧をメッセージにして返す
    def match_keyword_orders_list(self,reply_token):
        order_list = self.bittrex_private.get_orders()

        if order_list is None:
            reply_message = MessageDefine.NO_ORDER_MESSAGE

        else:
            reply_message = MessageDefine.ORDER_LIST_MESSAGE + "\n\n"
            # 所有しているコイン一覧を1行ずつメッセージに詰める
            for item in order_list:
                reply_message += str(item['market']) + ": " + str(item['quantity']) + "\n"\
                                 + "取引ID: " + str(item['uuid']) + "\n\n"

        # lineメッセージを返す
        self.line_utils.line_reply(reply_token,reply_message=reply_message)


    # 取引中の注文一覧をメッセージにして返す
    # line_message = "キャンセル <取引ID>"
    def match_keyword_cancel(self, reply_token, line_message):
        line_message_list = line_message.split()
        uuid = line_message_list[1]

        # 注文が存在するか確認
        order_list = self.bittrex_private.get_orders()
        if order_list is None:
            reply_message = MessageDefine.NO_ORDER_MESSAGE
            # lineメッセージを返す
            self.line_utils.line_reply(reply_token, reply_message=reply_message)


        else:
            # 注文をキャンセルする
            response = self.bittrex_private.order_cancel(uuid)
            if response:
                reply_message = MessageDefine.CANCEL_MESSAGE
            else:
                reply_message = MessageDefine.FAILED_CANCEL_MESSAGE

            reply_message += "\n" + MessageDefine.TRADE_ID + " : " + uuid

            # lineメッセージを返す
            self.line_utils.line_reply(reply_token, reply_message=reply_message)

main.py

最初に呼び出され、受け取ったメッセージを判別し、適切な処理へ振り分ける

main.py
# -*- coding: utf-8 -*-

from define.key_word_define import KeyWordDefine # キーワード定義情報一覧
from utils.reply import reply #受け取ったメッセージを処理し、LINEでリプライを行うクラス

reply = reply()

# lineメッセージを受け取って起動する
def lambda_handler(event, context):

    # debug
    print(event)
    print(event['body-json']['events'][0]['replyToken'])
    print(event['body-json']['events'][0]['message']['text'].encode('utf-8'))

    # lineイベントの取り出し
    line_event = event['body-json']['events'][0]

    # lineメッセージを返す用のトークン取得
    reply_token = line_event['replyToken']

    # 送られてきたlineメッセージ取り出し
    line_message = line_event['message']['text'].encode('utf-8')

    # [ヘルプ]のキーワードとマッチした場合
    for word in KeyWordDefine.HELP:
        if line_message.find(word) != -1:
            reply.match_keyword_help(reply_token)
            exit()

    # [所有している通貨一覧を取得する]のキーワードとマッチした場合
    for word in KeyWordDefine.WALLET:
        if line_message.find(word) != -1:
            reply.match_keyword_wallet(reply_token)
            exit()

    # [買い]のキーワードとマッチした場合
    for word in KeyWordDefine.ORDER_BUY:
        if line_message.find(word) != -1:
            reply.match_keyword_buy(reply_token, line_message)
            exit()

    # [売り]のキーワードとマッチした場合
    for word in KeyWordDefine.ORDER_SELL:
        if line_message.find(word) != -1:
            reply.match_keyword_sell(reply_token, line_message)
            exit()

    # [注文一覧]のキーワードとマッチした場合
    for word in KeyWordDefine.ORDERS_LIST:
        if line_message.find(word) != -1:
            reply.match_keyword_orders_list(reply_token)
            exit()

    # [キャンセル]のキーワードとマッチした場合
    for word in KeyWordDefine.ORDER_CANCEL:
        if line_message.find(word) != -1:
           reply.match_keyword_cancel(reply_token, line_message)
           exit()

3. デプロイ

  1. 上記で作成したファイルをaws Lamdaへアップロード
  2. API gatewayとaws Lamdaを関連付け、デプロイ
  3. デプロイしたAPI gatewayのURLをMessaging APIと紐付け
  4. 完成\(^o^)/

作ってみて

メッセージ1つで取引できるのは、とても気分的に楽ですね。
このチャットのお陰で、ストレスフリーな売買ができるようになりました!
これからはチャートを見て、気軽に売買ができます!(๑•̀ㅁ•́๑)✧

参考:API gateway連携手順

  • コメントでご要望いただいたAPI gatewayの連携手順を簡単にスクリーンショットで貼り付けました。
    スクリーンショット 2021-04-16 22.32.02.png
    スクリーンショット 2021-04-16 22.33.06.png
    スクリーンショット 2021-04-16 22.35.12.png
    スクリーンショット 2021-04-16 22.36.19.png
    スクリーンショット 2021-04-16 22.37.10.png
    スクリーンショット 2021-04-16 22.38.14.png

  • ※ ↓ Lambda関数名は文字を打つと候補が出るので、今回作成したLambda関数を選択する。
    スクリーンショット 2021-04-16 22.40.10.png
    スクリーンショット 2021-04-16 23.13.12.png

  • ※ ↓ 下にスクロールし、application/jsonと入力し、チェックマークをクリック。
    スクリーンショット 2021-04-16 23.11.06.png
    スクリーンショット 2021-04-16 23.12.21.png

  • ※ ↓ デプロイしないとAPIとして利用できないので、デプロイする。APIのpath等を変更した場合は都度デプロイが必要。
    スクリーンショット 2021-04-16 22.42.02.png
    スクリーンショット 2021-04-16 22.42.37.png

  • ※ ↓ 作成したAPIのエンドポイントが出力される。
    スクリーンショット 2021-04-16 22.43.36.png

更新

  • ソースコードをGitに上げました。(2017/12/23)
  • LINEとBittrexのアカウント作成とAPI key入手の手順キャプチャを追加しました。(2017/12/26)
  • API gateway連携手順のスクリーンショットを追記しました。(2021/4/16)
66
50
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?