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