やっていくログ

にんげんをやっていきましょう

【Python】並行処理のおべんきょうの参考

参考として先人の記事のリンクを貼る

動かし方
qiita.com

https://qiita.com/__init__/items/74b36eba31ccbc0364edqiita.com

注意すべき点
qiita.com

並行処理関連の公式ドキュメント
docs.python.jp


試しに、5秒ごとに目的の動作を行わせる

import datetime
import time
import threading
import queue


def func1(event):
    while(1):
        event.wait()
        print('hoge')
        event.clear()
    
    
def func2(event):
    while(1):
        event.wait()
        print('fuga')
        event.clear()
    
        
def timer(event):
    while(1):
        time.sleep(1)
        now=datetime.datetime.now()
        print(now)       
        if now.second%5==0:
            event.set()           


if __name__ == "__main__":
    event = threading.Event()
    t1 = threading.Thread(target=func1, args=(event,))
    t2 = threading.Thread(target=func2, args=(event,))
    t3 = threading.Thread(target=timer, args=(event,))
    t1.start()
    t2.start()
    t3.start()

結果、5秒に1回 hoge fuga する

2019-02-26 00:07:32.680651
2019-02-26 00:07:33.700176
2019-02-26 00:07:34.703568
2019-02-26 00:07:35.704306
hogefuga
2019-02-26 00:07:36.713224
2019-02-26 00:07:37.718298
2019-02-26 00:07:38.732420
2019-02-26 00:07:39.739030
2019-02-26 00:07:40.753466
fugahoge
2019-02-26 00:07:41.767390
2019-02-26 00:07:42.777210
2019-02-26 00:07:43.786152
2019-02-26 00:07:44.799364
2019-02-26 00:07:45.815294
hogefuga
2019-02-26 00:07:46.828104
2019-02-26 00:07:47.832805
2019-02-26 00:07:48.848034
2019-02-26 00:07:49.858399
2019-02-26 00:07:50.863243
hogefuga
2019-02-26 00:07:51.865927
2019-02-26 00:07:52.879863
2019-02-26 00:07:53.895192
2019-02-26 00:07:54.898684
2019-02-26 00:07:55.899330
fugahoge
…

以後適宜追記

ZOZOの前澤社長がどのように100人抽選したかに関する一考察

※1/8 16時 表記の修正/1.2節補足/1.4節の追加/おわりに補足

 

 

はじめに

ZOZOの前澤社長の以下のツイートが年明け早々TLに溢れました。

1/8に当選者へのDMが送られたようですが、もしマトモに抽選したとしたら、どのようにこれだけ膨大な数の参加者から抽選したのでしょうか?

 

時間制約について

以下のツイートから、締め切りから抽選まで9時間以内に終えたことがわかります。

  

1. 選別せずに抽選した場合

該当のツイートをRTした人を列挙し乱数とかで抽選すれば公平そうです。ただこれだけ膨大になるとRTした人をどうやって抽出したのかが気になるところです。まずはRTした人を選別せずに抽選した場合について、以下のような仮説を検証していきます。

1.1 該当ツイートをRTした人のリストを直接入手した?

だれがRTしたかわかればそれが一番楽ですが、公式アプリやWebクライアントだと100人程度が表示限界のようです。そこでTwitterapiを使って入手できないかと考えます。しかし、apiでも直近100ユーザー分のidしか取得できないようです。*1

1.2 フォロワーのリストを入手してから該当ツイートをRTしているか調べる?

 1/8 12時時点で616万フォロワーですので、フォロワーの全貌を把握するのは難儀かと思いますが、一応このような手法も考えられます。フォロワーを取得するapiが存在し、1回のリクエストで5000 ids入手できます。*2 リミットは 15 times/15 minですので、5000 ids/min入手できます。これだとフォロワー全部取得するまで 1232 min = 20.5 h かかるので、時間制限に引っ掛かります。ここから各ユーザーのタイムラインを取得して該当ツイートが含まれているか(RTしたツイートが含まれている)調べることになるので、さらに時間がかかります。

フォロワーの取得を応募開始から随時行っていたとしたら、猶予が2日くらいに伸びます。その場合、フォロワーそれぞれについて、該当ツイートをRTしているか確かめます。あるユーザーのツイートを得るapiでは、リミットが900 times/15 minなので、60 times/minの頻度で確認できます。*3 この場合全フォロワーについて直近のタイムラインに該当ツイートのRTが含まれているか確かめることになります。しかし全フォロワーを終えるのに102,666 min = 71.2 dayかかります。また、1回のリクエストでは直近200ツイートまでしか取得できないので、ヘビーユーザーも除外されます。

