キジトラのブログ

仮想通貨取引botなどについて書いていきたいと思います

bitFlyer FXの約定履歴から損益グラフを描くpythonプログラム

今日はbitFlyer FXの損益グラフを描くプログラムを公開したいと思います。 bitFlyerにはデフォルトで損益グラフを表示する機能がありますが、現物もFXも全部込みの損益グラフが表示されるため、現物持った状態でFXでbotを動かしていると、純粋なbotの損益を知ることができません。 また、損益しか表示されないので、価格変化との関係性や、取引ごとのポジションの変化もグラフだけからは読み取れません。

今回ここで公開するプログラムは、APIから自分の約定履歴を取得し、価格、損益、ポジションをプロットするものです。 例えばこんなグラフを描くことができます。

f:id:kijitora_2018:20190321103828p:plain

グラフからは、ここで想定以上にポジション掴んでるなとか、ここの急変動で焼かれてるな、などということを読み取れるようになります。

なお、私はtwitterでもたまにこの損益グラフを晒していますが、たいしたbotでもないのでどうせ参考にならんでしょと思って晒しています。 botにおいて重要なのは見ている指標だと思いますが、正直自分でこのグラフだけ見ても自分のbotのロジックを推測できません… 強botterなら推測できるかもしれませんが、そんな人は私ごときのbotは参考にするまでもないはずなので…

というわけで、ロジックばれを恐れず損益を公開してくれる人が増えたらいいなぁということでプログラムを公開します。 どんなbotがいるんだろうという興味本位です(笑)

なお、私は普段pythonはほとんどさわらないので、初心者丸出しのコードになっています。 forとかappendバリバリ使ってますが、温かく見守ってもらえると助かります。

約定履歴取得&csv保存プログラム

指定した期間の自分の約定履歴を取得し、csv形式で保存します。

※Private APIをさわっています。悪意のあるコードは書いていませんが、コードをよく読んで、おかしな挙動をしていないか確認し、自己責任で実行してください。トラブルが起こっても責任は取れません。

import pandas as pd
import pybitflyer
from dateutil.parser import parse
import datetime
import time
from pytz import timezone

#約定履歴取得期間
t_start = parse("2019-03-20 00:00:00" + "+09:00")
t_end = parse("2019-03-21 00:00:00" + "+09:00")

#bitflyer APIキー
api_key = "****"
api_secret = "****"

api = pybitflyer.API(api_key=api_key, api_secret=api_secret)

product_code = "FX_BTC_JPY"

list_executions = []

executions = api.getexecutions(product_code=product_code, count=500)
count = 0
while len(executions) > 0:
    for execution in executions:
        dt = parse(execution["exec_date"] + "+00:00").astimezone(timezone("Asia/Tokyo"))
        if t_start <= dt < t_end: #取得期間内なら
            list_executions.append([dt, execution["side"], execution["price"], execution["size"]])
    
    if dt < t_start:
        break
    else:
        last_id = executions[len(executions) - 1]["id"]
        print("取得中..." + dt.strftime("%Y-%m-%d %H:%M:%S"))
        time.sleep(2)
        executions = api.getexecutions(product_code=product_code, count=500, before=last_id)

df = pd.DataFrame(list_executions[::-1], columns=["date", "side", "price", "size"])

df.to_csv("executions.csv")

上のプログラムは愚直に最新の約定履歴から順番に取得するので、昔の約定履歴を取得する場合は工夫が必要です。 Nagiさん(@Nagi7692)のnoteが参考になるかと思います。

note.mu

損益プロットプログラム

上のプログラムで保存したcsvから、損益をプロットします。なお、bitFlyerの丸め処理やSFDは考慮していないので、厳密な精度はありません。 また、約定履歴保存期間の初期ポジションを手動で入力する必要があります(約定履歴から知る術を思いつきません…)。 ポジションが0に収束するタイプのbotであれば、ポジションの平均値が0になるようにすればある程度正しいと思います。

また、コード中にコメントで書いていますが、損益を正規化して絶対値を隠したり、ポジションのプロットの線を太くして細かい情報を隠したりできます。 差支えない範囲でいいので、損益を公開するbotterが増えたら嬉しいです。

import pandas as pd
import statistics
import matplotlib
import matplotlib.pyplot as plt
import pylab
import matplotlib.dates as mdates

init_position_BTC = 0 #初期ポジションを手動で指定

df = pd.read_csv("executions.csv")

date = pd.to_datetime(df["date"])
side = df["side"]
price = df["price"]
size = df["size"]

position_BTC = init_position_BTC
position_JPY = -init_position_BTC * price[0]
list_price = []
list_position = []
list_profit = []
list_date = []

#計算
for i in range(len(date)):
    list_date.append(date[i])
    list_price.append(price[i])
    if side[i] == "SELL":
        delta_BTC = -size[i]
        delta_JPY = size[i] * price[i]
    else:
        delta_BTC = size[i]
        delta_JPY = -size[i] * price[i]
    
    position_BTC += delta_BTC
    position_JPY += delta_JPY

    list_position.append(position_BTC)
    list_profit.append(position_BTC * price[i] + position_JPY)

ave_position = statistics.mean(list_position)
print("average position : {0}".format(ave_position))

print("profit : {0}".format(list_profit[len(list_profit) - 1]))

#正規化したかったら
#profit_max = max(list_profit)
#list_profit = list(map(lambda x: x / profit_max, list_profit))

#グラフ
matplotlib.rcParams["timezone"] = "Asia/Tokyo"

fig = plt.figure(figsize=(10, 4))
fig.subplots_adjust(left=0.1, bottom=0.15, right=0.9, top=0.95)

#price
ax1 = fig.add_subplot(2, 1, 1)
ax1.plot(list_date, list_price, "C0", label="Price")

#profit
#第2Y軸
ax2 = ax1.twinx()
ax2.plot(list_date, list_profit, "C1", label="Profit")

h1, l1 = ax1.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax1.legend(h1+h2, l1+l2, loc='lower right')

ax1.set_ylabel("Price [JPY/BTC]")
ax1.grid(True)
ax2.set_ylabel("Profit [JPY]")

pylab.setp(ax1.get_xticklabels(), visible=False) 

#position
ax3 = fig.add_subplot(2, 1, 2)
ax3.plot(list_date, list_position) #線を太くしたかったらlinewidth=5とか
ax3.set_ylabel("Position [BTC]")
ax3.grid(True)
ax3.set_xlabel("Date")
ax3.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d %H:%M"))

plt.show()

バグなどありましたら、コメントかtwitterで教えていただけると助かります。