Execution layer for algorithmic trading
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
4.2 KiB

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module ATrade.Quotes.QHP (
Period(..),
RequestParams(..),
QHPHandle,
mkQHPHandle,
requestHistoryFromQHP
) where
import ATrade.Exceptions
import ATrade.Logging (Message, logInfo)
import ATrade.Types
import Colog (WithLog)
import Control.Exception.Safe (MonadThrow, throw)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson
import Data.Binary.Get
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import Data.Time.Calendar
import Data.Time.Clock
import Data.Time.Clock.POSIX
import Data.Time.Format
import Language.Haskell.Printf (t)
import System.ZMQ4
data Period =
Period1Min |
Period5Min |
Period15Min |
Period30Min |
PeriodHour |
PeriodDay |
PeriodWeek |
PeriodMonth
deriving (Eq)
instance Show Period where
show Period1Min = "M1"
show Period5Min = "M5"
show Period15Min = "M15"
show Period30Min = "M30"
show PeriodHour = "H1"
show PeriodDay = "D"
show PeriodWeek = "W"
show PeriodMonth = "MN"
data QHPHandle = QHPHandle
{
qhpContext :: Context
, qhpEndpoint :: T.Text
}
mkQHPHandle :: Context -> T.Text -> QHPHandle
mkQHPHandle = QHPHandle
requestHistoryFromQHP :: (WithLog env Message m, MonadThrow m, MonadIO m) => QHPHandle -> TickerId -> BarTimeframe -> UTCTime -> UTCTime -> m [Bar]
requestHistoryFromQHP qhp tickerId timeframe fromTime toTime =
case parseQHPPeriod (unBarTimeframe timeframe) of
Just tf -> getQuotes (qhpContext qhp) (params tf)
_ -> throw $ BadParams "QHP: Unable to parse timeframe"
where
params tf = RequestParams
{
endpoint = qhpEndpoint qhp,
ticker = tickerId,
startDate = utctDay fromTime,
endDate = utctDay toTime,
period = tf
}
parseQHPPeriod x
| x == 60 = Just Period1Min
| x == 5 * 60 = Just Period5Min
| x == 15 * 60 = Just Period15Min
| x == 30 * 60 = Just Period30Min
| x == 60 * 60 = Just PeriodHour
| x == 24 * 60 * 60 = Just PeriodDay
| otherwise = Nothing
data RequestParams =
RequestParams
{
endpoint :: T.Text,
ticker :: T.Text,
startDate :: Day,
endDate :: Day,
period :: Period
} deriving (Show, Eq)
printDatetime :: UTCTime -> String
printDatetime = formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S"))
instance ToJSON RequestParams where
toJSON p = object [ "ticker" .= ticker p,
"from" .= printDatetime (UTCTime (startDate p) 0),
"to" .= printDatetime (UTCTime (endDate p) 0),
"timeframe" .= show (period p) ]
getQuotes :: (WithLog env Message m, MonadIO m) => Context -> RequestParams -> m [Bar]
getQuotes ctx params = do
logInfo "QHP" $ "Connecting to ep: " <> endpoint params
liftIO $ withSocket ctx Req $ \sock -> do
connect sock $ (T.unpack . endpoint) params
send sock [] (BL.toStrict $ encode params)
response <- receiveMulti sock
case response of
[header, rest] -> if header == "OK"
then return $ reverse $ parseBars (ticker params) $ BL.fromStrict rest
else return []
_ -> return []
parseBars :: TickerId -> BL.ByteString -> [Bar]
parseBars tickerId input =
case runGetOrFail parseBar input of
Left _ -> []
Right (rest, _, bar) -> bar : parseBars tickerId rest
where
parseBar = do
rawTimestamp <- realToFrac <$> getWord64le
baropen <- getDoublele
barhigh <- getDoublele
barlow <- getDoublele
barclose <- getDoublele
barvolume <- getWord64le
return Bar
{
barSecurity = tickerId,
barTimestamp = posixSecondsToUTCTime rawTimestamp,
barOpen = fromDouble baropen,
barHigh = fromDouble barhigh,
barLow = fromDouble barlow,
barClose = fromDouble barclose,
barVolume = toInteger barvolume
}