1.3 ツイートを検索する?

[Twitter API] リツイートを100件より多く取得する方法 – プログラミング生放送

上記サイトにて紹介されていた方法では、1.1で述べた100ユーザー制限を突破できます。結果からidを抽出すればRTしたユーザーのリストが作れます。1回の検索で100件取り出せ、リミットは180 times/15 minですので、1200 ids/min入手できます。1/8 12時時点で該当ツイートのRT数は564万ですので、これだとRTしたユーザーをすべて取得するのに 4700 min = 78.3 h かかり、時間制限に引っ掛かります。

1.4 ユーザーidを乱数で生成し、そのユーザーがフォロワーかつ該当ツイートをRTしているか調べる

学科民をはじめとする方々から上記の意見をいただいたので追記します。

1.3までは、あてはまるユーザーを探しにいくという発想でしたが、逆にユーザーをランダムに選んでそのユーザーが当てはまっているか調べるという発想です。2018年10月時点でTwitterのアクティブユーザー総数は3億2600万、うち日本のユーザーは4500万。*4 また、1/8時点で前澤社長のフォロワー数は約600万です。したがって、全ユーザーの1.8%が前澤社長のフォロワーということになります(!?)。また、該当ツイートのRT数が1/8 12時時点で564万であり、仮にすべてフォロワーによるRTとすれば、この564万ユーザーを引き当てられればよいということになります。

ただ、これは現時点で生きているアカウントの総数であり、今までに数多くのアカウントが消えているため、もしランダムにidを指定しても生きているアカウントにヒットするかはわかりません。また、Twitterのユーザーidの生成規則を調べてみたのですが、明快な答えが得られずじまいでした。例えばTwitterのオフィシャルアカウント(@Twitter)のユーザーidは783214、自分のアカウント(2012/8作成)のユーザーidは9桁、最近作成されたアカウントだとユーザーidが10桁になっているようです。9桁対応でidを生成するのであれば条件に適合するユーザーへのヒット率は0.56%ですが、10桁対応するとヒット率が0.056%に落ちます。

生成されたidのアカウントがフォロワーかつ該当ツイートRT済みであることを確認するためには、手作業だと面倒なのでやはりapiに頼ることになります。まず社長をフォローしているか確かめます。あるユーザーのフォローリストを得るapiでは、リミットが15 times/15 minなので、1分に1ユーザーしかチェックできません。*5 RTしているか調べるのは、1.2節で述べたのと同様のapiを用います。

有効な100人を得るために、その100倍以上のidを生成して確かめなければいけないので、apiでやるには意外と時間がかかりそうです。もしこの手法を採用するなら、id生成した後は社員がやるのかもしれません。

 

2. リプライを送った人に限定して抽選した場合

 どうも1の手法だと時間制限に引っ掛かるっぽいので、リプライした人限定なのかもしれません。該当ツイートにリプライしたユーザーを列挙する手法について考えます。

ところが、「あるツイートに対するリプライ」を拾ってくるapiがないらしい。あるのは「あるユーザーに対するリプライ」を拾ってくるapiのみ。*6

該当ツイートについているリプライ数は39万、また該当ツイート以降のツイートについているリプライ数は、足し合わせても10万程度という感じです。したがって約50万のリプライのデータからidを抽出すればよいことになります。1回のリクエストで200件のリプライが取得でき、リミットは75 times/15 minですので、1000 replys/min入手できます。50万リプとして全所要時間は 500 min = 8.3 hですので、時間制限に引っ掛かりません。

リプしてきた人を重複を取りのぞいて100人抽選し、その人が当該ツイートをRTしていなかったり、フォロワーでなかったりしたら、追加で抽選していく感じになるかと思います。

 

おわりに

ちょっと検索してみると、実際に当選したのは具体的な目標を語ったリプを送った人が多いようです。届くDMにも、目標を応援している旨のメッセージが書かれているようです。最初は完全にランダムでやるつもりが、予想外に伸びてしまったため、急遽リプを送った人から選ぶ形に変えたのかもしれません。

また、Rate Limiting — Twitter Developersを見ると、

Standard API endpoints only, does not apply to premium APIs

 と書いてあります。Twitter Developersに課金すれば、ここまで述べてきたしょうもないリミットを回避することができるわけです。金は正義っぽい。

この企画に便乗してバラマキ企画をしている方で、本当にばらまく意思があって、課金もしたくない方は、上記の手法からお選びください(いるのか?)。

 

