はじめに
最近、いわゆるFintech系の方がTwitterでフォロワーになって下さる事が多かったのです。
ツイート見たりしますと、Ripple関係の話が結構出て来ています。みずほ銀行とか大きな話が多いので、デジタル土方(にもなれてないか最近)な私には縁がないかなーとか思ってました。
が、Developperサイト見てみますと、最近は当然なのかもしれませんがオープンソースプロダクトで、かつAPI系とかもタダで落とせたりしました。どうもNode.jsで提供されているAPIを使うと、実稼働しているRippleネットワークにアクセスが出来るようです。
私は最近、どの言語もダメダメになってしまった悲しいプログラマですが、その中ではPythonの利用頻度がそこそこなのです。またPythonだと機械学習系とか計算系も充実してますので、AIからRipple使って自動コントラクトとかなかなか楽しそうな事も出来そうだ思いました。今回、PythonからJavaScriptを呼び出してRipple APIにアクセスするという、土台な部分は出来ましたので、備忘の意味も込めて記事化します。
で、何するの
今回試作したものは、機能としてはあまり意味がありません申し訳ございません。こんな感じです。
- メインはPython
- RIppleネットワークのtotalDropsというパラメータ(恐らくXRPの動きの1つ?)を一定時間、定期的に取得します。
- そのデータをcsvデータにまとめます。
- まとめたcsvデータはRで使えるようにしてみます。
- パラメータを取る所だけ、Node.jsを利用したJavaScriptを使います。
今回の成果物をベースにすると、RippleAPI呼び出しを増産してTensorFlowと繋げたりとか、いろいろ出来るようになるかと思います。
Rippleとは
ブロックチェーンを応用した決済プロトコルです。まぁ流行り系の技術になるかなーとか思います。本家はここです。
Rippleの説明に関しては、GiantGox様の以下サイトがとてもよくまとまっています。
http://gtgox.com/
↑の「Rippleとは」ぐらいを見ていただければ概要は理解できるかなと思います。
Ripple APIとは
デベロッパーサイトがここにあります。以下、そこからの引用。
Rippleの分散型決済ネットワークは、誰もが利用できるオープンソースの技術のもとに構築されています。デベロッパーがオープンソース・プラットフォームでソリューションを作るために利用可能なツールはこちらにあります。
Ripple APIとは上記でいう「利用可能なツール」の1つなようで、JavaScriptでRippleサーバにアクセスし、お金の移動とかが出来るみたいです。
RippleAPIはnode_js上で動作し、かつECMAScript 6(ES2015) を利用します。つまりはbabel-nodeを使って実行することでシステムにアクセスがで出来るって事みたいです。
APIの仕様などは以下のサイトで。
https://ripple.com/build/rippleapi/
スクリプトはこんな感じで書くんだよーというのはここ。
https://ripple.com/build/rippleapi/#boilerplate
今回は↑のご沙汰に従い、getLedgerというAPIを使って、totalDropsというデータを取得します。APIの説明はここ。
https://ripple.com/build/rippleapi/#getledger
実行環境の構築
Rippleにはビギナーズガイドがありますので、そこを見ながらやると良いです。ここではいくつか補足を。
APIの説明でも書きましたが、node_jsが実行できる環境が必要です。node_jsのセットアップに関しては情報が豊富にあるかと思いますので、省略します。RippleAPIを使う際、執筆時(2016年8月)では 0.12、4.x以上が必要ですので、そういう環境を構築してください。
私はMacOSXのnode_js 0.12.4 上に構築しました。
node_jsを動くようにすれば、以下の手順でbabelを入れれば準備完了となるみたいです(これはビギナーズガイドの受け売り)
- 適当なフォルダを作成して移動。
- 必要ならgit init(私はやりませんでした)。
- そこでpackage.jsonを書きます。内容はビギナーズガイドにありますので、コピペすれば良いです。
-
npm install
とすると、babelなどが入ってきます。 - ガイドにもありますように、npm install時にWarningが出ますがまぁそこは「はいはい」って感じで良いみたいです。
これで後はJavaScriptのプログラム書いて、以下のように実行すれば動かすことが出来るようになります。
./node_modules/.bin/babel-node スクリプト名
JavaScriptでRippleAPIを呼び出す
今回はAPIを1個呼び出すだけですので、RIppleのサンプルほぼそのままです。なので説明するよりもコードを見た方が理解が早いかと思います。
"use strict";
const RippleAPI = require("ripple-lib").RippleAPI;
const api = new RippleAPI({
server: "ここにRippleAPIのURLを入れて下さい" // Public rippled server
});
api.connect().then(() => {
// Ripple Networkに接続した後の処理をここに書きます。
// 具体的にはAPIを呼び出します。
return api.getLedger()
}).then(info => {
// APIの処理が完了すると、ここに来ます。
// info(名前はご自由らしい)に戻り値があります。
// 今回は、totalDropsを取り出して出力します。
// →これはAPIドキュメントの「Return Value」で定義されてます。
// consoleに出した文字列をPythonに渡すという感じです。
console.log(info.totalDrops);
}).then(() => {
// で、やること終わったので、Rippleから切断です。
return api.disconnect();
}).then(() => {
}).catch(console.error);
要はAPI呼び出して、結果をconsosole.logで出してPythonに通知するだけです。
serverの所にはRippleAPIのURLを入れてください。
以下のサイトでURLがあります。
https://ripple.com/build/rippled-apis/#websocket-api
もしくは、以下にあるサンプルにURLが書かれていますので、そこからのコピペでも良いかと思います。
https://ripple.com/build/rippleapi/#boilerplate
本記事では、上記のJavaScriptを「get_totalDrops.js」ファイルで扱う前提で話を進めさせて戴きます。それを単独で実行するとこんな感じで、totalDropsの数字が出てきます(出るまで数秒~10秒ぐらいかかります)
$ ./node_modules/.bin//babel-node get_totalDrops.js
99997222654452897
PythonからJavaScriptを呼び出す。
と書きましたが、今回は極めて手抜きな方法です。
- pythonファイルと、JavaScriptファイルを同じフォルダに置く
- node_js&rippleAPI と python がどちらも実行できる環境にする。
- pythonのcommands.getoutputを使って実行するだけ
- コンソールの文字列が戻り値で戻される。
今回取得するデータは数字なので、こんな感じで関数化します。
import commands
RIPPLE_API_COMMAND = "./node_modules/.bin//babel-node get_totalDrops.js"
def get_totalDrops():
return long(commands.getoutput(RIPPLE_API_COMMAND))
RIppleAPIを呼び出すget_totalDrops.jsをcommands.getoutputで実行します。その結果は文字列で来ます。今回は数値のみを出力させていますので、それをlongに変換して戻り値としています。
今回は戻り値1つでしたが、複数の結果を戻すときはcsvとかにすればこれの応用でやれます。バルクデータの場合はまた別のロジックが必要になると思います。
Python側の仕様
今回は
- RippleAPIでデータを収集
- データを加工
- 加工したデータの可視化
を一通りやってみようと思います。そのため、以下のような要件でやってみます。
- 指定された時間毎に、指定された回数、RippleAPI・getLedgerを呼び出します。
- 戻り値(totalDrops)から前回との差分を算出します。
- totalDropsと差分を蓄積します。
- 指定された回数呼び出したら、蓄積した結果などを基に2つのCSVファイルを生成します。
今回はRで結果を出してみますので、以下2つのCSVファイルを出してみます。
解析パラメータ用のCSV
ここには開始時刻と、各データの間隔(単位:秒)とを入れます。以下は例。
time, by
2016/8/20 7:59, 300
1行目はタイトルで、2行目に 開始時刻 , 間隔 と設定します。
データのCSV
ここには実際の結果(totalDropsと差分)を入れます。以下は例。
total, diff
99997224489730292,0
99997224488731486,998806
99997224487789148,942338
99997224487350204,438944
99997224486780979,569225
同じく1行目はタイトルで、2行目から totalDrops , 差分 という感じで次々入れておきます。上記の例は5回行った例です。
Python側のコード
これもそう処理が複雑な訳ではないので、いきなりコードを掲示させて戴きます。
#!/usr/bin/python
# coding: UTF-8
import commands
import time
import datetime
# --------- 以下は適当に変えてみてもらってよいです。
#何秒ごとにRippleAPIを呼び出すか
RIPPLE_WAIT_SECS = 300
#何回RIppleAPIを呼び出すか。
RIPPLE_NUM_OF_GET_TOTALDROPS = 10
# ---------
# パラメータのCSVファイル名
RIPPLE_PARA_CSV_FILENAME = "ripple_para.csv"
# totalDropsデータ、差分の入ったcsvファイル名
RIPPLE_DROP_CSV_FILENAME = "ripple_drops.csv"
# 今回作ったget_totalDrops.jsの実行コマンド操作
RIPPLE_API_COMMAND = "./node_modules/.bin//babel-node get_totalDrops.js"
# 改行コード定義
RIPPLE_CR = "\r\n"
# RippleAPIのgetLedgerを呼び出します。数値で戻ります。
def get_totalDrops():
return long(commands.getoutput(RIPPLE_API_COMMAND))
# 面倒なのでw、クラス化しました。
class RippleTotalDros:
def __init__(self):
self.total = long(0)
self.diff = long(-1)
self.start_time = ""
self.drop_csv = ""
self.para_csv = ""
# 現在時刻ベースに文字列生成
# need_seconed = True 例: 2016/8/15 12:27:5
# need_seconed = False 例: 2016/8/15 12:27
def get_date_time(self, need_second):
d = datetime.datetime.today()
text = str(d.year) + "/" + str(d.month) + "/" + str(d.day)
text += " " + str(d.hour) + ":" + str(d.minute)
if (need_second == True):
text += ":" + str(d.second)
return text
# getLedgerの戻り値(totalDrops)から差分を算出
# メンバ変数にそれぞれ保存
def calc_drop_data(self, result):
if self.diff >= 0:
self.diff = self.total - result
else:
self.diff = 0
self.total = result
# パラメータ設定用CSVをメンバ変数 para_csvに作成
def make_para_csv(self):
self.start_time = self.get_date_time(False)
self.para_csv = "time, by" + RIPPLE_CR
self.para_csv += self.start_time + "," + str(RIPPLE_WAIT_SECS) + RIPPLE_CR
# RippleAPIを呼び出して、データ収集する
# APIを呼び出して、その結果を基にデータのCSVデータを作成します。
# この処理は指定された時間×指定した回数かかるので、時間かかります。
def collect_totalDrops(self):
self.drop_csv = "total, diff" + RIPPLE_CR
print "start to correct " + self.start_time
for i in xrange(RIPPLE_NUM_OF_GET_TOTALDROPS):
result = get_totalDrops()
self.calc_drop_data(result)
date_time = self.get_date_time(True)
print i , " :" + date_time + " total=" , self.total , " diff=" , self.diff
self.drop_csv += str(self.total) + "," + str(self.diff) + RIPPLE_CR
if i < RIPPLE_NUM_OF_GET_TOTALDROPS - 1:
time.sleep(RIPPLE_WAIT_SECS) # 最後だったら待たずに抜けます。
# 作成されたCSVファイルをファイルにします。
def write_csv(self):
f = open(RIPPLE_PARA_CSV_FILENAME, "w")
f.write(self.para_csv)
f.close()
f = open(RIPPLE_DROP_CSV_FILENAME, "w")
f.write(self.drop_csv)
f.close()
# --- メイン
print "Ripple Collet totalDrops."
ripple = RippleTotalDros()
ripple.make_para_csv() # パラメータCSVデータ作成
ripple.collect_totalDrops() # データ収集&CSVデータ作成
ripple.write_csv() # CSVをファイルに書き込み
print "Complete. csv -> " + RIPPLE_DROP_CSV_FILENAME
スキル低い人が書いたので、是非はともかくw、コメントなども参考にしつつ見てもらえれば、内容は分かるかと思います。この例では5分おきに10回ほど実行します。
ちょっと前にも書きましたが、get_totalDrops.jsを同じフォルダにおき、node_js&rippleAPI と python のどちらも実行できる環境がある事が前提になりますので、注意して下さい。このPythonを実行すると、以下のようになります。
$ python ./get_totalDrops.py
Ripple Collet totalDrops.
start to correct 2016/8/21 13:7
0 :2016/8/21 13:8:1 total= 99997222541580190 diff= 0
1 :2016/8/21 13:13:33 total= 99997222534612330 diff= 6967860
2 :2016/8/21 13:19:6 total= 99997222528462961 diff= 6149369
3 :2016/8/21 13:24:36 total= 99997222523885100 diff= 4577861
4 :2016/8/21 13:30:4 total= 99997222514133479 diff= 9751621
5 :2016/8/21 13:35:36 total= 99997222505328424 diff= 8805055
6 :2016/8/21 13:41:7 total= 99997222500019477 diff= 5308947
7 :2016/8/21 13:46:37 total= 99997222496816135 diff= 3203342
8 :2016/8/21 13:52:5 total= 99997222493532687 diff= 3283448
9 :2016/8/21 13:57:35 total= 99997222488190979 diff= 5341708
Complete. csv -> ripple_drops.csv
更に、ripple_para.csvとripple_drops.csvという2つのCSVファイルが出来ます。
Rで結果を確認
私はPythonもほぼ素人ですが、Rはもっと素人です(何この日本語w)。今回は有意な解析というより、可視化できるかの確認という意味合いが強いです。コードを作成するにあたり、以下、奥村様のサイトを参考にさせていただきました。
plotするためのデータ作成
plotの元データはこうやって作ってみました。
ripple.para = read.csv("ripple_para.csv")
ripple.by_csv = as.numeric(ripple.para[1,2])
ripple.start_time = as.character(ripple.para[1,1])
ripple.drops = read.csv("ripple_drops.csv")
ripple.matrix <- as.matrix(ripple.drops)
base_time = as.POSIXct(ripple.start_time)
time_scale = seq(base_time, by=ripple.by_csv, along.with=ripple.matrix[,2])
要はcsvを読み込んで、そのデータをマトリックス化。時間を横軸にするので、その調整をした感じです。
totalDropsの可視化
totalDropsのplotはこんな感じ。totalDrpos, diff なのでy軸は[,1]となります。
plot(time_scale, ripple.matrix[,1], type="o", pch=16, xlab="time(sec)", ylab="total drops")
だんだん数値が落ちています。
差分の可視化
差分のplotはこんな感じ。totalDrpos, diff なのでy軸は[,2]となります。
plot(time_scale, ripple.matrix[,2], type="o", pch=16, xlab="time(sec)", ylab="drops")
ライセンス
以下使わせて戴きました。素晴らしいソフトウェアを提供して下さり、ありがとうございます。
- (一応書きます…)上記のコードはパブリックドメインとします。当然ですが使用した際の損害は誰も請け負ってくれません。そこだけ注意で。
- Python自体はPSF (Python Software Foundation)ライセンスです。
- ↑の情報はWikipediaのPythonがソースです。
- Node.jsのライセンスはここ。WikiPediaによるとMITライセンスな模様。
- RippleAPIのライセンスですが、おそらくこれではないかと思います。 えっASIS?
- Rのライセンスはここ。原則はGPL系と理解しています。
以上です。