pybitflyerでbitflyerAPIを使った残高確認と売買に挑戦してみた
APIとセキュリティ
ボクは、前回の記事で暗号通貨自動売買スマホアプリをボクには作れないと書いた。
仮想通貨を自動売買するだけのスマホアプリなら、ボクでもなんとか作れると思う。
だけど、みんなが使うレベルになるとセキュリティが大事になるし、何より使う人が安心して使えるよう透明性を確保しなくちゃいけない。
なぜなら、自動売買するにはAPIを使う必要があるからだ。
セキュリティをどうするか
以前書いたコードは長くなってきたので、こちらのリンク先をみてほしい。
APIは、引用の通りだけど、一般的にはWebサービスを提供する会社が、外部の人にプログラムの一部を使うことを許可するサービスだ。
つまり、APIとは、この「何か」と「何か」が「アプリケーション、ソフトウェア」と「プラグラム」をつなぐもの、という意味になります。
APIとはソフトウェアやアプリケーションなどの一部を外部に向けて公開することにより、第三者が開発したソフトウェアと機能を共有できるようにしてくれるものです。USBは外部デバイスとパソコンを繋ぐインターフェースですが、APIはソフトウェア同士を繋げます。つまり、異なるソフトウェアやサービス間で認証機能を共有したり、チャット機能を共有したり、片方から数値データを取り込み、別のプログラムでそのデータを解析したりできるようになります。
ボクが使っているのは、bitFlyer社なので、bitFlyer Lightning というAPIサービスで、残高照会や売買取引などができる。APIを作るには、APIのkeyとパスワードが必要。引用先に取得の方法が紹介されている。
bitFlier Lightning
こちらは開発者向けにレート情報を取得したり自動取引などをできる様々なAPIが提供されています。個人で使う分にはこちらのAPIでbitFlyerで提供されているサービスを使っていただけます。APIには認証のいらないものと認証の必要があるものがあります。
APIのコードとパスワードをコードに直接書くと情報が漏洩する可能性が高い。だから、コードに直接書かないようにする工夫が必要になる。Hayatasuさんは、coincheckの自動売買botを作っている方だけど、こちらがわかりやすい。
API Keyの取得が完了したので、次に設定ファイルを準備していきます。
まずはVSCodeでconfig.iniというファイルを作成しましょう。
簡単に説明すると、configparserというコマンドを使ってAPIのパスワードなどを拡張子iniに変更したテキストファイルに保存して、必要な時だけ呼び出すようにする方法だ。
一般的に、データベースの接続先や、保存先ファイルといった設定値はソースコードに直接書き込むのではなく設定ファイルを使用します。pythonでは、設定ファイルの読み込み用に、configparserというini形式に対応したパーサーが用意されています。ini形式とは以下のような大カッコで括った「セクション」と、その配下にあるキーと値の組からなる形式です。セミコロンでコメントを入れることもできます。windowsのレジストリなどで使用している拡張形式には対応していないので注意してください。
; iniファイルの例
[DB]
host = localhost
port = 3306
user = kuro
pass = kuropass
; ここはコメント
今回、API.iniというファイルを作り、テキストで次のようにテキストで記載した。上段の[API]にはAPI関連、[Investment]は売買するときの価格の上限下限の設定だ。
[API]
api_key = (ここにAPI keyを記載)
api_secret = (ここにAPI パスワードを記載)
[Investment]
minimum_jpn = 0
maximum_jpn = 100000
そして、importにはconfigparser。ちなみに、インスタンスはConfigParserと綴りが違う。ボクが使っているのはPython3で、同じ命令でもPython2だと挙動も違うみたいなので注意が必要。
import configparser
"""
初期設定
"""
config = configparser.ConfigParser()
次に、API.iniファイルを読み込む。configだけだと文字列で読み込むけど、getfloat関数などを使えば引数の保存形式を変えることができる。
config.read('API.ini')
API_KEY = config['API']['api_key']
API_SECRET = config['API']['api_secret']
MIN_JPN = config.getfloat('Investment', 'minimum_jpn')
MAX_JPN = config.getfloat('Investment', 'maximum_jpn')
APIで残高を取得
bitFlyerのAPIで使える仮想通貨は、2021日現在でビットコイン、イーサリアム、モナコイン、リップル、ステラルーメンの5つだ。
4 月 1 日から bitFlyer Lightning に以下 3 つの現物通貨ペアを追加しました。
既存の現物通貨ペア(BTC/JPY、ETH/JPY、ETH/BTC、BCH/BTC)と同様にお取引いただくことができ、Web(PC・スマホ)および API にてご利用可能です。
そこで、APIを使って2つの仮想通貨を同時運用しようと思う。初期設定の項目に2つのシンボルコードをリスト化し、空のリストを別に一つ作る。
本当は、一度にたくさんの通貨を同時に動かせるけど、テスト中にAPI利用を制限されてしまった。
Ticker_symbol = ["BTC", "ETH"]
AMOUNT = [0, 0, 0]
そして、繰り返し処理をするところに、次のコードを加えた。初期化のコードで売買判定用の引数を初期化している。
while True:
"""
初期化
"""
Count_Int = 0
Sell_Buy = 'Wait'
Magni = 0
pybitflyerを使えば、資産残高の取得は簡単。APIのカッコの中にAPIのキーとパスワードを入れれば、getbalanceの命令で資産残高全部のデータを取得できる。次の行で、通貨ごとの残高をリストに格納する処理をした。
"""
残高取得
"""
BALANCES = pybitflyer.API(api_key=API_KEY, api_secret=API_SECRET).getbalance()
AMOUNT[0] = (BALANCES[0])["amount"]
AMOUNT[1] = (BALANCES[1])["amount"]
AMOUNT[3] = (BALANCES[3])["amount"]
※ 残高の対応番号は下の通り
BALANCE[0] JPY
BALANCE[1] BTC
BALANCE[3] ETH
BALANCE[6] MONA
BALANCE[8] XRP
BALANCE[10] XLM
if関数で条件分けしている理由は、取引をやめる最小金額と、運用限度額の範囲を決めるためだ。あとは、運用限度額を仮想通貨の数で按分して取引できるよう設定している。
if sum(AMOUNT) == 0:
pass
elif AMOUNT[0] < MIN_JPN:
AMOUNT[0] = 0
elif AMOUNT[0] > MAX_JPN:
AMOUNT[0] = MAX_JPN
elif AMOUNT[1] == 0:
Magni += 1
elif AMOUNT[2] == 0:
Magni += 1
print('JPN ' + str(AMOUNT[0]) + Ticker_symbol[0] + str(AMOUNT[1]) + Ticker_symbol[1] + str(AMOUNT[2]))
APIでの売買実行
それでは、売買の判定をする。前回作ったfurture.pyという売買判定だけをするpyファイルからSell_Buyという引数に、Sell、Buy、Waitの3種類が命令がされ、残高の状態に応じて、売買を判断できるようにした。
まず、最初に、bitflyerの最小取引単位のお金を持っているか判定する。
"""
売買判定
"""
if SYMBOL is 'BTC' or SYMBOL is 'ETH':
SIZE = float(int(AMOUNT[0] * 100 / ask * Magni) * 0.01)
else:
SIZE = float(int(AMOUNT[0] * 10 / ask * Magni) * 0.1)
print('最大購入単位' + str(SIZE))
売買の判定は、資金がない場合と、購入資金不足は強制的に売買取引を行わない待機の状態にする。また、売り命令があっても、仮想通貨を所持していない場合も待機にした。
一方で、仮想通貨を所持して買い命令が来た場合に売り命令に切り替えている。これは、相場が下落する予測なので損切りをしている。
if sum(AMOUNT) == 0:
Sell_Buy = 'Wait'
print('資金なし')
pass
elif Sell_Buy is 'Buy' and SIZE == 0:
Sell_Buy = 'Wait'
print('購入資金不足')
pass
elif Sell_Buy is 'Sell' and AMOUNT[Count_Int] == 0:
Sell_Buy = 'Wait'
print('上昇傾向待機')
pass
elif Sell_Buy is 'Buy' and AMOUNT[Count_Int] > 0:
Sell_Buy = 'Sell'
print('下落傾向利確')
pass
最後に売買を実行する。売買を実行するためには、いくつかのコードを引数に入れないといけない。pybitflyerの売買をするための命令sendchildorderに入れる値は全部で7つ。
* product_codeはBTC/JPYの売買をするので、「BTC_JPY」
* child_order_typeは成行での売買をするので「MARKET」
* sideは買いを行うので「BUY」
* sizeはロット数を設定します。今回は1.0枚のビットコインを買うため1.0としています。
* minute_to_expireは注文してから注文が消えるまでの分数を設定します。今回は1000分注文を出したままにする設定です。
* time_in_forceは執行数量条件を設定します。
※執行数量条件はGTC、IOC、FOKのいずれかを設定します。通常はGTCで大丈夫です
今回、指値で注文するので、child_order_typeはMARKETではなくLIMITにした。
time_in_forceは、執行数量条件で、IOCはFOKと同じ意味だ。今回は、残数量執行条件であるIOCにした。
指定期間条件(GTD/GTC)
指定した期間が満了する日の日中立会終了まで有効とする条件で、特定日を指定する(GTD条件)か、取り消すまで有効(取り消さなければ、取引最終日の日中立会終了まで有効)とする(GTC条件)を選択可
残数量取消条件(FAK)
一部約定後に未執行数量が残る場合には、当該残数量を失効させる条件
全数量執行条件(FOK)
全数量が直ちに約定しない場合には、当該全数量を失効させる条件
では、コードを書いてみよう。最初のコードは、売買するときに必要な値を入力している。
売買命令のコードは大文字。PRICEは円単位で1円未満は切り捨てにすること。bitflyerは最小取引単位以下の端数は売却できない。そして、取引実行時に手数料が0.15%かかるから、その程度の残高を売買時に残してあげないといけない。これを全てクリアしないとエラーになる(9/29追記)。
"""
売買実行
"""
TYPE = 'LIMIT'
EXPIRE = 1
FORCE = 'IOC'
if Sell_Buy is 'Sell':
PRICE = bid
SIZE = AMOUNT[Count_Int]
pass
elif Sell_Buy is 'Buy':
PRICE = ask
pass
print(SYMBOL + ' '+ Sell_Buy)
if Sell_Buy is 'Wait':
pass
else:
pybitflyer.API(api_key=API_KEY, api_secret=API_SECRET).sendchildorder(product_code=CODE,child_order_type=TYPE,side=Sell_Buy,size=SIZE,price=PRICE,minute_to_expire=EXPIRE, time_in_force=FORCE)
最後に売買したデータをcsvに出力した。
with open('Sell_Buy.csv','a',newline='') as files:
writer = csv.writer(files)
writer.writerow([CODE,str(timestamp),TYPE,Sell_Buy,str(SIZE),str(PRICE),str(EXPIRE),EXPIRE])
今回作成したコードはこちら。printで出力された情報を読みやすくしたので、以前書いたものから少し変わっている。ちなみに、残高照会は実行できたけど、売買取引は売買判定が完成していないので動作確認をしていない。Pythonで暗号資産botを作っている方は多いので、ボクのプログラムを使う人はいないと思うけど。
# -*- coding: utf-8 -*- #
"""
We crush capitalism.
"""
import pybitflyer
import csv
import time
import sqlite3
import furture
import configparser
"""
初期設定
"""
Distance = furture.furture
config = configparser.ConfigParser()
config.read('API.ini')
API_KEY = config['API']['api_key']
API_SECRET = config['API']['api_secret']
MIN_JPN = config.getfloat('Investment', 'minimum_jpn')
MAX_JPN = config.getfloat('Investment', 'maximum_jpn')
Ticker_symbol = ["BTC", "ETH”]
AMOUNT = [0, 0, 0]
Data_Base = sqlite3.connect("furture.db")
Data_cur = Data_Base.cursor()
Data_cur.execute('CREATE TABLE IF NOT EXISTS furture(ID INTEGER PRIMARY KEY, symbol text, timestamp datatime, lpt real, ask real, bid real, ask_size real, bid_size real, ask_depth real, bid_depth real, volume_product real, volume real)')
Data_Base.commit()
Data_Base.close()
print('==========初期設定終了=========')
Start_time = time.perf_counter()
while True:
"""
初期化
"""
print(Start_time)
print(time.perf_counter())
Count_Int = 0
Sell_Buy = 'Wait'
Magni = 0
"""
残高取得
"""
BALANCES = pybitflyer.API(api_key=API_KEY, api_secret=API_SECRET).getbalance()
AMOUNT[0] = (BALANCES[0])["amount"]
AMOUNT[1] = (BALANCES[1])["amount"]
AMOUNT[2] = (BALANCES[3])["amount"]
if sum(AMOUNT) == 0:
pass
elif AMOUNT[0] < MIN_JPN:
AMOUNT[0] = 0
elif AMOUNT[0] > MAX_JPN:
AMOUNT[0] = MAX_JPN
elif AMOUNT[1] == 0:
Magni += 1
elif AMOUNT[2] == 0:
Magni += 1
print('JPN ' + str(AMOUNT[0]) + Ticker_symbol[0] + str(AMOUNT[1]) + Ticker_symbol[1] + str(AMOUNT[2]))
"""
ティッカー操作処理
"""
for SYMBOL in Ticker_symbol:
Count_Int += 1
"""
ティッカーからデータ取得
"""
CODE = SYMBOL + "_JPY"
tick = pybitflyer.API().ticker(product_code=CODE)
timestamp = tick["timestamp"]
ltp = tick["ltp"]
ask = tick["best_ask"]
bid = tick["best_bid"]
ask_size = tick["best_ask_size"]
bid_size = tick["best_bid_size"]
ask_depth = tick["total_ask_depth"]
bid_depth = tick["total_bid_depth"]
volume_product = tick["volume_by_product"]
volume = tick["volume"]
"""
データベースへ格納
"""
Data_Base = sqlite3.connect("furture.db")
Data_cur = Data_Base.cursor()
Data_cur.execute('INSERT INTO furture(symbol, timestamp, lpt, ask, bid, ask_size, bid_size, ask_depth, bid_depth, volume_product, volume) values (?,?,?,?,?,?,?,?,?,?,?)',[SYMBOL, timestamp, ltp, ask, bid, ask_size, bid_size, ask_depth, bid_depth, volume_product, volume])
Data_Base.commit()
"""
データを抽出してリストに入れる
"""
A = 'select * from furture where symbol = ? ORDER BY ID DESC'
Data_cur.execute(A, (SYMBOL,))
NewData = Data_cur.fetchmany(24)
Data_Base.close()
Distance.core(NewData, Sell_Buy)
"""
売買判定
"""
if SYMBOL is 'BTC' or SYMBOL is 'ETH':
SIZE = float(int(AMOUNT[0] * 100 / ask * Magni) * 0.01)
else:
SIZE = float(int(AMOUNT[0] * 10 / ask * Magni) * 0.1)
print('最大購入単位' + str(SIZE))
if sum(AMOUNT) == 0:
Sell_Buy = 'Wait'
print('資金なし')
pass
elif Sell_Buy is 'Buy' and SIZE == 0:
Sell_Buy = 'Wait'
print('購入資金不足')
pass
elif Sell_Buy is 'Sell' and AMOUNT[Count_Int] == 0:
Sell_Buy = 'Wait'
print('上昇傾向待機')
pass
elif Sell_Buy is 'Buy' and AMOUNT[Count_Int] > 0:
Sell_Buy = 'Sell'
print('下落傾向利確')
pass
"""
売買実行
"""
TYPE = 'LIMiT'
EXPIRE = 1
FORCE = 'FOK'
if Sell_Buy is 'Sell':
PRICE = bid
SIZE = AMOUNT[Count_Int]
pass
elif Sell_Buy is 'Buy':
PRICE = ask
pass
print(SYMBOL + ' '+ Sell_Buy)
if Sell_Buy is 'Wait':
pass
else:
pybitflyer.API(api_key=API_KEY, api_secret=API_SECRET).sendchildorder(product_code=CODE,child_order_type=TYPE,side=Sell_Buy,size=SIZE,price=PRICE,minute_to_expire=EXPIRE, time_in_force=FORCE)
with open('Sell_Buy.csv','a',newline='') as files:
writer = csv.writer(files)
writer.writerow([CODE,str(timestamp),TYPE,Sell_Buy,str(SIZE),str(PRICE),str(EXPIRE),EXPIRE])
"""
CSVへ書込み
"""
with open('furture.csv','a',newline='') as files:
writer = csv.writer(files)
writer.writerow([SYMBOL,str(timestamp),str(ltp),str(ask),str(bid),str(ask_size),str(bid_size),str(ask_depth),str(bid_depth),str(volume_product),str(volume)])
"""
20秒タイマー
"""
print('==========20秒待機=========')
time.sleep((20 + Start_time ) - time.perf_counter())
Start_time = Start_time + 20
メインとなるコードは、今回でほとんど完成した。次は、ティッカーから読み出した情報から、売買するかどうかの判定をするためのプログラムを考えなくちゃいけない。でも、これが一番難しい。現在、このコードのデバッグをしながら、売買条件を決めるコードを書いている。
目次
次の記事
前の記事