BitmexRektを題材に Tweetの取得・可視化・BTC取引botの話

この記事は eeic Advent calender 2018 その2 の第5日目の記事になっています。

4日目 EEIC走馬灯(落第学生三番煎じver) - acryloshobonileのブログ
6日目 eeic走馬灯(デバイス系かつ怠惰学生ver.)前編 - 自由帳

先輩方の走馬灯の間にお邪魔してすみませんというお気持ち…。
(強電系走馬灯、来年以降書いてみたい)

0. はじめに

電気電子工学科 B3 の者です。プログラミングは学科に入ってから始めました。普段は電力系の授業を受けたり実験をしたりしています。今回のコレはおべんきょうのための個人的な試みですが、アドベントカレンダーその2が空いてそうだったので記事としてまとめ、投稿させていただきます。
バックテストまでの全体のコードはGitLabにあげておきます。メインの処理は ipynb ファイルに記述しています。上手くないのでデータ保持の形式まわりがアレですが…。

1. BitmexRektについて

BitMEXとは、仮想通貨のレバレッジ取引を行う取引所です。Bitcoin をはじめいくつかの通貨が扱われており、取引量は世界最大級です。
一般にレバレッジ取引においては、ロスカットと呼ばれる自動精算システムがあります。(証拠金維持率)=100×(総資産評価額)/(必要証拠金) と定義し、証拠金維持率が一定の水準を下回ると、ロスカットが行われます。BitMEX では、ポジションが逆行して証拠金維持率が0%になるとロスカットされます。
BitmexRektとは、BitMEX において行われるロスカットを監視し、ツイートする bot のことです(図1)。ロスカットは一方的で大きな値動きがあるときに発生しやすいため、ロスカット発生情報は取引の参考とされています。
今回はこの BitmexRekt のツイートを取得し、得たデータをいじっていきます。

f:id:calpeace:20181213172254p:plain
図1 BitmexRektのツイート例

2. Twitterapiを使うために

2018年7月ごろに Twitterapi 制限が厳格化され、api を使うには Developer account としての登録が必要になりました。ここ で述べていますが、英作文をしなきゃいけないのでちょっと面倒です。アカウントを登録したら、app 登録を行って、api key など4種類取得します。

3. ツイートの取得

処理をpython-twitterというライブラリにすべてお任せします。
GetUserTimeline という関数を使うと、あるユーザーのツイートを取得することができるようなので、それを使います。
ツイートには id が振られており、それをもとに取得するツイートの範囲を指定することもできます。引数 max_id を使うと、そのid以前のツイートのみ取得することができます。
その他の機能については公式のドキュメントを参照してください。

今回は XBT/USD という Bitcoin 無期限取引のペアのみ抽出することとします。
Liquidated long は買い注文が清算されたことを表し、価格の下落が起きていることを示唆します。
Liquidated short は売り注文が清算されたことを表し、価格の上昇が起きていることを示唆します。
ロスカットが発生した時刻、方向、枚数を1セットのデータとして保存します。ここで方向については、買い注文の清算を-、売り注文の清算を+と定めます。
実際のコードは以下のように適当に。1サイクルで200ツイートが取得上限です。とりあえず600ツイート手に入れます。各種 key は予め読み込んでおきました。

api = twitter.Api(consumer_key=CONSUMER_KEY,
                  consumer_secret=CONSUMER_SECRET,
                  access_token_key=ACCESS_TOKEN_KEY,
                  access_token_secret=ACCESS_TOKEN_SECRET)
liquidated_list=[]
last_id=0

for i in range(3):
    if i==0:
        tweets = api.GetUserTimeline(screen_name="BitmexRekt",count=200)
    else:
        tweets = api.GetUserTimeline(screen_name="BitmexRekt",max_id=last_id-1,count=200)

    for tweet in tweets:
        word_list=tweet.text.split()
        if 'XBTUSD:' in word_list:
            if word_list[1]=='long':
                amount=(-1)*int(word_list[5].replace(",",""))
                at_price=float(word_list[7])
                liquidated_list.append([tweet.created_at,amount,at_price])
            elif word_list[1]=='short':
                amount=int(word_list[5].replace(",",""))
                at_price=float(word_list[7])
                liquidated_list.append([tweet.created_at,amount,at_price])
        last_id=tweet.id

#以下は word_list の format
#Liquidated short on XBTUSD: Buy 11,305 @ 3823.5

