Edited at
TISDay 17

海外の仮想通貨取引所が使いにくいので、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つで取引できるのは、とても気分的に楽ですね。

このチャットのお陰で、ストレスフリーな売買ができるようになりました!

これからはチャートを見て、気軽に売買ができます!(๑•̀ㅁ•́๑)✧


更新