A Haskell client library for the Binance cryptocurrency exchange API

A Haskell client library for the Binance cryptocurrency exchange API. The Binance API is documented here: Binance API documentation. The OpenAPI (Swagger) here: binance.github.io/binance-api-swagger. The library can be found here: maridonkers/binance-api.

Generated code

This code is auto-generated by Haskell-OpenAPI-Client-Code-Generator available on Hackage.

The generator was run as follows:

1openapi3-code-generator-exe --use-numbered-variant-constructors --convert-to-camel-case --package-name binance-api --module-name Binance --generate-nix-files spot_api.yaml

Where spot_apy.yml is the file containing the downloaded Binance OpenAPI specification (link is at the top of the binance.github.io/binance-api-swagger page).

Manual changes were required

The generated Haskell code needed some minor manual changing to make it compile.

In the generated Haskell source code I have replaced every first aggTradeM (the ones for capital 'M') with aggTradeMM in the src/Binance/Types/AggTrade.hs file, so that it now looks like this:

 1{-# LANGUAGE MultiWayIf #-}
 2-- CHANGE WITH CAUTION: This is a generated code file generated by https://github.com/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator.
 3{-# LANGUAGE OverloadedStrings #-}
 4
 5-- | Contains the types generated from the schema AggTrade
 6module Binance.Types.AggTrade where
 7
 8import qualified Binance.Common
 9
10import Binance.TypeAlias
11import qualified Control.Monad.Fail
12import qualified Data.Aeson
13import qualified Data.Aeson as Data.Aeson.Encoding.Internal
14import qualified Data.Aeson as Data.Aeson.Types
15import qualified Data.Aeson as Data.Aeson.Types.FromJSON
16import qualified Data.Aeson as Data.Aeson.Types.Internal
17import qualified Data.Aeson as Data.Aeson.Types.ToJSON
18import qualified Data.ByteString.Char8
19import qualified Data.ByteString.Char8 as Data.ByteString.Internal
20import qualified Data.Foldable
21import qualified Data.Functor
22import qualified Data.Maybe
23import qualified Data.Scientific
24import qualified Data.Text
25import qualified Data.Text.Internal
26import qualified Data.Time.Calendar as Data.Time.Calendar.Days
27import qualified Data.Time.LocalTime as Data.Time.LocalTime.Internal.ZonedTime
28import qualified GHC.Base
29import qualified GHC.Classes
30import qualified GHC.Int
31import qualified GHC.Show
32import qualified GHC.Types
33import qualified Prelude as GHC.Integer.Type
34import qualified Prelude as GHC.Maybe
35
36-- | Defines the object schema located at @components.schemas.aggTrade@ in the specification.
37data AggTrade = AggTrade
38  { -- | M: Was the trade the best price match?
39    aggTradeMM :: GHC.Types.Bool,
40    -- | T: Timestamp
41    aggTradeT :: GHC.Types.Bool,
42    -- | a: Aggregate tradeId
43    aggTradeA :: GHC.Int.Int64,
44    -- | f: First tradeId
45    aggTradeF :: GHC.Int.Int64,
46    -- | l: Last tradeId
47    aggTradeL :: GHC.Int.Int64,
48    -- | m: Was the buyer the maker?
49    aggTradeM :: GHC.Types.Bool,
50    -- | p: Price
51    aggTradeP :: Data.Text.Internal.Text,
52    -- | q: Quantity
53    aggTradeQ :: Data.Text.Internal.Text
54  }
55  deriving
56    ( GHC.Show.Show,
57      GHC.Classes.Eq
58    )
59
60instance Data.Aeson.Types.ToJSON.ToJSON AggTrade where
61  toJSON obj = Data.Aeson.Types.Internal.object (Data.Foldable.concat (["M" Data.Aeson.Types.ToJSON..= aggTradeMM obj] : ["T" Data.Aeson.Types.ToJSON..= aggTradeT obj] : ["a" Data.Aeson.Types.ToJSON..= aggTradeA obj] : ["f" Data.Aeson.Types.ToJSON..= aggTradeF obj] : ["l" Data.Aeson.Types.ToJSON..= aggTradeL obj] : ["m" Data.Aeson.Types.ToJSON..= aggTradeM obj] : ["p" Data.Aeson.Types.ToJSON..= aggTradeP obj] : ["q" Data.Aeson.Types.ToJSON..= aggTradeQ obj] : GHC.Base.mempty))
62  toEncoding obj = Data.Aeson.Encoding.Internal.pairs (GHC.Base.mconcat (Data.Foldable.concat (["M" Data.Aeson.Types.ToJSON..= aggTradeMM obj] : ["T" Data.Aeson.Types.ToJSON..= aggTradeT obj] : ["a" Data.Aeson.Types.ToJSON..= aggTradeA obj] : ["f" Data.Aeson.Types.ToJSON..= aggTradeF obj] : ["l" Data.Aeson.Types.ToJSON..= aggTradeL obj] : ["m" Data.Aeson.Types.ToJSON..= aggTradeM obj] : ["p" Data.Aeson.Types.ToJSON..= aggTradeP obj] : ["q" Data.Aeson.Types.ToJSON..= aggTradeQ obj] : GHC.Base.mempty)))
63
64instance Data.Aeson.Types.FromJSON.FromJSON AggTrade where
65  parseJSON = Data.Aeson.Types.FromJSON.withObject "AggTrade" (\obj -> (((((((GHC.Base.pure AggTrade GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "M")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "T")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "a")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "f")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "l")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "m")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "p")) GHC.Base.<*> (obj Data.Aeson.Types.FromJSON..: "q"))
66
67-- | Create a new 'AggTrade' with all required fields.
68mkAggTrade ::
69  -- | 'aggTradeMM'
70  GHC.Types.Bool ->
71  -- | 'aggTradeT'
72  GHC.Types.Bool ->
73  -- | 'aggTradeA'
74  GHC.Int.Int64 ->
75  -- | 'aggTradeF'
76  GHC.Int.Int64 ->
77  -- | 'aggTradeL'
78  GHC.Int.Int64 ->
79  -- | 'aggTradeM'
80  GHC.Types.Bool ->
81  -- | 'aggTradeP'
82  Data.Text.Internal.Text ->
83  -- | 'aggTradeQ'
84  Data.Text.Internal.Text ->
85  AggTrade
86mkAggTrade aggTradeMM aggTradeT aggTradeA aggTradeF aggTradeL aggTradeM aggTradeP aggTradeQ =
87  AggTrade
88    { aggTradeMM = aggTradeMM,
89      aggTradeT = aggTradeT,
90      aggTradeA = aggTradeA,
91      aggTradeF = aggTradeF,
92      aggTradeL = aggTradeL,
93      aggTradeM = aggTradeM,
94      aggTradeP = aggTradeP,
95      aggTradeQ = aggTradeQ
96    }

Usage examples

getSapiV1SystemStatus

 1{-# LANGUAGE OverloadedStrings #-}
 2
 3module Example1 where
 4
 5import qualified Binance
 6import Control.Monad (when)
 7import qualified Data.Text as T
 8import qualified Network.HTTP.Simple as HS
 9
10trade :: Bool -> IO ()
11trade verbose = do
12  ss <- getSystemStatus
13  putStrLn $ "Binance API system status: " <> ss
14  where
15    getSystemStatus :: IO String
16    getSystemStatus = do
17      resp <- Binance.runWithConfiguration Binance.defaultConfiguration Binance.getSapiV1SystemStatus
18      when verbose (print resp)
19      pure $
20        case HS.getResponseBody resp of
21          Binance.GetSapiV1SystemStatusResponse200 status ->
22            T.unpack (Binance.getSapiV1SystemStatusResponseBody200Msg status)
23              ++ " ("
24              ++ show (Binance.getSapiV1SystemStatusResponseBody200Status status)
25              ++ ")"
26          _ -> "wrong type of response"

Running this example code results in the following output:

 1Response {responseStatus = Status {statusCode = 200, statusMessage = ""}, responseVersion = HTTP/1.1, responseHeaders = [("Content-Type","application/json;charset=UTF-8"),("Content-Length","27"),("Connection","keep-alive"),("Date","Wed, 16 Nov 2022 20:18:25 GMT"),("Server","nginx"),("X-SAPI-USED-IP-WEIGHT-1M","1"),("Strict-Transport-Security","max-age=31536000; includeSubdomains"),("X-Frame-Options","SAMEORIGIN"),("X-Xss-Protection","1; mode=block"),("X-Content-Type-Options","nosniff"),("Content-Security-Policy","default-src 'self'"),("X-Content-Security-Policy","default-src 'self'"),("X-WebKit-CSP","default-src 'self'"),("Cache-Control","no-cache, no-store, must-revalidate"),("Pragma","no-cache"),("Expires","0"),("Access-Control-Allow-Origin","*"),("Access-Control-Allow-Methods","GET, HEAD, OPTIONS"),("X-Cache","Miss from cloudfront"),("Via","1.1 4ce5e5162c2d4fc9022ceb290f794ffe.cloudfront.net (CloudFront)"),("X-Amz-Cf-Pop","AMS1-C1"),("X-Amz-Cf-Id","V8g__abrKZMYn3os-dRPOuJK3964z28Gk9VrOnh-l6rK4cHK3uRZ8g==")], responseBody = GetSapiV1SystemStatusResponse200 (GetSapiV1SystemStatusResponseBody200 {getSapiV1SystemStatusResponseBody200Msg = "normal", getSapiV1SystemStatusResponseBody200Status = 0}), responseCookieJar = CJ {expose = []}, responseClose' = ResponseClose, responseOriginalRequest = Request {
 2  host                 = "api.binance.com"
 3  port                 = 443
 4  secure               = True
 5  requestHeaders       = [("User-Agent","Binance Public Spot API openapi3-code-generator/0.1.0.7 (https://github.com/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator)")]
 6  path                 = "/sapi/v1/system/status"
 7  queryString          = ""
 8  method               = "GET"
 9  proxy                = Nothing
10  rawBody              = False
11  redirectCount        = 10
12  responseTimeout      = ResponseTimeoutDefault
13  requestVersion       = HTTP/1.1
14  proxySecureMode      = ProxySecureWithConnect
15}
16}
17Binance API system status: normal (0)

getApiV3Klines

 1{-# LANGUAGE OverloadedStrings #-}
 2
 3module Example2 where
 4
 5import qualified Binance
 6import Control.Monad (when)
 7import Data.Either ()
 8import qualified Data.Text as T
 9import qualified Network.HTTP.Simple as HS
10
11defaultConf = Binance.defaultConfiguration
12
13config = Binance.apiKeyInHeaderAuthenticationSecurityScheme "your-api-key-goes-here"
14
15conf =
16  defaultConf
17    { Binance.configSecurityScheme = config
18    }
19
20trade :: Bool -> IO ()
21trade verbose = do
22  kl <- getKlines
23  putStr "Binance API klines "
24  case kl of
25    Left e -> putStrLn $ "*** " <> e
26    Right kls -> do
27      let l = length kls
28      putStrLn $ "(" <> show l <> ")"
29      putStrLn "[ Kline open time, Open price, High price, Low price, Close price, Volume, Kline Close time, Quote asset volume, Number of trades, Taker buy base asset volume, Taker buy quote asset volume, Unused field (ignore) ]"
30      mapM_ (\(i, ks) -> putStrLn $ show i <> ": [ " <> printKline ks <> " ]") $ zip [1 ..] kls
31  where
32    printKline = foldl (\acc c -> acc <> (if null acc then "" else ", ") <> kLineToString c) ""
33      where
34        kLineToString k = case k of
35          Binance.GetApiV3KlinesResponseBody200Variant1 s -> show s
36          Binance.GetApiV3KlinesResponseBody200Variant2 s -> T.unpack s
37
38    getKlines = do
39      resp <-
40        Binance.runWithConfiguration conf $
41          Binance.getApiV3Klines $
42            Binance.mkGetApiV3KlinesParameters Binance.GetApiV3KlinesParametersQueryIntervalEnum1m "BTCBUSD"
43      pure $
44        case HS.getResponseBody resp of
45          Binance.GetApiV3KlinesResponse200 klines -> Right klines
46          _ -> Left "wrong type of response"

Running this example code results in the following output (excerpt):

 1Binance API klines (500)
 2[ Kline open time, Open price, High price, Low price, Close price, Volume, Kline Close time, Quote asset volume, Number of trades, Taker buy base asset volume, Taker buy quote asset volume, Unused field (ignore) ]
 31: [ 1668674700000, 16579.28000000, 16580.95000000, 16572.00000000, 16576.36000000, 55.90257000, 1668674759999, 926698.28532810, 1124, 25.76195000, 427075.54336190, 0 ]
 42: [ 1668674760000, 16576.36000000, 16580.35000000, 16567.78000000, 16570.00000000, 47.20438000, 1668674819999, 782390.60662060, 780, 19.95414000, 330747.43392430, 0 ]
 53: [ 1668674820000, 16570.00000000, 16574.23000000, 16565.37000000, 16568.04000000, 44.22206000, 1668674879999, 732752.61122150, 906, 26.58506000, 440518.72028710, 0 ]
 6...
 7497: [ 1668704460000, 16643.01000000, 16670.00000000, 16639.54000000, 16660.34000000, 260.66434000, 1668704519999, 4341628.84450980, 3393, 140.34795000, 2337813.95373040, 0 ]
 8498: [ 1668704520000, 16658.53000000, 16677.40000000, 16658.52000000, 16673.91000000, 189.25594000, 1668704579999, 3154901.49785200, 3246, 107.74395000, 1796194.88950920, 0 ]
 9499: [ 1668704580000, 16673.86000000, 16686.16000000, 16662.65000000, 16670.89000000, 97.68432000, 1668704639999, 1628707.62788410, 2400, 46.47844000, 775010.42178890, 0 ]
10500: [ 1668704640000, 16669.80000000, 16678.15000000, 16668.27000000, 16668.27000000, 27.51324000, 1668704699999, 458747.76417530, 778, 12.29112000, 204949.28881110, 0 ]

Other examples

Another example of using generated code — not Binance API related — can be found here: Haskell-OpenAPI-Code-Generator/Stripe-Haskell-Library/example.

Disclaimer

I have not yet tested this client library myself — aside from the small examples given above — so no guarantees are given at all! Use this software at your own risk — caveat emptor!

Posts in this Series