こうするとこんな感じのデータが格納されます。時刻、清算枚数、清算価格の順になっています。

 ['Thu Dec 13 09:04:55 +0000 2018', 47757, 3397.5],
 ['Thu Dec 13 09:04:50 +0000 2018', 83828, 3393.5],
 ['Thu Dec 13 09:04:44 +0000 2018', 43445, 3387.0],
 ['Thu Dec 13 09:04:37 +0000 2018', 225990, 3383.5],
 ['Thu Dec 13 09:04:32 +0000 2018', 223023, 3377.0],
 ['Thu Dec 13 08:27:10 +0000 2018', -499, 3358.0],
 ['Thu Dec 13 07:59:55 +0000 2018', 58351, 3374.5],
 ['Thu Dec 13 07:59:50 +0000 2018', 2682, 3373.5],
 ['Thu Dec 13 06:00:05 +0000 2018', 32335, 3368.0],
 ['Thu Dec 13 06:00:02 +0000 2018', 36405, 3364.0],
 ['Thu Dec 13 05:59:47 +0000 2018', 5010, 3360.5],
 ['Thu Dec 13 05:59:41 +0000 2018', 107903, 3358.5],
 ['Thu Dec 13 05:51:39 +0000 2018', -35209, 3338.5],
 ['Thu Dec 13 05:50:29 +0000 2018', -6002, 3342.5],
 ['Thu Dec 13 05:50:22 +0000 2018', -158472, 3344.5],
 ['Thu Dec 13 05:29:11 +0000 2018', -72500, 3346.5],
 ['Thu Dec 13 05:29:04 +0000 2018', -68622, 3348.0],

4. 実際の値動きとともにグラフを描画

cryptowatchapi を使うと各取引所の直近の価格データを手に入れることができます。
価格データの抽出は get_ohlcv.py の中に記述してあります。取引所と単位時間(1min, 3min, 5min, 15min, 30min, 60min 等)を与えると時刻、始値、高値、安値、終値を返す関数を用意してあります。
※12/18 追記
稀に欠損値 all 0 が返ってくることがある模様…?

取得したツイートから得たロスカット情報は、longとshortに分けて保持します。
また、グラフの開始時刻を価格データのスタート時刻にするため、それより昔のロスカットデータを捨てて整理します(価格データは1分足だと500分ぶんしかないので、だいたいロスカットデータのほうが余る)。
この辺の実装は冗長なだけなので具体的なコードは割愛します。

価格とロスカット情報を2段に分けてプロットさせると図 2 のようになります。青は long のロスカット、赤は short のロスカットを表しています。2段目のグラフの縦軸は清算枚数を表しています。おおまかに、価格の下落により long のロスカットが発生し、上昇により short のロスカットが発生している様子が見て取れます。
また、価格とロスカット情報を1つにまとめてプロットさせると図 3, 4 のようになります。ロスカット情報を表す点は、濃度が清算枚数と対応しています。これを見ると価格の上下とロスカットの対応の様子がより分かりやすいかと思います。

f:id:calpeace:20181213200925p:plain
図2 BitMEX価格とロスカット散布図

f:id:calpeace:20181213201002p:plain
図3 BitMEX価格とロスカット散布図

f:id:calpeace:20181213203729p:plain
図4 BitMEX価格とロスカット散布図(別時間帯)

5. 取引 bot 化に向けたバックテストと課題

4まででは、過去のロスカットを抽出し可視化してみました。
ここで、リアルタイムでロスカットツイートを取得することにより、取引 bot のシグナルとして利用できるのではないかという目論見が立ちます。
そこで、得たロスカットデータをもとに、簡単なロジックのバックテストを行ってみました。
バックテストのコードもメインの ipynb ファイル内に記述してあります。

取引ルールは以下のようにします。

long のロスカットが発生したら short で entry、short のロスカットが発生したら long で entry。
価格は、ロスカットが発生した時刻の分足の終値で entry、次の分足の終値で exit とします。
コードでは、この辺の処理がしやすいように、途中のデータ整理で時刻の秒を切り捨てています。
(ex. hh:mm:ss = 19:22:30 にロスカットが発生したら、19:22の分足の終値で entry、19:23の分足の終値で exit する。言い換えれば 19:23:00に entry、19:24:00に exit)

ここで、
profit factor = (総利益)/(総損失の絶対値)
max drawdown : グラフの最大へこみ
と定義します。
PF は大きいほうがよく、drawdown の絶対値は小さいほうがよいです。

