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:
openapi3-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:
{-# LANGUAGE MultiWayIf #-}
-- CHANGE WITH CAUTION: This is a generated code file generated by https://github.com/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator.
{-# LANGUAGE OverloadedStrings #-}
-- | Contains the types generated from the schema AggTrade
module Binance.Types.AggTrade where
import qualified Binance.Common
import Binance.TypeAlias
import qualified Control.Monad.Fail
import qualified Data.Aeson
import qualified Data.Aeson as Data.Aeson.Encoding.Internal
import qualified Data.Aeson as Data.Aeson.Types
import qualified Data.Aeson as Data.Aeson.Types.FromJSON
import qualified Data.Aeson as Data.Aeson.Types.Internal
import qualified Data.Aeson as Data.Aeson.Types.ToJSON
import qualified Data.ByteString.Char8
import qualified Data.ByteString.Char8 as Data.ByteString.Internal
import qualified Data.Foldable
import qualified Data.Functor
import qualified Data.Maybe
import qualified Data.Scientific
import qualified Data.Text
import qualified Data.Text.Internal
import qualified Data.Time.Calendar as Data.Time.Calendar.Days
import qualified Data.Time.LocalTime as Data.Time.LocalTime.Internal.ZonedTime
import qualified GHC.Base
import qualified GHC.Classes
import qualified GHC.Int
import qualified GHC.Show
import qualified GHC.Types
import qualified Prelude as GHC.Integer.Type
import qualified Prelude as GHC.Maybe
-- | Defines the object schema located at @components.schemas.aggTrade@ in the specification.
data AggTrade = AggTrade
-- | M: Was the trade the best price match?
{ aggTradeMM :: GHC.Types.Bool,
-- | T: Timestamp
aggTradeT :: GHC.Types.Bool,
-- | a: Aggregate tradeId
aggTradeA :: GHC.Int.Int64,
-- | f: First tradeId
aggTradeF :: GHC.Int.Int64,
-- | l: Last tradeId
aggTradeL :: GHC.Int.Int64,
-- | m: Was the buyer the maker?
aggTradeM :: GHC.Types.Bool,
-- | p: Price
aggTradeP :: Data.Text.Internal.Text,
-- | q: Quantity
aggTradeQ :: Data.Text.Internal.Text
}deriving
GHC.Show.Show,
( GHC.Classes.Eq
)
instance Data.Aeson.Types.ToJSON.ToJSON AggTrade where
= 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))
toJSON 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)))
toEncoding obj
instance Data.Aeson.Types.FromJSON.FromJSON AggTrade where
= 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"))
parseJSON
-- | Create a new 'AggTrade' with all required fields.
mkAggTrade ::
-- | 'aggTradeMM'
GHC.Types.Bool ->
-- | 'aggTradeT'
GHC.Types.Bool ->
-- | 'aggTradeA'
GHC.Int.Int64 ->
-- | 'aggTradeF'
GHC.Int.Int64 ->
-- | 'aggTradeL'
GHC.Int.Int64 ->
-- | 'aggTradeM'
GHC.Types.Bool ->
-- | 'aggTradeP'
Data.Text.Internal.Text ->
-- | 'aggTradeQ'
Data.Text.Internal.Text ->
AggTrade
=
mkAggTrade aggTradeMM aggTradeT aggTradeA aggTradeF aggTradeL aggTradeM aggTradeP aggTradeQ AggTrade
= aggTradeMM,
{ aggTradeMM = aggTradeT,
aggTradeT = aggTradeA,
aggTradeA = aggTradeF,
aggTradeF = aggTradeL,
aggTradeL = aggTradeM,
aggTradeM = aggTradeP,
aggTradeP = aggTradeQ
aggTradeQ }
Usage examples
getSapiV1SystemStatus
{-# LANGUAGE OverloadedStrings #-}
module Example1 where
import qualified Binance
import Control.Monad (when)
import qualified Data.Text as T
import qualified Network.HTTP.Simple as HS
trade :: Bool -> IO ()
= do
trade verbose <- getSystemStatus
ss putStrLn $ "Binance API system status: " <> ss
where
getSystemStatus :: IO String
= do
getSystemStatus <- Binance.runWithConfiguration Binance.defaultConfiguration Binance.getSapiV1SystemStatus
resp print resp)
when verbose (pure $
case HS.getResponseBody resp of
Binance.GetSapiV1SystemStatusResponse200 status ->
T.unpack (Binance.getSapiV1SystemStatusResponseBody200Msg status)++ " ("
++ show (Binance.getSapiV1SystemStatusResponseBody200Status status)
++ ")"
-> "wrong type of response" _
Running this example code results in the following output:
Response {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 {
host = "api.binance.com"
port = 443
secure = True
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)")]
path = "/sapi/v1/system/status"
queryString = ""
method = "GET"
proxy = Nothing
rawBody = False
redirectCount = 10
responseTimeout = ResponseTimeoutDefault
requestVersion = HTTP/1.1
proxySecureMode = ProxySecureWithConnect
}
}
Binance API system status: normal (0)
getApiV3Klines
{-# LANGUAGE OverloadedStrings #-}
module Example2 where
import qualified Binance
import Control.Monad (when)
import Data.Either ()
import qualified Data.Text as T
import qualified Network.HTTP.Simple as HS
= Binance.defaultConfiguration
defaultConf
= Binance.apiKeyInHeaderAuthenticationSecurityScheme "your-api-key-goes-here"
config
=
conf
defaultConf= config
{ Binance.configSecurityScheme
}
trade :: Bool -> IO ()
= do
trade verbose <- getKlines
kl putStr "Binance API klines "
case kl of
Left e -> putStrLn $ "*** " <> e
Right kls -> do
let l = length kls
putStrLn $ "(" <> show l <> ")"
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) ]"
mapM_ (\(i, ks) -> putStrLn $ show i <> ": [ " <> printKline ks <> " ]") $ zip [1 ..] kls
where
= foldl (\acc c -> acc <> (if null acc then "" else ", ") <> kLineToString c) ""
printKline where
= case k of
kLineToString k Binance.GetApiV3KlinesResponseBody200Variant1 s -> show s
Binance.GetApiV3KlinesResponseBody200Variant2 s -> T.unpack s
= do
getKlines <-
resp $
Binance.runWithConfiguration conf $
Binance.getApiV3Klines Binance.GetApiV3KlinesParametersQueryIntervalEnum1m "BTCBUSD"
Binance.mkGetApiV3KlinesParameters pure $
case HS.getResponseBody resp of
Binance.GetApiV3KlinesResponse200 klines -> Right klines
-> Left "wrong type of response" _
Running this example code results in the following output (excerpt):
Binance API klines (500)
[ 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) ]
1: [ 1668674700000, 16579.28000000, 16580.95000000, 16572.00000000, 16576.36000000, 55.90257000, 1668674759999, 926698.28532810, 1124, 25.76195000, 427075.54336190, 0 ]
2: [ 1668674760000, 16576.36000000, 16580.35000000, 16567.78000000, 16570.00000000, 47.20438000, 1668674819999, 782390.60662060, 780, 19.95414000, 330747.43392430, 0 ]
3: [ 1668674820000, 16570.00000000, 16574.23000000, 16565.37000000, 16568.04000000, 44.22206000, 1668674879999, 732752.61122150, 906, 26.58506000, 440518.72028710, 0 ]
...
497: [ 1668704460000, 16643.01000000, 16670.00000000, 16639.54000000, 16660.34000000, 260.66434000, 1668704519999, 4341628.84450980, 3393, 140.34795000, 2337813.95373040, 0 ]
498: [ 1668704520000, 16658.53000000, 16677.40000000, 16658.52000000, 16673.91000000, 189.25594000, 1668704579999, 3154901.49785200, 3246, 107.74395000, 1796194.88950920, 0 ]
499: [ 1668704580000, 16673.86000000, 16686.16000000, 16662.65000000, 16670.89000000, 97.68432000, 1668704639999, 1628707.62788410, 2400, 46.47844000, 775010.42178890, 0 ]
500: [ 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!