1 {-# LANGUAGE ViewPatterns, NoMonomorphismRestriction #-}
8 import Control
.Applicative
(pure
, (<*>))
9 import Control
.Monad
(Monad
, fail, return)
10 import Data
.Bool (Bool(False, True))
11 import Data
.Fixed
(HasResolution
(..), Fixed
)
12 import Data
.Foldable
(foldMap
)
13 import Data
.Function
(const, id, on
)
14 import Data
.Functor
(fmap, (<$>))
15 import Data
.List
(minimum, maximum)
16 import Data
.Maybe (Maybe(Nothing
, Just
), catMaybes, mapMaybe)
17 import Data
.Monoid
(Monoid
(..), Sum
(..), (<>))
18 import Data
.Ord
(Ord
(..), min, max)
19 import Data
.Tuple
(curry, uncurry)
20 import Numeric
(readFloat)
22 ( Num
, Show(show), Fractional
(fromRational)
24 , print, putStr, putStrLn, read, take, undefined
30 import qualified Data
.ByteString
as BS
31 import qualified Data
.ByteString
.Char8
as CS
34 import Network
.URI
(URI
, parseAbsoluteURI
)
37 import Codec
.Utils
(Octet
)
38 import Data
.HMAC
(HashMethod
(..), hmac
)
39 import qualified Data
.Digest
.SHA512
as SHA512
(hash
)
42 import Network
.HTTP
.Base
43 ( Request
(..), Response
(..), RequestMethod
(..)
44 , urlEncodeVars
, httpPackageVersion
47 import Network
.HTTP
.Headers
(Header
(Header
), HeaderName
(HdrCustom
))
49 import qualified Network
.Browser
as Browser
(request
)
50 import Network
.Browser
51 ( Proxy
(NoProxy
), BrowserAction
52 , browse
, setAllowRedirects
, setMaxRedirects
, setAuthorities
53 , setAllowBasicAuth
, setMaxErrorRetries
, setMaxPoolSize
54 , setMaxAuthAttempts
, setProxy
, setUserAgent
58 import Control
.Monad
.Trans
.Either
59 ( EitherT
(..), bimapEitherT
, eitherT
, runEitherT
, hoistEither
63 import Control
.Error
.Util
(note
)
66 import qualified Data
.Text
as T
68 -- unordered-containers
69 import qualified Data
.HashMap
.Strict
as HM
(lookup)
72 import Cryptsy
.API
.Public
.MarketData
.New
73 import qualified Cryptsy
.API
.Public
.Market
as Market
(withText
)
74 import qualified Cryptsy
.API
.Public
.Trade
as Trade
75 ( price
, quantity
, total
)
78 url
= "https://www.cryptsy.com/api"
81 mUri
= parseAbsoluteURI url
84 apiPubkey
= "5a8808b25e3f59d8818d3fbc0ce993fbb82dcf90"
89 get_nonce
:: IO Integer
96 sha512_hm
:: HashMethod
97 sha512_hm
= HashMethod
98 { digest
= SHA512
.hash
99 , input_blocksize
= 1024
102 hexdump
:: [Octet
] -> String
103 hexdump
= mconcat
. fmap show2Hex
107 readHex :: String -> Maybe [Octet
]
111 oldMain
= case (readHex apiPrivkey
, mUri
) of
114 putStrLn " could not be parsed as an absolute URI."
116 putStrLn "<apiPrivkey> could not be parsed as hex."
117 (Just pk
, Just uri
) -> do
120 postString
= urlEncodeVars
[ ("nonce", show nonce
) ]
121 postSig
= hexdump
. hmac sha512_hm pk
. BS
.unpack
$ CS
.pack postString
126 [ Header
(HdrCustom
"Key" ) apiPubkey
127 , Header
(HdrCustom
"Sign") postSig
129 , rqBody
= postString
133 (_
, response
) <- Browser
.request request
134 return $ rspBody response
137 initBrowser
:: BrowserAction t
()
139 setAllowRedirects
True
140 setMaxRedirects
$ Just
10
142 setAllowBasicAuth
False
143 setMaxErrorRetries
$ Just
0
144 setMaxPoolSize
$ Just
2
145 setMaxAuthAttempts
$ Just
0
147 setUserAgent
$ "Network.Browser/" <> httpPackageVersion
151 instance HasResolution E8
where
152 resolution
= const 100000000
154 type CryptsyNum
= Fixed E8
156 parseCryptsyNum
:: Text
-> Parser CryptsyNum
157 parseCryptsyNum
(readFloat . T
.unpack
-> reads) = case reads of
158 [] -> fail "No parse."
159 [(r
, "")] -> return r
160 [(_
, _
)] -> fail "Incomplete parse."
161 _
-> fail "Ambiguous parse."
163 newtype Min n
= Min
{ getMin
:: Maybe n
} deriving Show
164 instance Ord n
=> Monoid
(Min n
) where
166 mappend
(Min Nothing
) m
= m
167 mappend m
(Min Nothing
) = m
168 mappend
(Min
(Just m
)) (Min
(Just n
)) = Min
. Just
$ min m n
169 mconcat
[] = Min Nothing
170 mconcat mins
= Min
. Just
. minimum . catMaybes $ fmap getMin mins
172 newtype Max n
= Max
{ getMax
:: Maybe n
} deriving Show
173 instance Ord n
=> Monoid
(Max n
) where
175 mappend
(Max Nothing
) m
= m
176 mappend m
(Max Nothing
) = m
177 mappend
(Max
(Just m
)) (Max
(Just n
)) = Max
. Just
$ max m n
178 mconcat
[] = Max Nothing
179 mconcat maxs
= Max
. Just
. maximum $ mapMaybe getMax maxs
181 data MarketSummary p q t
= MarketSummary
184 , count
:: Sum
Integer
185 , primaryVolume
:: Sum q
186 , secondaryVolume
:: Sum t
188 instance (Ord p
, Num q
, Num t
) => Monoid
(MarketSummary p q t
) where
189 mempty
= MarketSummary
195 mappend
= curry $ MarketSummary
<$>
196 uncurry (mappend `on` minPrice
) <*>
197 uncurry (mappend `on` maxPrice
) <*>
198 uncurry (mappend `on` count
) <*>
199 uncurry (mappend `on` primaryVolume
) <*>
200 uncurry (mappend `on` secondaryVolume
)
201 mconcat
= MarketSummary
<$>
202 (mconcat
. fmap minPrice
) <*>
203 (mconcat
. fmap maxPrice
) <*>
204 (mconcat
. fmap count
) <*>
205 (mconcat
. fmap primaryVolume
) <*>
206 (mconcat
. fmap secondaryVolume
)
208 tradeSummary
:: GTrade dt p q t
-> MarketSummary p q t
209 tradeSummary trade
= MarketSummary
210 { minPrice
= Min
. Just
$ Trade
.price trade
211 , maxPrice
= Max
. Just
$ Trade
.price trade
213 , primaryVolume
= Sum
$ Trade
.quantity trade
214 , secondaryVolume
= Sum
$ Trade
.total trade
218 main
= eitherT
putStrLn print $ do
219 MarketData mkts
<- bimapEitherT
show id . EitherT
. browse
$ do
221 runEitherT
$ marketData parseMarkets
222 mkt
<- hoistEither
. note
"No 'LTC/BTC' market."
223 $ HM
.lookup (T
.pack
"LTC/BTC") mkts
224 return . foldMap tradeSummary
$ recenttrades mkt
226 parseMarkets
= withMarket parseMarket
227 parseMarket
= Market
.withText