まずはロスカットデータの元である取引所 BitMEX でのバックテスト結果を2例示します(図5, 6)。
500分ぶんの価格データを利用したバックテストで、図 5 と図 6 はそれぞれ別の日に採取したデータを用いています。
ロスカットが多いときはモリモリ、少ないときも着実に積み重ねている様子が見て取れます。

f:id:calpeace:20181213210455p:plain
図5 BitMEXにおけるバックテスト
f:id:calpeace:20181213210523p:plain
図6 BitMEXにおけるバックテスト(2)

次に、国内の取引所 bitFlyerレバレッジ取引価格データを使ってバックテストします。
シグナルは BitMEX のロスカットデータですが、価格は相互に追従しあうものだと思われるため、試してみました。
結果を2例示します(図7, 8)。グラフはBitMEXのバックテスト結果とよく似ています。強いですね。

f:id:calpeace:20181213211325p:plain
図7 bitFlyerにおけるバックテスト
f:id:calpeace:20181213211353p:plain
図8 bitFlyerにおけるバックテスト(2)

しかし、果たしてそうでしょうか。

おそらく、実際にこのロジックを bot 化して運用しても、ここまでうまくはいかないのではないかと思われます。以下に述べるような問題点があるためです。

  • ロスカットが発生するような場面では、サーバが遅延する

実際に注文を出す際には、注文を出した瞬間の価格で必ず約定するわけではありません。ある程度の価格のブレがあり得ます。
特に、ロスカットが発生するような場面では、価格の激しい上下動があり、注文数も増加するため、取引所のサーバの遅延が起きます。
今回のバックテストでは、必ず終値で約定するとしているため、この影響は無視できないわけです。

  • 売り板と買い板のスプレッド損

売りたい人は少しでも高く売りたい、買いたい人は少しでも安く買いたい、それゆえ売り板と買い板には価格差が生じます。
この価格差はスプレッドと呼ばれます。取引所を実際に眺めてみるとわかりやすいかと思います。
実際に entry する場合には、成行注文/指値注文のどちらかを選択して注文することになります。
成行注文とは、価格はいくらでも構わないので買わせてくれ/売らせてくれ、という注文方式です。
すぐに約定する代わりに、先ほど述べたスプレッドぶん不利な価格で売買していることになります。
指値注文とは、価格を指定して注文する方式です。
スプレッドによる不利益はなさそうですが、約定せずにさらに価格が上昇/下落して、チャンスを失ってしまう可能性があります。
バックテストでは、この影響を無視して必ず終値で約定するとしていましたので、実際の結果はこの影響を受けて変わってくるでしょう。

あくまでもデータで遊んでみた!という体験をまとめたものですので、これを鵜呑みにして取引されても一切責任はとりません。

※12/16 追記
フォワードテストもやってみているので、その結果も書くかもしれない。
※12/20 追記
フォワードテスト第1弾、書いた。

6. 取引 botフォワードテスト

せっかくなので、BitMEXのロスカットデータを生かした bot を作ってみます。
bitFlyer でのレバレッジ取引です。python で書いて AWS Cloud9 上で動かしています。
pybitflyer や ccxt を使ってちょっと慣れればシンプルな売買はすぐ実装できます。
先駆者の数多くの指南記事がありますので、興味があればググってみてください。

第 1 弾のロジックは、5 章でバックテストしたものを微調整して、以下の通りとしました。

(例)

時刻 ロスカット 取引
22:13:00~22:13:59 0
22:14:00~22:14:59 -200
22:15:00~22:15:59 -300 22:15:00 short entry
22:16:00~22:16:59 0 short hold
22:17:00~22:17:59 0 22:17:00 short exit

entry を指値にしたのは、最初に entry と exit どちらも成行にしたものを組んで回してみた結果、あまりに危なっかしかったからです。
また、ロスカットが続いている間は exit しないことに決めたのは、取引回数が増えることによる成行スプレッド損を軽減するためです。
たぶんロスカットが続いている間は利益が出る方向に価格が推移しているので、ポジションを持ちっぱなしでも大丈夫だと考えました。

図10のように、勝とうが負けようが bot は淡々と取引を行っていきます。

f:id:calpeace:20181220003112p:plain
図10 取引の様子

