Zaif取引APIで取引履歴をエクセル出力

# きっかけ
Zaif取引所で取引履歴を出力しようとしたが、一時停止中だったため、勉強がてらAPIで取引履歴を取得してみることにした。

[取引履歴CSVデータのダウンロードの一時停止のお知らせ](https://corp.zaif.jp/info/8027/)

## １．環境構築
①OS
Windows10

②言語
Python

Zaif取引所APIにはJava、JavaScript、Ruby、PHP、Golang、Pythonのライブラリがある。
どの言語にしようか迷ったのだが、下記記事を見てPythonでやってみることにした。

参考：[【データから紐解く！】2017年の人気プログラミング言語を徹底比較！](https://techacademy.jp/magazine/8735)

Pythonをインストール。バージョンはPython 3.6.4。

参考：[Pythonをインストールする（for Windows）](https://qiita.com/taiponrock/items/f574dd2cddf8851fb02c)

③開発環境
サクラエディタ

普段仕事で使用しているサクラエディタでコーディングすることにした。
サクラエディタは、Altボタンで矩形選択などもできるので、普段から重宝している。
下記を参考に、サクラエディタのマクロでデバックできるようにした。

参考：[Python用にサクラエディタをインストールしてみた](http://turtlewalk.hatenablog.jp/entry/2015/12/27/073918)

Ctrl+0で別Windowで結果を出力できる(下の画像みたいな感じで出ます)。
<img width="771" alt="sakura_dev.png" src="https://qiita-image-store.s3.amazonaws.com/0/228630/b5851672-c752-1207-f655-34e98cc6db4d.png">


なお、Pythonについて調べている途中でCloud9というよさげなIDEがあるということを知ったのだが、既にサクラエディタでPython用のタイプ別設定まで済ませてしまっていたので、そのままサクラエディタで続行した。

参考：[AWS Cloud9](https://aws.amazon.com/jp/cloud9/)


④ライブラリ
・Zaif取引所API用
Zaif取引所APIを簡単に呼べるPython用のライブラリを導入。

参考：[zaifのAPIを簡単にコール出来るようにしました。](https://github.com/techbureau/zaifapi)

・エクセル操作用
openpyxlを導入。

参考：[[Python] openpyxl で Excel を操作してみた！]
(http://note.crohaco.net/2017/python-openpyxl-excel/)


## ２．print関数で出力
いきなりエクセル出力する前に、まずはprintでそれっぽい一覧を出力してみることにした。

①コード

```python3:zaif_trade_print.py
# Zaif取引APIでトレード履歴を一覧表示
from zaifapi import ZaifPublicApi
from zaifapi import ZaifTradeApi
from datetime import *
from decimal import Decimal

KEY = "取引所のAPI_KEY"
SECRET = "取引所のAPI_SECRET"
zaif_public = ZaifPublicApi()
zaif_trade = ZaifTradeApi(KEY,SECRET)

print("トレード履歴一覧(最大1000件)")
#トレード履歴取得
trade_list = zaif_trade.trade_history()

#ヘッダー
print("no  |order_id|pair    | act|amount        |price         |fee   |fee_amount  |yact|bonus|timestamp          |comment")
print("項番|注文id  |通貨ペア| b/a|数量     :単位|価格     :単位|手数料|手数料 :単位|b/a |ボナ |取引日時           |コメント")
print("------------------------------------------------------------------------------------------------------------------------")
#トレード数分繰り返し(MAX1000)
for i, trade_key in enumerate(trade_list):
    #単位付与
    unit = trade_list[trade_key]['currency_pair'].split('_')
    #'bonus'がNoneだった場合0に置換
    trade_list[trade_key]['bonus'] = str(trade_list[trade_key]['bonus']).replace('None', '0')
    #項番(インデックス+ 1)・キー(注文id)・キーに紐付くValue(注文の中身)を表示
    print("{0:>04d}".format(i + 1),                                                                   "|", #項番      (int)
          "{0:>8s}" .format(trade_key),                                                               "|", #注文id    (str)
          "{0:>8s}" .format(trade_list[trade_key]['currency_pair']),                                  "|", #通貨ペア  (str)
          "{0:>4s}" .format(trade_list[trade_key]['action']),                                         "|", #bid/ask   (str)
          "{0:>10s}".format(str(Decimal(trade_list[trade_key]['amount']))[0:10])   ,unit[0].ljust(4), "|", #数量      (float)←dec経由でstrに変換
          "{0:>10s}".format(str(Decimal(trade_list[trade_key]['price']))[0:10]),    unit[1].ljust(4), "|", #価格      (float)←dec経由でstrに変換
          "{0:>6s}" .format(str(Decimal(trade_list[trade_key]['fee']))[0:6]),                         "|", #手数料    (float)←dec経由でstrに変換
          "{0:>8s}" .format(str(Decimal(trade_list[trade_key]['fee_amount']))[0:8]),unit[0].ljust(4), "|", #手数料分  (float)←dec経由でstrに変換
          "{0:>4s}" .format(trade_list[trade_key]['your_action']),                                    "|", #bid/ask   (str)←自己取引時はboth
          "{0:>5s}" .format(str(trade_list[trade_key]['bonus'])),                                     "|", #-手数料分 (NoneType)←NoneTypeってあり？
          str(datetime.fromtimestamp(int(trade_list[trade_key]['timestamp']))),                       "|", #取引日時  (UNIX_TIMESTAMP)←datetimeに変換
          trade_list[trade_key]['comment'],                                                                #コメント
          sep = '') #カンマ前後の空白削除
```

②出力結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/228630/4f0db31c-444d-7ebe-b79b-96d428dd9429.png)

③つまづいた点と解決方法の参考リンク
(1)JSONの扱い
APIの戻り値がJSON形式なので、そこからどのように使いたい項目の値を取り出すか。

参考1：[PythonにおいてのJSONファイルの取扱いあれこれ]
(https://qiita.com/wakaba130/items/5f54aed913156dc4438f)
参考2：[dictionary(辞書)型のループ処理]
(http://www.python.ambitious-engineer.com/archives/186)

(2)Pythonでの桁合わせ
桁合わせしないと取引によって表示がずれるため、桁合わせが必要。

参考：[Pythonで桁あわせをするには…]
(http://nonbiri-tereka.hatenablog.com/entry/2014/08/08/121208)

(3)UNIX_TIMESTAMPの変換
そのままだと'1402018713'みたいな表示になるので、DateTime型(YYYY-MM-DD HH:MM:SS)に変換して表示。

参考：[誰もが一度は陥る日付処理。各種プログラミング言語におけるDateTime型/TimeStamp型の変換方法のまとめ
](http://yut.hatenablog.com/entry/20130617/1371425713)

(4)浮動小数点の変換
float型をそのままstrすると、浮動小数点形式で表示されることがあった(下記画像の選択範囲)。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/228630/46949504-8204-846c-6b67-a76b207701cd.png)

上記事象を解決するために、一度decimalに変換してからstrした。

```python3:
#修正前：floatのままstr
"{0:>10s}".format(str(trade_list[trade_key]['price'])[0:10]),    unit[1].ljust(4), "|", #価格      (float)←floatを直接strに変換
#注文id 79392363の価格出力結果：2.5e-05

#修正後：floatをdecimalに変換した後str
"{0:>10s}".format(str(Decimal(trade_list[trade_key]['price']))[0:10]),    unit[1].ljust(4), "|", #価格      (float)←dec経由でstrに変換
#注文id 79392363の価格出力結果：0.00002500
```

参考：[Python で正確な小数の計算 (1) - Decimal モジュールを使う - すぐに忘れる脳みそのためのメモ]
(http://jutememo.blogspot.jp/2008/08/python_27.html)

④未解決の問題
・trade_history関数の戻り値：bonusの型がNoneType
他の数値系項目はfloatなのに…
最初floatだと思って他の戻り値と同様decimal変換しようとしたら、エラーが出たため、type関数で型を確認したところ、NoneTypeであった。

ドキュメントにもfloatと記載してあるのだが…私が何か勘違いしている？
参考：[Zaif api document v1.1.1 ドキュメント » 現物取引API » 個別情報 »  trade_history]
(http://techbureau-api-document.readthedocs.io/ja/latest/trade/2_individual/5_trade_history.html)


## ３．テキスト形式で出力
とりあえずprintでそれっぽく出力できたので、次にテキスト形式で出力することにした(エクセルは難しそうだと思ったので(>_<))。

・主な変更点
　print関数にかわって、writelines関数を使用。

①コード(zaif_trade_print.pyとの差分のみ)

```python3:zaif_trade_text.py

#zaif_trade_print.pyと同様のため省略#

# ファイルオープン
f = open('trade_list.txt', 'w')

#ヘッダー書き込み
f.write("トレード履歴一覧(最大1000件)" + "\n")
f.write("no  |order_id|pair    | act|amount        |price         |fee   |fee_amount  |yact|bonus|timestamp          |comment" + "\n")
f.write("項番|注文id  |通貨ペア| b/a|数量     :単位|価格     :単位|手数料|手数料 :単位|b/a |ボナ |取引日時           |コメント" + "\n")
f.write("------------------------------------------------------------------------------------------------------------------------" + "\n")
#トレード数分繰り返し(MAX1000)
for i, trade_key in enumerate(trade_list):
    #単位付与
    unit = trade_list[trade_key]['currency_pair'].split('_')
    #'bonus'がNoneだった場合0に置換
    trade_list[trade_key]['bonus'] = str(trade_list[trade_key]['bonus']).replace('None', '0')
    #項番(インデックス+ 1)・キー(注文id)・キーに紐付くValue(注文の中身)
    list = ("{0:>04d}".format(i + 1),                                                                   "|", #項番      (int)
            "{0:>8s}" .format(trade_key),                                                               "|", #注文id    (str)
            "{0:>8s}" .format(trade_list[trade_key]['currency_pair']),                                  "|", #通貨ペア  (str)
            "{0:>4s}" .format(trade_list[trade_key]['action']),                                         "|", #bid/ask   (str)
            "{0:>10s}".format(str(Decimal(trade_list[trade_key]['amount']))[0:10]),   unit[0].ljust(4), "|", #数量      (float)←dec経由でstrに変換
            "{0:>10s}".format(str(Decimal(trade_list[trade_key]['price']))[0:10]),    unit[1].ljust(4), "|", #価格      (float)←dec経由でstrに変換
            "{0:>6s}" .format(str(Decimal(trade_list[trade_key]['fee']))[0:6]),                         "|", #手数料    (float)←dec経由でstrに変換
            "{0:>8s}" .format(str(Decimal(trade_list[trade_key]['fee_amount']))[0:8]),unit[0].ljust(4), "|", #手数料分  (float)←dec経由でstrに変換
            "{0:>4s}" .format(trade_list[trade_key]['your_action']),                                    "|", #bid/ask   (str)←自己取引時はboth
            "{0:>5s}" .format(str(trade_list[trade_key]['bonus'])),                                     "|", #-手数料分 (NoneType)←NoneTypeってあり？
            str(datetime.fromtimestamp(int(trade_list[trade_key]['timestamp']))),                       "|", #取引日時  (UNIX_TIMESTAMP)←datetimeに変換
            trade_list[trade_key]['comment'] + "\n")                                                         #コメント

    # ファイル書き込み
    f.writelines(list)


# ファイルクローズ
f.close()

print("トレード履歴(trade_list.txt)を出力しました")
```

②出力結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/228630/827140eb-7496-fc38-545a-e5fd2afc7eae.png)

③参考リンク
参考：[Python: テキストファイルに書き込み – write()、writelines()メソッド]
(http://www.yukun.info/blog/2008/09/python-file-write-writelines.html)

## ４．エクセルで出力
いよいよエクセルで出力してみる。
エクセルの操作はエクセルのマクロで使うVBAと似たような感じだったので、思いの他扱いやすかった。

主な変更点
・エクセルを操作するため、openpyxlをimport
・エクセルカラム変換関数num2charを定義
・Decimal関数をasでdecとして定義(見た目の問題)
・trade_history関数の戻り値：'fee'の出力を削除(理由は⑤にて後述)

①コード

```python3:zaif_trade_list.py
# Zaif取引APIでトレード履歴を一覧表示
from zaifapi import ZaifPublicApi
from zaifapi import ZaifTradeApi
from datetime import *
from decimal import Decimal as dec
import openpyxl as px

# Zaif API
KEY = "取引所のAPI_KEY"
SECRET = "取引所のAPI_SECRET"
zaif_public = ZaifPublicApi()
zaif_trade = ZaifTradeApi(KEY,SECRET)

# エクセルカラム変換関数(1⇒A…27⇒AA)
def num2char(num):
    quotient, remainder = divmod(num, 26)
    chars = ''
    if quotient > 0:
        chars = chr(quotient + 64)
    if remainder > 0:
        chars = chars + chr(remainder + 64)
    return chars

#トレード履歴取得
trade_list = zaif_trade.trade_history()

# エクセルファイル作成
wb = px.Workbook()  # 新規ファイルの作成
ws = wb.active      # 現在選択中のシートを取得
ws.title = "list"   # シートをリネーム

# ヘッダー書き込み
header1 = ['項番','注文id','通貨ペア','b/a','数量','単位','価格','単位',
           '手数料','単位','b/a','ボーナス','取引日時','コメント']

for i in range(14):
    cell = num2char(i + 1) + "1"
    ws[cell].value = header1[i]
    i += 1
    
#トレード数分繰り返し(MAX1000)
for j, trade_key in enumerate(trade_list):
    #単位付与
    unit = trade_list[trade_key]['currency_pair'].split('_')
    #'bonus'がNoneだった場合0に置換
    trade_list[trade_key]['bonus'] = str(trade_list[trade_key]['bonus']).replace('None', '0')
    #項番(インデックス+ 1)・キー(注文id)・キーに紐付くValue(注文の中身)
    list = [(j + 1),                                                              #項番     (int)
            int(trade_key),                                                       #注文id   (str)
            trade_list[trade_key]['currency_pair'],                               #通貨ペア (str)
            trade_list[trade_key]['action'],                                      #bid/ask  (str)
            dec(str(dec(trade_list[trade_key]['amount']))[0:10]),   unit[0],      #数量     (float)
            dec(str(dec(trade_list[trade_key]['price']))[0:10]),    unit[1],      #価格     (float)
            dec(str(dec(trade_list[trade_key]['fee_amount']))[0:8]),unit[0],      #手数料   (float)
            trade_list[trade_key]['your_action'],                                 #bid/ask  (str)
            dec(str(trade_list[trade_key]['bonus'])),                             #-手数料  (NoneType)
            str(datetime.fromtimestamp(int(trade_list[trade_key]['timestamp']))), #取引日時 (UNIX_TIMESTAMP)
            trade_list[trade_key]['comment']]
    # トレード履歴書き込み
    for k in range(14):
        cell = num2char(k + 1) + str(j + 2)
        ws[cell].value = list[k]

# ファイル保存
wb.save("trade_list.xlsx")
print("トレード履歴(trade_list.xlsx)を出力しました")
```

②出力結果
G8、G9セルが浮動小数点表示になっているが、G列の幅を広げると固定小数点表示になる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/228630/bc5ad675-8eb1-8bb8-b3e9-e66543962040.png)

③参考リンク

参考1：[[Python] openpyxl で Excel を操作してみた！]
(http://note.crohaco.net/2017/python-openpyxl-excel/)
参考2：[openpyxlでExcel操作！最低限必要そうなのまとめてみた - PythonでExcelを操作しちゃおう！]
(http://www.lisz-works.com/entry/openpyxl-basic#comment-8599973812334343469)
参考3：[Excelの列番号を数字に変換]
(https://siguniang.wordpress.com/2010/07/22/excel%E3%81%AE%E5%88%97%E7%95%AA%E5%8F%B7%E3%82%92%E6%95%B0%E5%AD%97%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%97%E3%81%9F%E3%81%84/) ⇐エクセルカラム変換関数num2charを使用させて頂きましたm(_ _)m
参考4：[Zaif APIを使って取引履歴を取得する]
(https://qiita.com/yh0sh/items/835ab767d00b6d2f0dbe) ⇐python2で、csvでの出力。

④未解決の問題
(1)出力件数
trade_history関数のパラメータ：count(取得するレコード数)はデフォルトの1000件。
1000件以上の場合は、countに値を指定する等の工夫が必要。

(2)通貨ペア
trade_history関数のパラメータ：currency_pair(通貨ペア)は指定していない。
指定なしの場合、取得できるのは以下のみ。
btc_jpy/mona_jpy/mona_btc/xem_jpy/xem_btc
よって、eth_jpy等、上記以外の通貨ペアの履歴を取得したい場合、currency_pairに値を指定する必要がある。
一応なんとか全通貨ペア対応版を作ってみたが、処理時間に問題あり(５．全通貨ペア対応版 参照)。

⑤trade_history関数の戻り値：'fee'の出力を削った理由
zaif_trade_list.pyにてcurrency_pairに'eth_btc'を指定し、eth_btcの履歴出力を試したところ、戻り値：'fee'が取得できずエラーとなった。
trade_historyはETH追加に伴い2017年10月12日に仕様変更されたようだが、'fee'は使わなくなったのだろうか？
実際私の取引履歴では'fee'は全て0で、'fee_amount'に手数料相当の値が入っていたので、未使用になったものと思われるがどうなんだろう…

参考：[Zaif - 暗号通貨取引所 on Twitter: "ETH追加に伴い、trade_historyのドキュメントを更新いたしました。"](https://twitter.com/zaifdotjp/status/918408881545588736)

## ５．全通貨ペア対応版
作ってみた。

主な変更点
・全通貨ペアへの対応
trade_historyでデフォルトで対応している通貨ペア以外の取引も取得できるように、以下の処理を追加。
(1)currency_pairs関数で通貨ペアの一覧を取得
(2)trade_history関数のデフォルト通貨ペア以外の通貨ペアだった場合、取引履歴を再取得

①コード

```python3:zaif_trade_multi.py
#zaif_trade_list.pyと同じため省略

#デフォルト通貨一覧
def_list = ['btc_jpy', 'mona_jpy', 'mona_btc', 'xem_jpy', 'xem_btc']

#通貨一覧取得
currency_list = zaif_public.currency_pairs('all')

#トレード履歴取得
trade_list = zaif_trade.trade_history()
for c in range(len(currency_list)):
    currency = currency_list[c]["currency_pair"]
    if(currency not in def_list):
        time.sleep(3)
        temp_list = zaif_trade.trade_history(currency_pair = currency)
        if len(temp_list) != 0:
            trade_list.update(temp_list)

# エクセルファイル作成
#以下、zaif_trade_list.pyと同じため省略
```

②出力結果
末尾にデフォルト指定以外の通貨ペアの取引が追加された(選択範囲)。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/228630/f0d92d31-262a-5d38-a500-5b0e07ec2813.png)

③未解決の問題
(1)処理時間
sleep入れないとAPI制限に引っかかって下記のエラーになる。
zaifapi.api_error.ZaifApiError: time wait restriction, please try later.

trade_historyのデフォルト通貨以外の通貨ペアの取引履歴を取得するために、同関数を繰り返し呼び、その度に3秒sleepを入れてるので、2～3分程時間がかかる。

参考：[Zaif api document v1.1.1 ドキュメント » Q&A » エラーメッセージ一覧](http://techbureau-api-document.readthedocs.io/ja/latest/faq/2_error_message.html)


# ごあいさつ
Qiita初投稿、且つ、Pythonで初めて作ったプログラムなので色々とお見苦しい点多いかと思います。
申し訳ありません(>_<)

また、一人で作業していたのですが、Google検索すると参考になるサイトがたくさんあり、非常に助かりました。
先輩たちに感謝ですm(_ _)m

普段の職場でのプログラミングでは、周りの上司にすぐ質問でき、また作成後はレビューで指摘も受けられるので、そういった環境のありがたみも感じました。
ただし、期限を守らないといけないので、家でのプログラミングと違ってかなり緊張感ありますが(^^;)


問題点、改善点等ご指摘頂ければ幸いです。



# 2018/1/18追記
取得件数・通貨ペア指定可能版を作成しました。

[Zaif取引APIで取引履歴をエクセル出力(取得件数・通貨ペア指定可)]
(https://qiita.com/natsume_410/items/5505fba7a75a771b0b94)