|
|
|
@ -17,11 +17,12 @@ import Control.Exception |
|
|
|
import Control.Monad |
|
|
|
import Control.Monad |
|
|
|
import Control.Monad.Extra |
|
|
|
import Control.Monad.Extra |
|
|
|
import Control.Monad.IO.Class |
|
|
|
import Control.Monad.IO.Class |
|
|
|
import Control.Monad.Loops |
|
|
|
import Control.Monad.Loops (whileM_) |
|
|
|
import Data.Attoparsec.Text |
|
|
|
import Data.Binary |
|
|
|
|
|
|
|
import Data.Binary.Get |
|
|
|
import qualified Data.ByteString as B |
|
|
|
import qualified Data.ByteString as B |
|
|
|
|
|
|
|
import qualified Data.ByteString.Lazy as BL |
|
|
|
import Data.Conduit hiding (connect) |
|
|
|
import Data.Conduit hiding (connect) |
|
|
|
import Data.Conduit.Attoparsec |
|
|
|
|
|
|
|
import qualified Data.Conduit.List as CL |
|
|
|
import qualified Data.Conduit.List as CL |
|
|
|
import qualified Data.HashSet as HS |
|
|
|
import qualified Data.HashSet as HS |
|
|
|
import Data.IORef |
|
|
|
import Data.IORef |
|
|
|
@ -29,7 +30,6 @@ import qualified Data.Map.Strict as M |
|
|
|
import Data.Maybe |
|
|
|
import Data.Maybe |
|
|
|
import qualified Data.Text as T |
|
|
|
import qualified Data.Text as T |
|
|
|
import Data.Text.Encoding |
|
|
|
import Data.Text.Encoding |
|
|
|
import qualified Data.Text.Foreign as FT |
|
|
|
|
|
|
|
import Data.Time.Calendar |
|
|
|
import Data.Time.Calendar |
|
|
|
import Data.Time.Clock |
|
|
|
import Data.Time.Clock |
|
|
|
import Foreign.Marshal.Alloc |
|
|
|
import Foreign.Marshal.Alloc |
|
|
|
@ -45,144 +45,51 @@ data PipeReaderHandle = |
|
|
|
running :: IORef Bool |
|
|
|
running :: IORef Bool |
|
|
|
} deriving (Eq) |
|
|
|
} deriving (Eq) |
|
|
|
|
|
|
|
|
|
|
|
data DataLine = CurrentParamLine T.Text Double Integer Double Integer Integer Double Integer Integer |
|
|
|
|
|
|
|
| AllTradeLine T.Text Integer Double Integer UTCTime |
|
|
|
|
|
|
|
deriving (Show, Eq) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
yieldJust :: Maybe a -> Source IO a |
|
|
|
zmqSocketConduit :: (Subscriber a, Receiver a) => T.Text -> Socket a -> IORef Bool -> Source IO [B.ByteString] |
|
|
|
yieldJust maybeV = do -- Probably already present in some library |
|
|
|
zmqSocketConduit ep sock running' = do |
|
|
|
case maybeV of |
|
|
|
liftIO $ do |
|
|
|
Just v -> yield v |
|
|
|
debugM "PipeReader" $ "Connecting to: " ++ T.unpack ep |
|
|
|
Nothing -> return () |
|
|
|
connect sock (T.unpack ep) |
|
|
|
|
|
|
|
subscribe sock B.empty |
|
|
|
zmqSocketConduit :: (Receiver a) => Socket a -> Source IO T.Text |
|
|
|
lastHeartbeat <- liftIO $ getCurrentTime >>= newIORef |
|
|
|
zmqSocketConduit sock = do |
|
|
|
whileM_ (andM [notTimeout lastHeartbeat, liftIO (readIORef running')]) $ do |
|
|
|
maybeStr <- liftIO $ do |
|
|
|
evs <- liftIO $ poll 200 [Sock sock [In] Nothing] |
|
|
|
bs <- receive sock |
|
|
|
unless (null (head evs)) $ do |
|
|
|
case decodeUtf8' bs of |
|
|
|
bs <- liftIO $ receiveMulti sock |
|
|
|
Left _ -> return Nothing |
|
|
|
when ((not . null $ bs) && (head bs == "SYSTEM#HEARTBEAT")) $ liftIO $ getCurrentTime >>= writeIORef lastHeartbeat |
|
|
|
Right v -> return (Just v) |
|
|
|
yield bs |
|
|
|
yieldJust maybeStr |
|
|
|
zmqSocketConduit ep sock running' |
|
|
|
zmqSocketConduit sock |
|
|
|
where |
|
|
|
|
|
|
|
notTimeout hb = do |
|
|
|
line2TickConduit :: Conduit DataLine IO Tick |
|
|
|
now <- liftIO $ getCurrentTime |
|
|
|
line2TickConduit = do |
|
|
|
last <- liftIO $ readIORef hb |
|
|
|
volumeMap <- liftIO $ newIORef M.empty |
|
|
|
return $ now `diffUTCTime` last < 10 |
|
|
|
ignoreCPSet <- liftIO $ newIORef HS.empty |
|
|
|
|
|
|
|
lastTimestamp <- liftIO $ newIORef $ UTCTime (fromGregorian 1970 1 1) 0 |
|
|
|
|
|
|
|
awaitForever $ \line -> |
|
|
|
|
|
|
|
case line of |
|
|
|
|
|
|
|
CurrentParamLine tickerId last voltoday bid biddepth biddeptht offer offerdepth offerdeptht -> do |
|
|
|
|
|
|
|
ts <- liftIO $ readIORef lastTimestamp |
|
|
|
|
|
|
|
yieldTick tickerId BestBid ts (fromDouble bid) biddepth |
|
|
|
|
|
|
|
yieldTick tickerId BestOffer ts (fromDouble offer) offerdepth |
|
|
|
|
|
|
|
yieldTick tickerId TotalSupply ts (fromInteger offerdeptht) 0 |
|
|
|
|
|
|
|
yieldTick tickerId TotalDemand ts (fromInteger biddeptht) 0 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shouldParsePrice <- liftIO $ HS.member tickerId <$> readIORef ignoreCPSet |
|
|
|
|
|
|
|
when shouldParsePrice $ do |
|
|
|
|
|
|
|
m <- liftIO $ readIORef volumeMap |
|
|
|
|
|
|
|
case M.lookup tickerId m of |
|
|
|
|
|
|
|
Just vol -> |
|
|
|
|
|
|
|
if | vol < voltoday -> yieldTick tickerId LastTradePrice ts (fromDouble last) (voltoday - vol) |
|
|
|
|
|
|
|
| vol > voltoday -> yieldTick tickerId LastTradePrice ts (fromDouble last) vol |
|
|
|
|
|
|
|
| otherwise -> return () |
|
|
|
|
|
|
|
Nothing -> yieldTick tickerId LastTradePrice ts (fromDouble last) 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
liftIO $ atomicModifyIORef' volumeMap (\m -> (M.insert tickerId voltoday m, ())) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AllTradeLine tickerId flags price volume ts -> do |
|
|
|
parseBarConduit :: Conduit [B.ByteString] IO (TickerId, BarTimeframe, Bar) |
|
|
|
liftIO $ writeIORef lastTimestamp ts |
|
|
|
parseBarConduit = awaitForever $ \bs -> |
|
|
|
if |
|
|
|
case deserializeBar (BL.fromStrict <$> bs) of |
|
|
|
| flags == 1 -> yieldTick tickerId LastTradePrice ts (fromDouble price) volume |
|
|
|
Just (tf, bar) -> yield (barSecurity bar, tf, bar) |
|
|
|
| flags == 2 -> yieldTick tickerId LastTradePrice ts (fromDouble price) volume |
|
|
|
_ -> return () |
|
|
|
| otherwise -> return () |
|
|
|
|
|
|
|
liftIO $ atomicModifyIORef' ignoreCPSet (\s -> (HS.insert tickerId s, ())) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
where |
|
|
|
qssdataConduit :: Conduit (TickerId, BarTimeframe, Bar) IO QuoteSourceServerData |
|
|
|
yieldTick tickerId dtype ts val vol = |
|
|
|
qssdataConduit = awaitForever $ \(tid, tf, bar) -> yield $ QSSBar (tf, bar) |
|
|
|
yield $ Tick { security = tickerId, |
|
|
|
|
|
|
|
datatype = dtype, |
|
|
|
|
|
|
|
timestamp = ts, |
|
|
|
|
|
|
|
value = val, |
|
|
|
|
|
|
|
volume = vol } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chanSink :: BoundedChan QuoteSourceServerData -> Sink Tick IO () |
|
|
|
chanSink :: (Show a) => BoundedChan a -> Sink a IO () |
|
|
|
chanSink chan = awaitForever |
|
|
|
chanSink chan = awaitForever |
|
|
|
(\t -> liftIO $ do |
|
|
|
(\t -> do |
|
|
|
writeChan chan (QSSTick t)) |
|
|
|
liftIO $ writeChan chan t) |
|
|
|
|
|
|
|
|
|
|
|
startPipeReader :: Context -> T.Text -> BoundedChan QuoteSourceServerData -> IO PipeReaderHandle |
|
|
|
startPipeReader :: Context -> T.Text -> BoundedChan QuoteSourceServerData -> IO PipeReaderHandle |
|
|
|
startPipeReader ctx pipeEndpoint tickChan = do |
|
|
|
startPipeReader ctx pipeEndpoint tickChan = do |
|
|
|
debugM "PipeReader" $ "Trying to open pipe: " ++ T.unpack pipeEndpoint |
|
|
|
debugM "PipeReader" $ "Trying to open pipe: " ++ T.unpack pipeEndpoint |
|
|
|
s <- socket ctx Sub |
|
|
|
s <- socket ctx Sub |
|
|
|
connect s (T.unpack pipeEndpoint) |
|
|
|
|
|
|
|
subscribe s B.empty |
|
|
|
|
|
|
|
debugM "PipeReader" "Pipe opened" |
|
|
|
debugM "PipeReader" "Pipe opened" |
|
|
|
running' <- newIORef True |
|
|
|
running' <- newIORef True |
|
|
|
tid <- forkIO $ readerThread s running' |
|
|
|
tid <- forkIO $ readerThread s running' |
|
|
|
return PipeReaderHandle { prThreadId = tid, running = running' } |
|
|
|
return PipeReaderHandle { prThreadId = tid, running = running' } |
|
|
|
where |
|
|
|
where |
|
|
|
readerThread s running' = runConduit $ (zmqSocketConduit s) =$= conduitParserEither parseTrade =$= handleParseResult =$= line2TickConduit =$= chanSink tickChan |
|
|
|
readerThread s running' = runConduit $ (zmqSocketConduit pipeEndpoint s running') =$= parseBarConduit =$= qssdataConduit =$= chanSink tickChan |
|
|
|
parseTrade = parseCurrentParam <|> parseAllTrade |
|
|
|
|
|
|
|
parseCurrentParam = do |
|
|
|
|
|
|
|
string "CT:" |
|
|
|
|
|
|
|
secName <- takeTill (== ':') |
|
|
|
|
|
|
|
string ":" |
|
|
|
|
|
|
|
last <- double |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
voltoday <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
bid <- double |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
biddepth <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
biddeptht <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
offer <- double |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
offerdepth <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
offerdeptht <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
return $ CurrentParamLine secName last voltoday bid biddepth biddeptht offer offerdepth offerdeptht |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parseAllTrade = do |
|
|
|
|
|
|
|
string "AT:" |
|
|
|
|
|
|
|
secName <- takeTill (== ':') |
|
|
|
|
|
|
|
string ":" |
|
|
|
|
|
|
|
flags <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
price <- double |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
qty <- decimal |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
dt <- parseDateTime |
|
|
|
|
|
|
|
string ";" |
|
|
|
|
|
|
|
return $ AllTradeLine secName flags price qty dt |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
parseDateTime = do |
|
|
|
|
|
|
|
y <- decimal |
|
|
|
|
|
|
|
string "." |
|
|
|
|
|
|
|
mon <- decimal |
|
|
|
|
|
|
|
string "." |
|
|
|
|
|
|
|
day <- decimal |
|
|
|
|
|
|
|
string " " |
|
|
|
|
|
|
|
h <- fromInteger <$> decimal |
|
|
|
|
|
|
|
string ":" |
|
|
|
|
|
|
|
m <- fromInteger <$> decimal |
|
|
|
|
|
|
|
string ":" |
|
|
|
|
|
|
|
s <- fromInteger <$> decimal |
|
|
|
|
|
|
|
string "." |
|
|
|
|
|
|
|
ms <- fromInteger <$> decimal |
|
|
|
|
|
|
|
return $ UTCTime (fromGregorian y mon day) $ h * 3600 + m * 60 + s + ms / 1000 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleParseResult = do |
|
|
|
|
|
|
|
awaitForever $ \res -> |
|
|
|
|
|
|
|
case res of |
|
|
|
|
|
|
|
Left err -> liftIO $ warningM "PipeReader" $ "Can't parse: " ++ show err |
|
|
|
|
|
|
|
Right (_, r) -> yield r |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stopPipeReader :: PipeReaderHandle -> IO () |
|
|
|
stopPipeReader :: PipeReaderHandle -> IO () |
|
|
|
stopPipeReader h = killThread (prThreadId h) >> writeIORef (running h) False |
|
|
|
stopPipeReader h = killThread (prThreadId h) >> writeIORef (running h) False |
|
|
|
|