図 11 に、実際の損益推移を示します。ログインボーナスでもらえる微小な BTC 現物の価格変動の影響があるため、bot が止まっているときも損益変動が少しあります。
バックテストではドローダウンがほとんどなかったのに、この結果を見ると命からがら生き延びているだけという感じがします。
ロットを 0.05 としているので、バックテストの結果のおよそ 1/20 が 500 分で得られる収益の理論値なわけですが、とてもじゃないがそのレベルに到達していないようです。
実際に bot を回してみると、サーバーが注文を受け付けなかったり、ラグがあったり、価格が上下にブレて不利な約定価格になっていたり、といった問題に直面します。
これを書いている間に、所有ポジションをチェックするあたりで書き換えるべき点が見つかったので、もうすこし改善の余地はありそうです。
今後もロスカットデータを生かした bot のアイデアを試していきます。良さげなモノがあればここに追記していきたいと思います。

f:id:calpeace:20181220010405p:plainf:id:calpeace:20181220010509p:plain
図11 損益推移(左: 11:00ごろスタート / 右: 09:00ごろスタート)

7. おわりに

f:id:calpeace:20181213214511p:plain
図12 Bitcoin年初来チャート(trading viewより)
昨年の今頃は少なからぬ人々がビットコインビットコインだと言っていた印象ですが、今年はモリモリ下り坂です(図9)。
ただ、取引所を筆頭に api をモリモリ公開してくれたり、ccxt をはじめとする優秀なライブラリがあったりするおかげで、プログラミングの経験が浅い自分のような人でも、おべんきょうの題材とするのには悪くないんじゃないかと感じています。また、自分で組んだ bot が健気に動いている様子を眺めていると、結果がショボかろうと愛しさが湧いてきます。おもしろいものです。

日々控室で同期を見ていると、この学科には様々な分野に長けた強い人が多いなぁと感じます。
そんな強い人々が取引 bot 作りに手を出したらきっとすごいことになるんじゃないかと思いつつ、筆をおきます。

最後までお付き合いいただきありがとうございました。

Twitter apiを使うためにDeveloper accountを申請する

2018/07からTwitter apiの使用が厳しくなり、ちょっと検索apiを叩こうにもDeveloper accountを作らなければならなくなった模様。

apiに関するツイートを検索してみると、厳格化に対する嘆きが散見される。

しかし、やりたいことがちょっと思いついてしまったので、やるしかない。

 

とりあえず申請。

"Twitter api"でサーチすると(たぶん)一番上に出てくるDeveloperページからapplyを選択し、順に次のような項目を入力。

 

・User profile

 どのアカウントと連携させるか?

・Account details

 組織or個人(非商用)の選択

・Use case details

 どのような目的でapiを利用するのか選択

 さらに300文字以上の英作文

  1.apiの使用目的

  2.コンテンツの分析の有無と分析手法

  3.自発的なツイート・リツイート・いいねの有無

  4.取得したデータを具体的に表示するか統計的に処理して表示するか

 

申請が通ったら更新する。

 

追記

1時間もしないうちに許可された わいわい!

f:id:calpeace:20181203043603j:plain

 

cryptactの未分類取引と格闘する

はじめに

cryptactは、仮想通貨取引における煩雑な税金計算をアシストしてくれる無料ツール。各取引所が提供する取引履歴をアップロードしていけば、自動で各年の収支をはじき出してくれる。

ただ、実際には未分類取引、すなわちエラーが発生する場合があり、正確に計算するためには、そうした未分類取引をなるべく解消しなければならない。

以下に、使っていて気付いた留意点を書きとめておく。新たな発見があれば時々更新したい。

 

全体として押さえておくべきこと

・時刻の情報を正確に

計算にあたって、すべてのデータを時系列に並べている。積立やチップで得たコインの取得日時をも正確に記述する必要がある。順番がずれると、コインが足りないとされて未分類取引になってしまう。

また、高頻度系botを回している場合、同じ秒に別種の約定が発生する場合がある。このとき、近くの別の約定もまとめられてしまう場合がある。順番に通貨ペアを渡り歩くような発注で、未分類取引にされてしまう場合は、1秒程度各約定時刻を離して記述しなおすとうまく通ることがある。

 

・データを分割して保管

データのデバッグにあたって、年をまたいでいるデータファイルがある場合、1年ごと、あるいは数か月単位で区切って保管しておくと、どこから未分類取引が生じたのかがわかりやすくなる。アップロードしていくときは、古いものから順に追記していくとよい。

 

Zaif

・コイン積立

2018/11/24現在、cryptactはZaifのコイン積立に対応していないので、カスタムファイルを作って補完しなければならない。カスタムファイルのフォーマットはcryptactのサポートページから手に入る。またコイン積立の履歴はZaifのユーザーページから見ることができるが、csvなどでダウンロードできるわけではないので、手でデータを採取してくるしかない。コイン積立の履歴ページから、excel等にコピペして整形するとよい。

  

・簡単売買

BitFlyerの販売所などの場合、販売所の売買履歴もcsvとして手に入る。しかし、zaifの簡単売買は、簡単売買のページに飛んでそこで履歴を手動で取得してこないといけない。取得した履歴をカスタムファイルに追記すると反映される。

 

Twitterチップ

チャットチップの送受信履歴はcsvでダウンロードできるが、Twitterチップの履歴はそうではないらしい。ソーシャル設定→プロフィールと進んで履歴を取得しないといけない。しかも最新の履歴数件しか表示されていない。Twitter受取チップ総額は表示されているので、一応総額は把握できるが、正確な受取時刻がわからない場合が出てくる。

 

・残額調整履歴

ハッキングを受けたMONAやBCHの返却が11月末ごろから始まった。場合によってはハッキング時の保有コイン数の数割を日本円で返金している模様で、その履歴は残額調整履歴のところに載っている。この分は、コインを売った形とみなして、カスタムデータに記述しておかないと未分類にされてしまう。

 

・信用売買

現物のBTCの板をそのまま利用した信用取引を行うことができる。そこでは、JPY以外にもBTCやXEMといった仮想通貨を証拠金として利用することができる。ポジションを決裁したときには証拠金として預けていた仮想通貨が返金される。もし損失が出たら、まずはJPYから充当されるが、それで足りない場合は証拠金の仮想通貨から補填する。このあたりの処理で、現物の残高がマイナスになると言われる場合がある。これは、複数のポジションを保有しているときに、エントリーの順番と決済の順番が入れ替わると発生する可能性がある。空売りを現物売りと同様に処理しているため現物が実態以上に減っていることになる。仕方がないので、カスタムファイルで、エラーが出る注文群の先頭で現物を補充し、全ポジション決済後にその現物を同値で売ったことにして対処する。

 

csvデータの更新

zaifでは毎日夜にcsvデータが更新される。更新時の1日前の取引までが記載される模様。

↑ 5日に1回くらいしか更新されてないみたい…?

1日1回更新申請が行えるので、前日までの取引は手に入れられる模様(2019/2/16 追記)

 

BitFlyer

・Lightning 取引履歴

Lightningの履歴を普通にダウンロードしてくると、最近の65535件しか記載されていない。仕方がないのでデータ取得の日付範囲を指定して取得する。高頻度系botをやっている人は特に大変だと思う。

データシートにはParent Order, Child Order, Executionsの3種記載されているが、実際に計算の際見ているのはExecutionsの模様。もしノーポジのはずなのにBTC-FXの所有枚数が0になっていない場合は、Executionsの記載の過不足を確認するとよさそう。

取引履歴(4種)を一括でダウンロードできるようになったので、上記問題は解消。

 

bitbank

・取引履歴

デフォルトの取引履歴は直近48時間分から最大500件まで取得可能。それ以前の履歴は "2日(48時間)以前のデータはこちらから検索できます" というところから同様のページに飛べるが、ここでも500件ごとにしか取得できない。API叩いても500件しか取得できない。高頻度系botをやっている人はめちゃくちゃ大変だと思う。

サポートに問い合わせてみたら、1週間くらいで送ってくれるとのことである。

→1週間後くらいに、全取引履歴csvへのリンクを送ってくれた。個別対応するほうがむこうとしてはコストがかからないんでしょうか…

2019年verの取引履歴を出力してもらうときは 30 分程度で送付してくれた。

 

Liquid

・ゼロ数量売買はSelf-matchをチェック

現物取引でゼロ数量売買の未分類エラーが出る場合がある。その場合は、データをチェックし、売買形態がSelf-matchになっている取引を見つける。Self-matchのうち、手数料が発生せず、かつJPYが絡んでいない取引の場合、エラーを返す模様。そうした取引を削除する。

 

・有効桁数の問題

Liquid は他の取引所に比べ各コインの表示桁数が多いので、端数が積もってズレが生じていそう…。

 

海外未対応取引所

・時刻のタイムゾーンに注意

デフォルトで対応している取引所であれば、タイムゾーンの処理は自動で行ってくれる。しかし、まだ対応していない取引所での取引を記録する際には、カスタムファイルを用いる必要がある。ここで、海外取引所では、UTCで時刻を表記している可能性があるので、おそらく日本標準時に合わせたほうがうまくいく。

 

カスタムファイル

・数式を利用した excel シートはたぶんアップできない

excel シートを csv に変換しましょう

 

