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.
309 lines
14 KiB
309 lines
14 KiB
|
3 years ago
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||
|
|
{-# LANGUAGE OverloadedLabels #-}
|
||
|
|
{-# LANGUAGE RecordWildCards #-}
|
||
|
|
{-# LANGUAGE TypeApplications #-}
|
||
|
|
|
||
|
|
module TXMLConnector
|
||
|
|
(
|
||
|
|
start
|
||
|
|
) where
|
||
|
|
|
||
|
|
import ATrade.Logging (Message, Severity (..),
|
||
|
|
logWith)
|
||
|
|
import Colog (LogAction)
|
||
|
|
import Config (SubscriptionConfig (SubscriptionConfig),
|
||
|
|
TransaqConnectorConfig (..),
|
||
|
|
transaqHost, transaqLogLevel,
|
||
|
|
transaqLogPath, transaqLogin,
|
||
|
|
transaqPassword, transaqPort)
|
||
|
|
import Control.Concurrent (ThreadId, forkIO, threadDelay)
|
||
|
|
import Control.Concurrent.STM (TVar, atomically, modifyTVar',
|
||
|
|
newTVarIO, readTVarIO,
|
||
|
|
writeTVar)
|
||
|
|
import Control.Concurrent.STM.TBQueue (TBQueue, newTBQueue,
|
||
|
|
readTBQueue, writeTBQueue)
|
||
|
|
import Control.Monad (forever, void)
|
||
|
|
import Data.Maybe (mapMaybe)
|
||
|
|
import qualified Data.Text as T
|
||
|
|
import Text.XML.Light.Input (parseXML)
|
||
|
|
import Text.XML.Light.Types (Content (Elem),
|
||
|
|
Element (elName),
|
||
|
|
QName (qName))
|
||
|
|
import Transaq (AllTradesTrade (..),
|
||
|
|
CommandConnect (..),
|
||
|
|
CommandDisconnect (CommandDisconnect),
|
||
|
|
CommandSubscribe (..),
|
||
|
|
ConnectionState (Disconnected),
|
||
|
|
Language (LanguageEn),
|
||
|
|
MarketInfo (..),
|
||
|
|
Quotation (..),
|
||
|
|
ResponseAllTrades (ResponseAllTrades),
|
||
|
|
ResponseCandleKinds (ResponseCandleKinds),
|
||
|
|
ResponseMarkets (ResponseMarkets),
|
||
|
|
ResponseQuotations (ResponseQuotations),
|
||
|
|
ResponseQuotes (ResponseQuotes),
|
||
|
|
ResponseSecurities (ResponseSecurities),
|
||
|
|
SecurityId (..),
|
||
|
|
TransaqCommand (toXml),
|
||
|
|
TransaqResponse (..),
|
||
|
|
TransaqResponse (..),
|
||
|
|
TransaqResponseC (fromXml),
|
||
|
|
state)
|
||
|
|
import TXML (LogLevel, freeCallback,
|
||
|
|
initialize, sendCommand,
|
||
|
|
setCallback)
|
||
|
|
|
||
|
|
import ATrade.QuoteSource.Server (QuoteSourceServerData (..))
|
||
|
|
import ATrade.Types (DataType (BestBid, BestOffer, LastTradePrice),
|
||
|
|
Tick (..), TickerId,
|
||
|
|
fromDouble)
|
||
|
|
import Control.Concurrent.BoundedChan (BoundedChan, writeChan)
|
||
|
|
import Control.Monad (forM_)
|
||
|
|
import qualified Data.Map.Strict as M
|
||
|
|
import Data.Time.Clock (UTCTime, getCurrentTime)
|
||
|
|
import qualified Transaq
|
||
|
|
import qualified TXML
|
||
|
|
|
||
|
|
data ConnectionParams =
|
||
|
|
ConnectionParams
|
||
|
|
{
|
||
|
|
cpLogin :: T.Text
|
||
|
|
, cpPassword :: T.Text
|
||
|
|
, cpHost :: T.Text
|
||
|
|
, cpPort :: Int
|
||
|
|
, cpLogPath :: T.Text
|
||
|
|
, cpLogLevel :: LogLevel
|
||
|
|
}
|
||
|
|
deriving (Show, Eq, Ord)
|
||
|
|
|
||
|
|
data TXMLConnectorHandle =
|
||
|
|
TXMLConnectorHandle
|
||
|
|
{
|
||
|
|
threadId :: ThreadId,
|
||
|
|
notificationQueue :: TBQueue TransaqResponse
|
||
|
|
}
|
||
|
|
|
||
|
|
data ConnectionStage = StageConnection | StageGetInfo | StageConnected
|
||
|
|
deriving (Eq, Show, Ord)
|
||
|
|
|
||
|
|
data TickKey = TickKey TickerId DataType
|
||
|
|
deriving (Show, Ord, Eq)
|
||
|
|
|
||
|
|
start ::
|
||
|
|
LogAction IO Message
|
||
|
|
-> TransaqConnectorConfig
|
||
|
|
-> BoundedChan QuoteSourceServerData
|
||
|
|
-> IO TXMLConnectorHandle
|
||
|
|
start logger config qssChannel = do
|
||
|
|
logWith logger Info "TXMLConnector" "Starting"
|
||
|
|
notificationQueue <- atomically $ newTBQueue 50000
|
||
|
|
tickTable <- newTVarIO M.empty
|
||
|
|
threadId <- forkIO (workThread logger config notificationQueue qssChannel tickTable)
|
||
|
|
return $ TXMLConnectorHandle {..}
|
||
|
|
|
||
|
|
workThread ::
|
||
|
|
LogAction IO Message
|
||
|
|
-> TransaqConnectorConfig
|
||
|
|
-> TBQueue TransaqResponse
|
||
|
|
-> BoundedChan QuoteSourceServerData
|
||
|
|
-> TVar (M.Map TickKey Tick)
|
||
|
|
-> IO ()
|
||
|
|
workThread logger config queue qssChannel tickMap = do
|
||
|
|
rc <- initialize (transaqLogPath config) (parseTransaqLogLevel $ transaqLogLevel config)
|
||
|
|
case rc of
|
||
|
|
Left str -> log Error "TXMLConnector.WorkThread" $ "Unable to initialize TXML" <> str
|
||
|
|
Right _ -> do
|
||
|
|
rc <- setCallback parseAndWrite
|
||
|
|
case rc of
|
||
|
|
Nothing -> log Error "TXMLConnector.WorkThread" "Unable to set callback"
|
||
|
|
Just cb -> do
|
||
|
|
serverConnected <- newTVarIO StageConnection
|
||
|
|
void $ forever $ do
|
||
|
|
connStatus <- readTVarIO serverConnected
|
||
|
|
case connStatus of
|
||
|
|
StageConnection -> handleUnconnected serverConnected
|
||
|
|
StageGetInfo -> handleGetInfo serverConnected
|
||
|
|
StageConnected -> handleConnected serverConnected
|
||
|
|
freeCallback cb
|
||
|
|
where
|
||
|
|
log = logWith logger
|
||
|
|
parseTransaqLogLevel 1 = TXML.Warning
|
||
|
|
parseTransaqLogLevel 3 = TXML.Debug
|
||
|
|
parseTransaqLogLevel _ = TXML.Info
|
||
|
|
parseAndWrite xml = do
|
||
|
|
let parsed = mapMaybe parseContent $ parseXML xml
|
||
|
|
log Debug "TXML.Callback" $ "Parsed entities: " <> (T.pack . show . length) parsed
|
||
|
|
log Debug "TXML.Callback" $ "parsed xml: " <> (T.pack . show) (parseXML xml)
|
||
|
|
log Debug "TXML.Callback" $ "parsed: " <> (T.pack . show) xml
|
||
|
|
mapM_ writeToQueue parsed
|
||
|
|
pure True
|
||
|
|
parseContent (Elem el) = parseElement el
|
||
|
|
parseContent _ = Nothing
|
||
|
|
parseElement el = case qName $ elName el of
|
||
|
|
"candles" -> TransaqResponseCandles <$> fromXml el
|
||
|
|
"server_status" -> TransaqResponseServerStatus <$> fromXml el
|
||
|
|
"markets" -> TransaqResponseMarkets <$> fromXml el
|
||
|
|
"candlekinds" -> TransaqResponseCandleKinds <$> fromXml el
|
||
|
|
"securities" -> TransaqResponseSecurities <$> fromXml el
|
||
|
|
"sec_info" -> TransaqResponseSecInfo <$> fromXml el
|
||
|
|
"quotations" -> TransaqResponseQuotations <$> fromXml el
|
||
|
|
"alltrades" -> TransaqResponseAllTrades <$> fromXml el
|
||
|
|
"quotes" -> TransaqResponseQuotes <$> fromXml el
|
||
|
|
_ -> Nothing
|
||
|
|
writeToQueue resp = atomically $ writeTBQueue queue resp
|
||
|
|
handleConnected serverConnected = do
|
||
|
|
item <- atomically $ readTBQueue queue
|
||
|
|
case item of
|
||
|
|
TransaqResponseAllTrades (ResponseAllTrades trades) -> do
|
||
|
|
let ticks = fmap allTradeToTick trades
|
||
|
|
forM_ ticks (writeChan qssChannel . QSSTick)
|
||
|
|
forM_ ticks insertToTickMap
|
||
|
|
TransaqResponseQuotations (ResponseQuotations quotations) -> do
|
||
|
|
now <- getCurrentTime
|
||
|
|
let ticks = concatMap (quotationToTicks now) quotations
|
||
|
|
forM_ ticks (writeChan qssChannel . QSSTick)
|
||
|
|
forM_ ticks insertToTickMap
|
||
|
|
_ -> pure ()
|
||
|
|
handleGetInfo serverConnected = do
|
||
|
|
item <- atomically $ readTBQueue queue
|
||
|
|
case item of
|
||
|
|
TransaqResponseServerStatus serverStatus ->
|
||
|
|
case state serverStatus of
|
||
|
|
Transaq.Disconnected -> do
|
||
|
|
log Warning "TXMLConnector.WorkThread" "Server disconnected"
|
||
|
|
atomically $ writeTVar serverConnected StageConnection
|
||
|
|
Transaq.Connected -> do
|
||
|
|
log Info "TXMLConnector.WorkThread" "Server connected"
|
||
|
|
atomically $ writeTVar serverConnected StageConnected
|
||
|
|
v <- makeSubscriptions config
|
||
|
|
case v of
|
||
|
|
Left errmsg -> do
|
||
|
|
log Warning "TXMLConnector.WorkThread" $ "Unable to subscribe: " <> errmsg
|
||
|
|
void $ sendCommand $ toXml CommandDisconnect
|
||
|
|
Right _ -> log Info "TXMLConnector.WorkThread" "Subscriptions done"
|
||
|
|
Transaq.Error errmsg -> do
|
||
|
|
log Warning "TXMLConnector.WorkThread" $ "Connection error: " <> errmsg
|
||
|
|
atomically $ writeTVar serverConnected StageConnection
|
||
|
|
TransaqResponseResult result ->
|
||
|
|
log Info "TXMLConnector.WorkThread" $ "Incoming result" <> (T.pack . show) result
|
||
|
|
-- TODO: handle order response
|
||
|
|
TransaqResponseCandles candles ->
|
||
|
|
log Debug "TXMLConnector.WorkThread" $
|
||
|
|
"Incoming candles message: " <> (T.pack . show . length . Transaq.candles $ candles)
|
||
|
|
-- TODO: Pass to qhp
|
||
|
|
TransaqResponseMarkets (ResponseMarkets markets) -> do
|
||
|
|
log Debug "TXMLConnector.WorkThread" "Incoming markets:"
|
||
|
|
forM_ markets (\m -> log Debug "TXMLConnector.WorkThread" $ (T.pack . show) (marketId m) <> "/" <> marketName m)
|
||
|
|
-- TODO: Pass to qtis
|
||
|
|
TransaqResponseCandleKinds (ResponseCandleKinds kinds) -> do
|
||
|
|
log Debug "TXMLConnector.WorkThread" "Incoming candle kinds:"
|
||
|
|
forM_ kinds (log Debug "TXMLConnector.WorkThread" . (T.pack . show))
|
||
|
|
-- TODO: Pass to qtis, maybe something else?
|
||
|
|
TransaqResponseSecurities (ResponseSecurities securities) -> do
|
||
|
|
log Debug "TXMLConnector.WorkThread" "Incoming securities:"
|
||
|
|
forM_ securities (log Debug "TXMLConnector.WorkThread" . (T.pack . show))
|
||
|
|
-- TODO: Pass to qtis
|
||
|
|
TransaqResponseSecInfo secInfo ->
|
||
|
|
log Debug "TXMLConnector.WorkThread" $ "Incoming secinfo:" <> (T.pack . show) secInfo
|
||
|
|
-- TODO: Pass to qtis
|
||
|
|
TransaqResponseQuotations (ResponseQuotations quotations) -> do
|
||
|
|
log Debug "TXMLConnector.WorkThread" "Incoming quotations:"
|
||
|
|
forM_ quotations (log Debug "TXMLConnector.WorkThread" . (T.pack . show))
|
||
|
|
-- Pass to ticktable and quotesource server
|
||
|
|
TransaqResponseQuotes (ResponseQuotes quotes) -> do
|
||
|
|
log Debug "TXMLConnector.WorkThread" "Incoming quotes:"
|
||
|
|
forM_ quotes (log Debug "TXMLConnector.WorkThread" . (T.pack . show))
|
||
|
|
-- Pass to quotesource server
|
||
|
|
_ -> pure ()
|
||
|
|
handleUnconnected serverConnected = do
|
||
|
|
log Debug "TXMLConnector.WorkThread" "Sending connect command"
|
||
|
|
v <- sendCommand $
|
||
|
|
toXml $ CommandConnect
|
||
|
|
{
|
||
|
|
login = transaqLogin config,
|
||
|
|
password = transaqPassword config,
|
||
|
|
host = transaqHost config,
|
||
|
|
port = transaqPort config,
|
||
|
|
language = LanguageEn,
|
||
|
|
autopos = False,
|
||
|
|
micexRegisters = True,
|
||
|
|
milliseconds = True,
|
||
|
|
utcTime = True,
|
||
|
|
proxy = (),
|
||
|
|
rqDelay = Nothing,
|
||
|
|
sessionTimeout = Nothing,
|
||
|
|
requestTimeout = Nothing,
|
||
|
|
pushULimits = Nothing,
|
||
|
|
pushPosEquity = Nothing
|
||
|
|
}
|
||
|
|
case v of
|
||
|
|
Left err -> do
|
||
|
|
log Warning "TXMLConnector.WorkThread" $ "Unable to connect: [" <> err <> "]"
|
||
|
|
threadDelay (1000 * 1000 * 10)
|
||
|
|
Right _ -> do
|
||
|
|
atomically $ writeTVar serverConnected StageGetInfo
|
||
|
|
-- item <- atomically $ readTBQueue queue
|
||
|
|
-- case item of
|
||
|
|
-- TransaqResponseServerStatus status -> do
|
||
|
|
-- case state status of
|
||
|
|
-- Transaq.Error errmsg -> do
|
||
|
|
-- log Warning "TXMLConnector.WorkThread" $ "Unable to connect: " <> errmsg
|
||
|
|
-- void $ sendCommand $ toXml CommandDisconnect
|
||
|
|
-- threadDelay (10 * 1000 * 1000)
|
||
|
|
-- Transaq.Connected -> do
|
||
|
|
-- atomically $ writeTVar serverConnected StageGetInfo
|
||
|
|
-- -- v <- makeSubscriptions config
|
||
|
|
-- -- case v of
|
||
|
|
-- -- Left errmsg -> do
|
||
|
|
-- -- log Warning "TXMLConnector.WorkThread" $ "Unable to subscribe: " <> errmsg
|
||
|
|
-- -- void $ sendCommand $ toXml CommandDisconnect
|
||
|
|
-- -- Right _ ->
|
||
|
|
-- Transaq.Disconnected -> do
|
||
|
|
-- log Warning "TXMLConnector.WorkThread" "Unable to connect (disconnected)"
|
||
|
|
-- threadDelay (10 * 1000 * 1000)
|
||
|
|
-- other -> do
|
||
|
|
-- log Warning "TXMLConnector.WorkThread" $ "Stray message: " <> (T.pack . show) other
|
||
|
|
-- threadDelay (1000 * 1000)
|
||
|
|
makeSubscriptions config =
|
||
|
|
sendCommand $ toXml $
|
||
|
|
CommandSubscribe
|
||
|
|
{
|
||
|
|
alltrades = fmap subscriptionToSecurityId (allTradesSubscriptions config),
|
||
|
|
quotations = fmap subscriptionToSecurityId (quotationsSubscriptions config),
|
||
|
|
quotes = fmap subscriptionToSecurityId (quotesSubscriptions config)
|
||
|
|
}
|
||
|
|
subscriptionToSecurityId (SubscriptionConfig brd code) = SecurityId brd code
|
||
|
|
insertToTickMap tick = atomically $ modifyTVar' tickMap (M.insert (TickKey (security tick) (datatype tick)) tick)
|
||
|
|
|
||
|
|
allTradeToTick :: AllTradesTrade -> Tick
|
||
|
|
allTradeToTick att =
|
||
|
|
Tick
|
||
|
|
{
|
||
|
|
security = attBoard att <> "#" <> attSecCode att,
|
||
|
|
datatype = LastTradePrice,
|
||
|
|
timestamp = attTimestamp att,
|
||
|
|
value = fromDouble $ attPrice att,
|
||
|
|
volume = fromIntegral $ attQuantity att
|
||
|
|
}
|
||
|
|
|
||
|
|
quotationToTicks :: UTCTime -> Quotation -> [Tick]
|
||
|
|
quotationToTicks timestamp q =
|
||
|
|
let security = qBoard q <> "#" <> qSeccode q in
|
||
|
|
[
|
||
|
|
Tick
|
||
|
|
{
|
||
|
|
security = security,
|
||
|
|
datatype = BestBid,
|
||
|
|
timestamp = timestamp,
|
||
|
|
value = fromDouble $ qBid q,
|
||
|
|
volume = fromIntegral $ qQuantity q
|
||
|
|
},
|
||
|
|
Tick
|
||
|
|
{
|
||
|
|
security = security,
|
||
|
|
datatype = BestOffer,
|
||
|
|
timestamp = timestamp,
|
||
|
|
value = fromDouble $ qOffer q,
|
||
|
|
volume = fromIntegral $ qQuantity q
|
||
|
|
}]
|