算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

KotlinでWebSocketクライアントを作ってモナーコインの取引を見守る

国内発 仮想通貨のモナーコインが熱いです。 その価格は、年初から600倍以上にもなったとか! 私は乗り遅れまいと先月末あたりに買ってみました。 値動きが激しく持っているだけで面白いです。 なにより、日本の仮想通貨という誇りとモナーのかわいさが最高ですね。

で、値動きが激しいのでチャートが気になってしかたないわけです。 しかし(あることはあるんですが)よさげなチャートがないのが残念です。 そこで、自分で作ってしまおう!というのが今回のモチベーションです。 第一歩として取引情報を取得するところです。

Zaifは、仮想通貨の数多くある取引所のひとつです。 ここでは便利なAPIが提供されています。 ドキュメントを眺めてみると、 現物公開APIのストリーミングAPIが提供されているようなので、これを今回は使います。 公開APIなのでAPIキーなどが不要で、すぐに使い始めることができます。

WebScoketで下記URLに接続すれば、取引情報が流れてきます。

wss://ws.zaif.jp/stream?currency_pair=mona_jpy

すぐにこのAPIを試したいならwscatを使うとよいでしょう。 jqを併用すれば加工したり整形したりすることもできます。

$ wscat -c wss://ws.zaif.jp/stream?currency_pair=mona_jpy | jq '.trades[] | { price: .price, date: (.date | .+32400) | todate }'
{
  "price": 1900,
  "date": "2017-12-08T19:23:20Z"
}
{
  "price": 1899.6,
  "date": "2017-12-08T19:23:17Z"
}
...

さて、本記事タイトルのとおり、このAPIをKotlinプログラムから呼び出します。 WebSocketクライアントとして、tyrusを使います。 JSR 356のリファレンス実装のようです。

github.com

今回はこんな感じで依存性を追加しました。

def tyrusVersion = '1.13.1'
compile "org.glassfish.tyrus.bundles:tyrus-standalone-client-jdk:$tyrusVersion"
compile "org.glassfish.tyrus:tyrus-container-grizzly-client:$tyrusVersion"

最小限のコードはこんな感じになります(インポートは省略)。

fun main(args: Array<String>) {
    val config = ClientEndpointConfig.Builder.create().build()
    val client = ClientManager.createClient()
    val session = client.connectToServer(object : Endpoint() {
        override fun onOpen(session: Session, config: EndpointConfig) {
            session.addMessageHandler(MessageHandler.Whole<String> { message ->
                println(message)
            })
        }
    }, config, URI.create("wss://ws.zaif.jp/stream?currency_pair=mona_jpy"))

    while (session.isOpen) {
        Thread.sleep(1000)
    }
}

自信ないけど最後のループは、じっと待つためです。 サーバからのデータを非同期で受信するため、これがないと最初のデータを受け取る前にプログラムが終了してしまうからです。 正しいやり方があれば、コメントで教えてくださいm( )m

まぁ、これだけであとはJacksonとか使っていい感じにやればOKです。 で終わりだと面白くないので、せっかくだからKotlinのコルーチン機能を使って書き直してみます。

// v サスペンド関数                                                                      v チャネルを返す
suspend fun ClientManager.connectingChannel(config: ClientEndpointConfig, uri: URI): Channel<String> {
    val channel = Channel<String>() // チャネルの生成
    try {
        connectToServer(object : Endpoint() {
            override fun onOpen(session: Session, config: EndpointConfig) {
                session.addMessageHandler(MessageHandler.Whole<String> { message ->
                    // コールバックで受け取ったメッセージをチャネルに送信する
                    channel.sendBlocking(message)
                })
            }
        }, config, uri)
    } catch (e: Throwable) {
        channel.close(e)
    }
    return channel
}

この拡張関数connectingChannelが返すチャネルを使って、メッセージの到着を監視することができます。

fun main(args: Array<String>) = runBlocking {
    val config = ClientEndpointConfig.Builder.create().build()
    val client = ClientManager.createClient()
    val channel = client.connectingChannel(config, URI.create("wss://ws.zaif.jp/stream?currency_pair=mona_jpy"))

    while (isActive) {
        val message = channel.receive()
        println(message)
    }
}

いい感じに整えて動かすとこんな感じです。

f:id:ngsw_taro:20171208193100g:plain

ちょっと地味すぎでしたね。。 まぁこれでデータを取れるので、あとはかっこよく見せるだけです。 おわり。