csvexcel でいじるとエラー吐かれるもの 

 bitbank, Liquid

(excelcsv 出力のデフォルト文字コードは Shift-JIS だが、これらの取引所から出力される csv文字コードUTF-8 であるため。また、UTF-8csv 出力したとしても、最後尾の列に意図せぬ空列が入り込み、エラーが起きる場合がある。このときは VS Code をはじめとするテキストエディタで編集するとよい)

【Python】浮動小数点処理とdecimalモジュール

・はじめに

cryptocurrency bot制作でしばらく悩まされていた不具合の原因が小数計算にあることに気づき、Pythonにおける浮動小数点処理を調べる必要に迫られた。decimalモジュールを利用する際の備忘録として以下にまとめる。付け焼刃なので間違っているかも。

・とりあえず使うために

まずはインポート

from decimal import * 

Decimal()の引数に数値か文字列を与える。数値で与えるとfloatと同じ。文字列で与えると10進数同様の取り扱いができる。こうすれば四則演算は普通に行ってよい。

a=Decimal(0.1) #floatとおなじ
b=Decimal('0.1') #10進数扱い
c=0.1 #float
d=Decimal(str(c)) #10進数扱い

丸め処理は.quantize()を用いる。roundingを指定することでいくつかの丸め処理が可能。とりあえずゼロ丸めだけしたかったのでその例のみ記す。quantizeの第1パラメータには揃えたい桁数を有する値をDecimal形式で与える。1E-4などで与えてもよい。第2パラメータにはroundingを指定する。指定しなければDecimalのデフォルトの丸め形式が実行される。

Decimal('0.01283').quantize(Decimal('0.01'),rounding=ROUND_DOWN) #0.01
Decimal('0.314155').quantize(Decimal('1E-2'),rounding=ROUND_DOWN) #0.31

つの丸め処理しかしない場合はデフォルトの丸め形式を指定しておくと楽そう。またデフォルトの有効桁数を指定することもできるようである。

getcontext().prec=24 #24桁にする
print(Decimal('0.00000038471942')/Decimal('0.0000312413')) #0.0123144497828195369590894
getcontext().rounding=ROUND_DOWN #切り捨てに設定
getcontext().prec=10 #10桁にする
print(Decimal('0.00000038471942')/Decimal('0.0000312413')) #0.01231444978

なお、decimalで得た数値を他の関数に与えるなどの場合はintやfloatにキャストしてあげないといけない。

・速度を調べる

floatで計算したときより速度が遅いらしいので、雑に速度を比較した。以下のようにひたすら小数を足し上げるのにかかる時間を測定。

import time
from decimal import *

start=time.time()
sum1=Decimal('0')
for i in range(1000):
    sum1+=Decimal('0.1111')
end=time.time()
print(sum1,end-start)

start2=time.time()
sum2=0
for i in range(1000):
    sum2+=0.1111
end2=time.time()
print(sum2,end2-start2)

値と実行時間の結果は以下の通り。

float合計 decimal合計 float実行時間 decimal実行時間
100回 11.110000000000026 11.1100 0.0009992123 0.0009994507
1000回 111.0999999999974 111.1000 0.0010001659 0.0009992123
10000回 1110.9999999999386 1111.0000 0.0019972324 0.0049958229
100000回 11110.00000000701 11110.0000 0.0129878521 0.0489549637
1000000回 111099.99999810797 111100.0000 0.1228837967 0.4615705013
10000000回 1110999.9999210974 1111000.0000 1.1908910275 4.4668421745

単純な足し算だからか、実行時間はほぼ線形に増加した。floatで扱ったときとdecimalで扱った時では約4倍の開きがあった。計算がより複雑になると変わってくるかもしれないが、まあメチャクチャ遅いって感じはしない。

仮想通貨取引所用ライブラリ ccxt を導入する

仮想通貨取引botを細々といじるにあたり、今までは取引所ごとに異なるライブラリを用いていた(e.g. pybitflyer, zaifapi)が、海外取引所などもまとめて一つのライブラリccxtで扱えるらしいので、それぞれの環境に導入した。

 

・at AWS Cloud9

 ターミナルで $sudo pip-3.6 install ccxt とすればモリモリインストールされる。

 

・at jupyter notebook

 Anaconda prompt で $pip install ccxt とすればモリモリインストールされる。あるいは白紙のnotebookで !pip install ccxt としても可能。

error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools"

って怒られたので、visual studio build tools 2017 を拾ってきてから再度試したらモリモリインストールが完了した。