Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

背景

みなさん、最近仮想通貨が熱いですね。
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つで取引できるのは、とても気分的に楽ですね。
このチャットのお陰で、ストレスフリーな売買ができるようになりました!
これからはチャートを見て、気軽に売買ができます!(๑•̀ㅁ•́๑)✧

更新

speedkingg
馬鹿なことを真剣に。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした