5年前のPythonのbitFlyer情報取得記事をClojureでupdate

前回の記事で2022時点で仮想通貨botの自動売買の方法を考察しました.

仮想通貨botの自動売買でどう稼ぐか(2022/07) | Futurismo

この記事ではClojureを開発言語にすることで地獄をみないか仮説検証します. いや, 犬の道なことはだいたいわかっているのだけれども, これは未練というか死ぬ前の思い出的なものかもしれません. システムトレードについて今から7年前の2015年にがっつり取り組んでいました(しかも今と同じ7月).

夏休みの自由研究 は OANDA APIを利用して FX システムトレード | Futurismo

このころは本当にプロクラミングが楽しかった. というわけで昔を思い出しながら書いていきます. 過去のブログ記事を漁っていたら5年前の2017年にもシステムトレード的なことをやろうとしてbitFlyerをいじっていた形跡がありました.

pybitflyer をつかって Python で ビットコインレートを取得してみた | Futurismo

さらに, OANDAのAPIもClojureでやろうとしたリポジトリも発見しました.

https://github.com/tsu-nera/oanda-forex-clj

ちゃんとやったことをブログやGitHubに残すことは大事ですね!

ということで今回は5年前のリスペクトとしてClojureでbitFlyerのAPI経由でtickerを取得してみようと思います.

Java XChangeをつかう

Pythonだとccxtという素晴らしいbitFlyer wrapperがあるものの, Clojureだと当然ないわけで. しかしClojureはJavaのライブラリを呼び出せるのでccxt的なJavaライブラリを探すとXChangeというライブラリがあった. bitFlyerにも対応しているようなのでこれを呼び出す方向で進める.

{:paths ["src"]

 :deps
 {org.clojure/clojure                {:mvn/version "1.11.1"}
  org.knowm.xchange/xchange-core     {:mvn/version "5.0.13"}
  org.knowm.xchange/xchange-bitflyer {:mvn/version "5.0.13"}}}
(ns bitflyer-clj.core
  (:import
    (org.knowm.xchange
      ExchangeFactory)
    (org.knowm.xchange.bitflyer
      BitflyerExchange)))

(defn create-exchange
  [instance]
  (let [ex      instance
        ex-spec (.getDefaultExchangeSpecification ex)]
    (.createExchange (ExchangeFactory/INSTANCE) ex-spec)))

(def exchange (create-exchange (BitflyerExchange.)))
(def service (.getMarketDataService exchange))

(dotimes [i 5]
  (println (.toString (.getTicker service "BTC_JPY")))
  (Thread/sleep 1000))

実行結果はこんな感じ.

BitflyerTicker{productCode='BTC_JPY', timestamp='2022-07-18T13:55:35.843', tickId=3022718, bestBid=3063084.0, bestAsk=3064399.0, bestBidSize=0.01, bestAskSize=0.01, totalBidDepth=1010.43034989, totalAskDepth=290.79611167, ltp=3064031.0, volume=13216.11208803, volumeByProduct=3248.08986873, additionalProperties={}}
BitflyerTicker{productCode='BTC_JPY', timestamp='2022-07-18T13:55:37.273', tickId=3022727, bestBid=3063084.0, bestAsk=3064399.0, bestBidSize=0.01, bestAskSize=0.01, totalBidDepth=1010.40534989, totalAskDepth=290.75711167, ltp=3064031.0, volume=13216.04208803, volumeByProduct=3248.08986873, additionalProperties={}}
BitflyerTicker{productCode='BTC_JPY', timestamp='2022-07-18T13:55:38.153', tickId=3022739, bestBid=3063085.0, bestAsk=3064399.0, bestBidSize=0.025, bestAskSize=0.01, totalBidDepth=1010.5293768, totalAskDepth=290.29811332, ltp=3064031.0, volume=13216.03208803, volumeByProduct=3248.08986873, additionalProperties={}}
BitflyerTicker{productCode='BTC_JPY', timestamp='2022-07-18T13:55:39.057', tickId=3022750, bestBid=3063085.0, bestAsk=3064399.0, bestBidSize=0.025, bestAskSize=0.01, totalBidDepth=1010.55370123, totalAskDepth=290.37211167, ltp=3064031.0, volume=13216.04208803, volumeByProduct=3248.08986873, additionalProperties={}}

一応ライブラリ経由で情報取得はできた. しかし気になる点は取得したデータはオブジェクトに格納してアクセスしやすいようにしているがClojureの視点ではオブジェクトはいらなくて単なるマップデータがあればよく, どうせオブジェクトをマップに変換するので冗長な処理になっている.

そのためライブラリを使わないほうがいいんじゃないかと思い始めた.

clj-httpによるbitflyer api直叩き取得

Clojureでhttp requestを叩こうとするとclj-httpかhttpkitで, websocketに対応しているのはhttpkitだけど, 慣れているという点でclj-httpをつかってみた.

(ns bitflyer-clj.core
  (:require
   [camel-snake-kebab.core :as csk]
   [camel-snake-kebab.extras :as cske]
   [clj-http.client :as client]))

(def end-point "https://api.bitflyer.com/v1/")

(defn get-ticker
  []
  (when-let [resp (client/get (str end-point "ticker")
                              {:as            :json
                               :cookie-policy :standard})]
    (->> resp
         :body
         (cske/transform-keys csk/->kebab-case))))


(def ticker (get-ticker))
(println (get-ticker))

(dotimes [_ 3]
  (println (get-ticker))
  (Thread/sleep 1000))

実行結果.

{:ltp 3004118.0, :tick-id 3467616, :best-ask-size 0.025, :total-ask-depth 292.32526458, :market-ask-size 0.0, :best-bid 3004118.0, :state RUNNING, :market-bid-size 0.0, :best-bid-size 6.8786E-4, :volume 16091.06810877, :product-code BTC_JPY, :total-bid-depth 954.8232615, :timestamp 2022-07-18T22:15:42.313, :best-ask 3006354.0, :volume-by-product 4144.45384629}
{:ltp 3004118.0, :tick-id 3467643, :best-ask-size 0.025, :total-ask-depth 292.39336458, :market-ask-size 0.0, :best-bid 3004142.0, :state RUNNING, :market-bid-size 0.0, :best-bid-size 0.054, :volume 16091.07810877, :product-code BTC_JPY, :total-bid-depth 954.8013751, :timestamp 2022-07-18T22:15:44.433, :best-ask 3006354.0, :volume-by-product 4144.45384629}
{:ltp 3004118.0, :tick-id 3467654, :best-ask-size 0.01, :total-ask-depth 291.70336458, :market-ask-size 0.0, :best-bid 3004335.0, :state RUNNING, :market-bid-size 0.0, :best-bid-size 0.03, :volume 16091.08810877, :product-code BTC_JPY, :total-bid-depth 955.6663751, :timestamp 2022-07-18T22:15:45.4, :best-ask 3005837.0, :volume-by-product 4144.45384629}

おわりに

とりあえず方針としてはxchangeを使わないで自分で素でapi叩くことにする. 巨人の肩に乗れない懸念はあるものの, そんなにapi作成は大変でないので. 正確にいえば7年前よりもより気軽にapiが叩けるようになったので成長した.

https://github.com/tsu-nera/bitflyer-clj-poc/tree/fetch-tikcer