6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

coincheckの約定履歴をグラフにしてWebから画像で取得するサーバーを書いてみた

Last updated at Posted at 2015-12-17

長いですがタイトルそのままの内容ですw

coincheckから約定履歴を取得する

coincheck は Bitcoin の取引所で、ありがたいことに多機能なAPIを公開されています。今回はその中でも認証なしで使える約定履歴のAPIにアクセスして直近の取引額を取得してみます。APIクライアントにはServantを使います。

data Trade = Trade
  { amount     :: String
  , rate       :: Int
  , order_type :: String
  , created_at :: String
  } deriving Generic

instance FromJSON Trade

Tradeは約定履歴のAPIで返却されるデータ構造です。これを使ってAPIを定義します。

type CoincheckAPI = "api" :> "trades" :> Get '[JSON] [Trade]

coincheckAPI :: Proxy CoincheckAPI
coincheckAPI = Proxy

今回は約定履歴しか使わないのでこれで十分です。他のAPIを使いたくなっても:<|>で追加していけばいいだけです。APIを定義したのでクライアントを作りましょう。

getTrades :: EitherT ServantError IO [Trade]
getTrades = client coincheckAPI $ BaseUrl Https "coincheck.jp" 443

冗談のように短いですがこれで終わりです。

Webサーバーで画像を返却する

servant-JuicyPixelsを使います。

type API = "trades.png" :> Get '[PNG] DynamicImage

api :: Proxy API
api = Proxy

これで GET /trades.png にアクセスすればpng画像が返ってくるはずです。

グラフ画像を生成する

いよいよ一番実装しなければいけないところです。グラフの描画にはChartを使います。

server :: Server API
server = do
  trades <- bimapEitherT (const err500) id getTrades
  image <- io $ do
    let imagePath = "trades.png"
    toFile def imagePath $ plot (line "price" $ [[(parseCreatedAt t, rate t) | t <- trades]])
    readImage imagePath
  either (const $ left err500) pure image
    where
      parseCreatedAt :: Trade -> LocalTime
      parseCreatedAt trade = parseTimeOrError True defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S.000Z")) (created_at trade)

ポイントは Servant Client から Servant Server への変換だと思います。型的には EitherT ServantError IO a -> EitherT ServantErr IO aという変換になっていて、ここではbimapEitherT :: Functor m => (e -> f) -> (a -> b) -> EitherT e m a -> EitherT f m bを利用して変換しています。あとはtoFileでグラフを画像ファイルに書き出してreadImageで書きだしたファイルを読み込んでDynamicImageにしています。実はここはファイルにせずに直接DynamicImageに変換できればよかったのですがChartを使ってどのようにすればわからなかったので知っている人がいればコメント等でアドバイスいただければ幸いです。

あとは実行あるのみです。

main :: IO ()
main = do
  putStrLn "Listening on 8080"
  Warp.run 8080 $ serve api server

これで http://localhost:8080/trades.png にアクセスすると

このようにグラフ画像が表示されるはずです :exclamation:

最後に cabal ファイルと全てのコードを載せておきます。

coincheck-trades-image-server.cabal
name:                coincheck-trades-image-server
version:             0.1.0.0
synopsis:            Initial project template from stack
license:             BSD3
build-type:          Simple
cabal-version:       >=1.10

executable coincheck-trades-image-server-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , time
                     , transformers
                     , either
                     , aeson
                     , servant-server
                     , servant-client
                     , Chart
                     , Chart-cairo
                     , JuicyPixels
                     , servant-JuicyPixels
                     , warp
  default-language:    Haskell2010
app/Main.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}

module Main where

import Codec.Picture
import Control.Monad.Trans.Either
import Control.Monad.IO.Class
import Data.Aeson
import Data.Time
import Graphics.Rendering.Chart.Easy
import Graphics.Rendering.Chart.Backend.Cairo
import GHC.Generics
import qualified Network.Wai.Handler.Warp as Warp
import Servant
import Servant.Client
import Servant.JuicyPixels

data Trade = Trade
  { amount     :: String
  , rate       :: Int
  , order_type :: String
  , created_at :: String
  } deriving Generic

instance FromJSON Trade

type CoincheckAPI = "api" :> "trades" :> Get '[JSON] [Trade]

coincheckAPI :: Proxy CoincheckAPI
coincheckAPI = Proxy

getTrades :: EitherT ServantError IO [Trade]
getTrades = client coincheckAPI $ BaseUrl Https "coincheck.jp" 443

type API = "trades.png" :> Get '[PNG] DynamicImage

api :: Proxy API
api = Proxy

server :: Server API
server = do
  trades <- bimapEitherT (const err500) id getTrades
  image <- liftIO $ do
    let imagePath = "trades.png"
    toFile def imagePath $ plot (line "price" $ [[(parseCreatedAt t, rate t) | t <- trades]])
    readImage imagePath
  either (const $ left err500) pure image
    where
      parseCreatedAt :: Trade -> LocalTime
      parseCreatedAt trade = parseTimeOrError True defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S.000Z")) (created_at trade)

main :: IO ()
main = do
  putStrLn "Listening on 8080"
  Warp.run 8080 $ serve api server
6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?