経緯
今はやってないですが、数年前は仮想通貨のFX取引(レバレッジきかせて先物、空売りとかができるやつ)にハマっていた時期がありました。一時的に利益を上げたりしたものの、結局マイナスになりロスカットして退場。
そんな当時、以下のようなことを思っていました。
・感情に流されず売買(トレード)したい。
・短期売買をもっと頻繁かつ簡単にしたい。
じゃあプログラムで自動化してしまおう。そんな経緯で今回のプログラムを作りました。
どうやってつくるか
各取引所で公開しているAPIを利用して、プログラムから注文を自動的に実行させようと考えました。今回は大手取引所の一つである、自分としてもよく利用するbitflyerでのAPIで売買プログラムを作ることにしました。
■自動売買プログラムに必要なプログラムは2つ。
①売り/買い注文、キャンセル等のAPIをメソッド化したモジュール
②①で作った自作モジュールから適宜メソッド(API)を呼び出して自動取引を行うプログラム
コード
大前提、bitflyerの口座開設とPython3の実行環境は作っておきましょう。
まずは、①売り/買い注文、キャンセル等のAPIをメソッド化したモジュールを作成します。
import hashlib
import hmac
import requests
import datetime
import json
import urllib
class BFApi():
CURRENCY_PAIR = ""
API_KEY = ""
API_SECRET = ""
API_URL = "https://api.bitflyer.jp"
nonce = int((datetime.datetime.today() - datetime.datetime(2017, 1, 1)).total_seconds()) * 10
# コンストラクタ
def __init__(self, key, secret, product_code="FX_BTC_JPY"):
self.API_KEY = key
self.API_SECRET = secret
self.CURRENCY_PAIR = product_code
# bitFlyerのプライベートAPIリクエストを送信する関数
def _private_api(self, i_path, i_params=None):
timestamp = str(datetime.datetime.today())
headers = {"ACCESS-KEY": self.API_KEY, "ACCESS-TIMESTAMP": timestamp, "Content-Type": "application/json"}
s = hmac.new(bytearray(self.API_SECRET.encode("utf-8")), digestmod=hashlib.sha256)
b = None
if i_params is None:
w = timestamp + "GET" + i_path
s.update(w.encode("utf-8"))
headers["ACCESS-SIGN"] = s.hexdigest()
b = requests.get(self.API_URL + i_path, headers=headers)
else:
body = json.dumps(i_params)
w = timestamp + "POST" + i_path + body
s.update(w.encode("utf-8"))
headers["ACCESS-SIGN"] = s.hexdigest()
b = requests.post(self.API_URL + i_path, data=body, headers=headers)
if b.status_code != 200:
raise Exception("HTTP ERROR status={0},{1}".format(b.status_code, b.json()))
return b
# 売買を行うAPIの共通部分
def _trade_api(self, price, amount, side):
j = self._private_api("/v1/me/sendchildorder",
{
"product_code": self.CURRENCY_PAIR,
"child_order_type": "LIMIT",
"side": side,
"price": price,
"size": amount,
"time_in_force": "GTC"
}).json()
return j
# 最終取引価格を得る関数
def board(self):
j = requests.get("https://api.bitflyer.jp/v1/board?product_code=FX_BTC_JPY").json()
x = j["mid_price"]
return x
# 残高を得る関数
def balance(self):
j = self._private_api("/v1/me/getbalance").json()
jpy = [i for i in j if i["currency_code"] == "JPY"][0]
btc = [i for i in j if i["currency_code"] == "BTC"][0]
eth = [i for i in j if i["currency_code"] == "ETH"][0]
return {"btc": btc["available"], "jpy": jpy["available"], "eth": eth["available"]}
# 証拠金残高を得る関数
def collateral(self):
j = self._private_api("/v1/me/getcollateral").json()
return {"collateral": j["collateral"]}
# 売り注文を実行する関数
def sell(self, price, amount):
j = self._trade_api(price, amount, "SELL")
return j["child_order_acceptance_id"]
# 買い注文を実行する関数
def buy(self, price, amount):
j = self._trade_api(price, amount, "BUY")
return j["child_order_acceptance_id"]
# 注文をキャンセルする関数
def cancel(self, oid):
j = self._private_api("/v1/me/cancelchildorder",
{
"product_code": self.CURRENCY_PAIR,
"child_order_acceptance_id": oid
})
return
# 注文状態を調べる関数
def is_active_order(self, oid):
j = self._private_api("/v1/me/getchildorders?" + urllib.parse.urlencode({
"product_code": self.CURRENCY_PAIR})).json()
t = [i for i in j if i["child_order_acceptance_id"] == oid]
if t and t[0]["child_order_state"] == "ACTIVE":
return True
return False
お次は、②①で作った自作モジュールから適宜メソッド(API)を呼び出して自動取引を行うプログラムを作成します。
import time
from bf_module import BFApi
# API_KEY API_SECRET のキーを入力する。取扱注意!
API_KEY = ""
API_SECRET = ""
# bitflyer取引所のパラメータ
order_min_size = 0.01 # BTCの数量最小値
order_digit = 3 # BTC数量の桁数
fee_rate = 0.0 # 取引手数料のレート(%)
# 1回の取引量
buy_unit = 0.01 # 購入価格
profit = 200 # 利益
api = BFApi(API_KEY, API_SECRET)
while True:
print(" ### bitflyer ### ")
print("BTC/JPYの自動売買取引を開始します")
time.sleep(5)
# 最終取引価格の取得
print("最終取引価格を取得しています")
buy_price = api.board()
print("購入予定価格は {0} 円です".format(int(buy_price)))
# BTCの購入数量の決定
buy_amount = round(buy_unit, order_digit)
print("BTCの購入数量は {0} です".format(buy_amount))
if buy_amount > 0:
# BTC残高が不十分なら終了
buy_amount = max(order_min_size, buy_amount)
# 残高を計算
balance = api.balance()
# JPY残高の確認
if balance["jpy"] < buy_amount * buy_price:
print("JPY残高が不足しています。")
continue
print("現在のBTC残高は {0} です".format(balance["btc"]))
# 買い注文の開始
print("買い注文を開始します")
print("購入価格 x 購入数量={0} x {1}".format(int(buy_price), buy_amount))
oid = api.buy(int(buy_price), buy_amount)
print("注文ID={0}".format(oid))
# 買い注文がサーバで処理されるまで少し待つ
print("サーバ処理中(3秒間処理待ち)")
time.sleep(3)
# さらに最大30秒間買い注文が約定するのを待つ
for i in range(0, 10):
if api.is_active_order(oid) == False:
oid = None
break
print("まだ約定に時間がかかりそうです({0}回目/10回中)".format(i))
print("少々お待ちを")
time.sleep(3)
# 注文が残っていたらキャンセルする
if oid != None:
api.cancel(oid)
print("約定できなかったため注文をキャンセルしています")
print("注文ID={0}".format(oid))
time.sleep(5)
# 購入予定価格の決定(bidの先頭)
sell_price = buy_price + profit # 購入価格に利益を加算して売り価格を算出
# 再売り注文
print("再売り注文をしています")
oid02 = api.sell(int(sell_price), buy_amount)
print("売却価格 x 売却数量={0} x {1}".format(int(sell_price), buy_amount))
print("注文ID={0}".format(oid02))
time.sleep(5)
# 注文が成立するまで永遠に待つ
while api.is_active_order(oid02):
print("少々お待ちを")
time.sleep(3)
print("売り注文が約定されました。")
print("注文ID={0}".format(oid02))
else:
print("売り注文が約定されました。引き続き買い注文へ移ります")
補足
プログラムの流れは下記の流れの繰り返しです。買い→売り注文が前提になります。
残高確認 → 買い注文 → 約定待ち → 約定完了 → 売り注文 → 約定待ち→ 約定完了 → 残高確認 ・・・
今後
他の言語(rubyとか)でも同様にプログラムを作っていきたい。あと、今回は「買う → 売る」の単純なプログラムだったので、「売る → 買う」バージョンも実装させたいす・・・いつか。
免責事項
利用・改善大歓迎ですが、損しても知りません。
参考情報
日経ソフトウエア 2017年9月号,2017年11月号