Market Data Storage Server
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.

131 lines
5.5 KiB

9 years ago
{-# LANGUAGE OverloadedStrings #-}
module ATrade.MDS.Database (
9 years ago
DatabaseConfig(..),
DatabaseInterface(..),
startDatabase,
stopDatabase
9 years ago
) where
import qualified Data.Configurator as C
import qualified Data.Text as T
9 years ago
import qualified Data.Text.Lazy as TL
import Data.Text.Format
9 years ago
import qualified Data.Vector as V
import ATrade.Types
import Data.Time.Clock
9 years ago
import Data.Time.Clock.POSIX
import Data.Maybe
9 years ago
import Control.Concurrent.MVar
import Control.Concurrent
import System.Log.Logger
import Database.HDBC
import Database.HDBC.PostgreSQL
9 years ago
import Control.Monad
import Control.Monad.Loops
9 years ago
data TimeInterval = TimeInterval UTCTime UTCTime
data Timeframe = Timeframe Int
timeframeDaily = Timeframe 86400
timeframeHour = Timeframe 3600
timeframeMinute = Timeframe 60
data DatabaseCommand = DBGet TickerId TimeInterval Timeframe | DBPut TickerId TimeInterval Timeframe (V.Vector Bar)
9 years ago
data DatabaseResponse = DBOk | DBData [(TimeInterval, V.Vector Bar)] | DBError T.Text
9 years ago
data DatabaseConfig = DatabaseConfig {
dbHost :: T.Text,
dbDatabase :: T.Text,
dbUser :: T.Text,
dbPassword :: T.Text
} deriving (Show, Eq)
data DatabaseInterface = DatabaseInterface {
tid :: ThreadId,
getData :: TickerId -> TimeInterval -> Timeframe -> IO [(TimeInterval, V.Vector Bar)],
putData :: TickerId -> TimeInterval -> Timeframe -> V.Vector Bar -> IO ()
}
startDatabase :: DatabaseConfig -> IO DatabaseInterface
startDatabase config = do
conn <- connectPostgreSQL (mkConnectionString config)
9 years ago
makeSchema conn
9 years ago
cmdVar <- newEmptyMVar
respVar <- newEmptyMVar
9 years ago
compVar <- newEmptyMVar
tid <- forkFinally (dbThread conn cmdVar respVar) (cleanup conn cmdVar respVar compVar)
9 years ago
return DatabaseInterface {
tid = tid,
getData = doGetData cmdVar respVar,
putData = doPutData cmdVar respVar }
where
9 years ago
makeSchema conn = runRaw conn "CREATE TABLE IF NOT EXISTS bars (id SERIAL PRIMARY KEY, ticker TEXT, timestamp BIGINT, open NUMERIC(20, 10), high NUMERIC(20, 10), low NUMERIC(20, 10), close NUMERIC(20,10), volume BIGINT);"
mkConnectionString config = TL.unpack $ format "User ID={};Password={};Host={};Port=5432;Database={}" (dbUser config, dbPassword config, dbHost config, dbDatabase config)
9 years ago
dbThread conn cmdVar respVar = forever $ do
cmd <- readMVar cmdVar
handleCmd conn cmd >>= putMVar respVar
whileM_ (isJust <$> tryReadMVar respVar) yield
takeMVar cmdVar
cleanup conn cmdVar respVar compVar _ = disconnect conn >> putMVar compVar ()
handleCmd conn cmd = case cmd of
9 years ago
DBPut tickerId (TimeInterval start end) tf@(Timeframe timeframeSecs) bars -> do
9 years ago
delStmt <- prepare conn "DELETE FROM bars WHERE timestamp > ? AND timestamp < ? AND ticker == ? AND timeframe == ?;"
9 years ago
execute delStmt [(SqlPOSIXTime . utcTimeToPOSIXSeconds) start, (SqlPOSIXTime . utcTimeToPOSIXSeconds) end, (SqlString . T.unpack) tickerId, (SqlInteger . toInteger) timeframeSecs]
stmt <- prepare conn $ "INSERT INTO bars (ticker, timeframe, timestamp, open, high, low, close, volume)" ++
9 years ago
" values (?, ?, ?, ?, ?, ?, ?, ?); "
9 years ago
executeMany stmt (map (barToSql tf) $ V.toList bars)
9 years ago
return DBOk
DBGet tickerId interval@(TimeInterval start end) (Timeframe timeframeSecs) -> do
9 years ago
rows <- quickQuery' conn "SELECT timestamp, open, high, low, close, volume FROM bars WHERE ticker == ? AND timeframe == ? AND timestamp > ? AND timestamp < ?;" [(toSql. T.unpack) tickerId, toSql timeframeSecs, (toSql . utcTimeToPOSIXSeconds) start, (toSql . utcTimeToPOSIXSeconds) end]
return $ DBData [(interval, V.fromList $ mapMaybe (barFromResult tickerId) rows)]
barFromResult ticker [ts, open, high, low, close, volume] = Just Bar {
barSecurity = ticker,
barTimestamp = fromSql ts,
barOpen = fromRational $ fromSql open,
barHigh = fromRational $ fromSql high,
barLow = fromRational $ fromSql low,
barClose = fromRational $ fromSql close,
barVolume = fromSql volume
}
barFromResult _ _ = Nothing
barToSql :: Timeframe -> Bar -> [SqlValue]
barToSql (Timeframe timeframeSecs) bar = [(SqlString . T.unpack . barSecurity) bar,
(SqlInteger . toInteger) timeframeSecs,
(SqlRational . toRational . barOpen) bar,
(SqlRational . toRational . barHigh) bar,
(SqlRational . toRational . barLow) bar,
(SqlRational . toRational . barClose) bar,
(SqlRational . toRational . barVolume) bar ]
9 years ago
9 years ago
stopDatabase :: MVar () -> DatabaseInterface -> IO ()
stopDatabase compVar db = killThread (tid db) >> readMVar compVar
9 years ago
doGetData :: MVar DatabaseCommand -> MVar DatabaseResponse -> TickerId -> TimeInterval -> Timeframe -> IO [(TimeInterval, V.Vector Bar)]
doGetData cmdVar respVar tickerId timeInterval timeframe = do
putMVar cmdVar (DBGet tickerId timeInterval timeframe)
resp <- takeMVar respVar
case resp of
DBData x -> return x
DBError err -> do
warningM "DB.Client" $ "Error while calling getData: " ++ show err
return []
9 years ago
_ -> do
warningM "DB.Client" "Unexpected response"
return []
9 years ago
doPutData :: MVar DatabaseCommand -> MVar DatabaseResponse -> TickerId -> TimeInterval -> Timeframe -> V.Vector Bar -> IO ()
doPutData cmdVar respVar tickerId timeInterval timeframe bars = do
putMVar cmdVar (DBPut tickerId timeInterval timeframe bars)
resp <- takeMVar respVar
case resp of
9 years ago
DBOk -> return ()
9 years ago
DBError err -> do
warningM "DB.Client" $ "Error while calling putData: " ++ show err
return ()
9 years ago
_ -> do
warningM "DB.Client" "Unexpected response"
9 years ago
return ()
9 years ago