|
|
|
@ -1,9 +1,12 @@ |
|
|
|
|
|
|
|
{-# LANGUAGE LambdaCase #-} |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
|
|
|
|
{-# LANGUAGE TupleSections #-} |
|
|
|
|
|
|
|
|
|
|
|
module ATrade.QuoteSource.Client ( |
|
|
|
module ATrade.QuoteSource.Client ( |
|
|
|
QuoteData(..), |
|
|
|
QuoteData(..), |
|
|
|
startQuoteSourceClient, |
|
|
|
startQuoteSourceClient, |
|
|
|
stopQuoteSourceClient |
|
|
|
stopQuoteSourceClient, |
|
|
|
|
|
|
|
quoteSourceClientSubscribe |
|
|
|
) where |
|
|
|
) where |
|
|
|
|
|
|
|
|
|
|
|
import ATrade.Types |
|
|
|
import ATrade.Types |
|
|
|
@ -20,6 +23,7 @@ import Data.IORef |
|
|
|
import qualified Data.List as L |
|
|
|
import qualified Data.List as L |
|
|
|
import Data.List.NonEmpty |
|
|
|
import Data.List.NonEmpty |
|
|
|
import Data.Maybe |
|
|
|
import Data.Maybe |
|
|
|
|
|
|
|
import qualified Data.Set as S |
|
|
|
import qualified Data.Text as T |
|
|
|
import qualified Data.Text as T |
|
|
|
import Data.Text.Encoding |
|
|
|
import Data.Text.Encoding |
|
|
|
import Data.Time.Clock |
|
|
|
import Data.Time.Clock |
|
|
|
@ -29,10 +33,14 @@ import System.ZMQ4.ZAP |
|
|
|
|
|
|
|
|
|
|
|
import Safe |
|
|
|
import Safe |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data QSSClientMessage = QSSSubscribe [(TickerId, BarTimeframe)] | QSSUnsubscribe [(TickerId, BarTimeframe)] |
|
|
|
|
|
|
|
|
|
|
|
data QuoteSourceClientHandle = QuoteSourceClientHandle { |
|
|
|
data QuoteSourceClientHandle = QuoteSourceClientHandle { |
|
|
|
tid :: ThreadId, |
|
|
|
tid :: ThreadId, |
|
|
|
completionMvar :: MVar (), |
|
|
|
completionMvar :: MVar (), |
|
|
|
killMVar :: MVar () |
|
|
|
killMVar :: MVar (), |
|
|
|
|
|
|
|
messageBox :: BoundedChan QSSClientMessage, |
|
|
|
|
|
|
|
subscriptions :: IORef (S.Set (TickerId, BarTimeframe)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
data QuoteData = QDTick Tick | QDBar (BarTimeframe, Bar) |
|
|
|
data QuoteData = QDTick Tick | QDBar (BarTimeframe, Bar) |
|
|
|
@ -51,12 +59,14 @@ startQuoteSourceClient :: BoundedChan QuoteData -> [T.Text] -> Context -> T.Text |
|
|
|
startQuoteSourceClient chan tickers ctx endpoint csp = do |
|
|
|
startQuoteSourceClient chan tickers ctx endpoint csp = do |
|
|
|
compMv <- newEmptyMVar |
|
|
|
compMv <- newEmptyMVar |
|
|
|
killMv <- newEmptyMVar |
|
|
|
killMv <- newEmptyMVar |
|
|
|
|
|
|
|
msgbox <- newBoundedChan 500 |
|
|
|
|
|
|
|
subs <- newIORef $ S.fromList $ fmap (\x -> (x, BarTimeframe 0)) tickers |
|
|
|
now <- getCurrentTime |
|
|
|
now <- getCurrentTime |
|
|
|
lastHeartbeat <- newIORef now |
|
|
|
lastHeartbeat <- newIORef now |
|
|
|
tid <- forkIO $ finally (clientThread lastHeartbeat killMv) (cleanup compMv) |
|
|
|
tid <- forkIO $ finally (clientThread lastHeartbeat killMv msgbox subs) (cleanup compMv) |
|
|
|
return QuoteSourceClientHandle { tid = tid, completionMvar = compMv, killMVar = killMv } |
|
|
|
return QuoteSourceClientHandle { tid = tid, completionMvar = compMv, killMVar = killMv, messageBox = msgbox, subscriptions = subs } |
|
|
|
where |
|
|
|
where |
|
|
|
clientThread lastHeartbeat killMv = whileM_ (isNothing <$> tryReadMVar killMv) $ withSocket ctx Sub (\sock -> do |
|
|
|
clientThread lastHeartbeat killMv msgbox subs = whileM_ (isNothing <$> tryReadMVar killMv) $ withSocket ctx Sub (\sock -> do |
|
|
|
setLinger (restrict 0) sock |
|
|
|
setLinger (restrict 0) sock |
|
|
|
debugM "QuoteSource.Client" $ "Client security parameters: " ++ show csp |
|
|
|
debugM "QuoteSource.Client" $ "Client security parameters: " ++ show csp |
|
|
|
case (cspCertificate csp, cspServerCertificate csp) of |
|
|
|
case (cspCertificate csp, cspServerCertificate csp) of |
|
|
|
@ -65,14 +75,15 @@ startQuoteSourceClient chan tickers ctx endpoint csp = do |
|
|
|
zapSetServerCertificate serverCert sock |
|
|
|
zapSetServerCertificate serverCert sock |
|
|
|
_ -> return () |
|
|
|
_ -> return () |
|
|
|
connect sock $ T.unpack endpoint |
|
|
|
connect sock $ T.unpack endpoint |
|
|
|
debugM "QuoteSource.Client" $ "Tickers: " ++ show tickers |
|
|
|
subslist <- readIORef subs |
|
|
|
mapM_ (subscribe sock . encodeUtf8) tickers |
|
|
|
debugM "QuoteSource.Client" $ "Tickers: " ++ show subslist |
|
|
|
|
|
|
|
mapM_ (subscribe sock . encodeUtf8 . mkSubCode) subslist |
|
|
|
subscribe sock $ B8.pack "SYSTEM#HEARTBEAT" |
|
|
|
subscribe sock $ B8.pack "SYSTEM#HEARTBEAT" |
|
|
|
|
|
|
|
|
|
|
|
now <- getCurrentTime |
|
|
|
now <- getCurrentTime |
|
|
|
writeIORef lastHeartbeat now |
|
|
|
writeIORef lastHeartbeat now |
|
|
|
whileM_ (andM [notTimeout lastHeartbeat, isNothing <$> tryReadMVar killMv]) $ do |
|
|
|
whileM_ (andM [notTimeout lastHeartbeat, isNothing <$> tryReadMVar killMv]) $ do |
|
|
|
evs <- poll 200 [Sock sock [In] Nothing] |
|
|
|
evs <- poll 50 [Sock sock [In] Nothing] |
|
|
|
unless (null (L.head evs)) $ do |
|
|
|
unless (null (L.head evs)) $ do |
|
|
|
rawTick <- fmap BL.fromStrict <$> receiveMulti sock |
|
|
|
rawTick <- fmap BL.fromStrict <$> receiveMulti sock |
|
|
|
now <- getCurrentTime |
|
|
|
now <- getCurrentTime |
|
|
|
@ -82,6 +93,11 @@ startQuoteSourceClient chan tickers ctx endpoint csp = do |
|
|
|
else case deserializeBar rawTick of |
|
|
|
else case deserializeBar rawTick of |
|
|
|
Just (tf, bar) -> writeChan chan $ QDBar (tf, bar) |
|
|
|
Just (tf, bar) -> writeChan chan $ QDBar (tf, bar) |
|
|
|
_ -> writeList2Chan chan (deserializeTicks rawTick) |
|
|
|
_ -> writeList2Chan chan (deserializeTicks rawTick) |
|
|
|
|
|
|
|
whileJust (tryReadChan msgbox) $ \case |
|
|
|
|
|
|
|
QSSSubscribe tickers -> do |
|
|
|
|
|
|
|
atomicModifyIORef' subs (\x -> (foldr S.insert x tickers, ())) |
|
|
|
|
|
|
|
mapM_ (subscribe sock . encodeUtf8 . mkSubCode) tickers |
|
|
|
|
|
|
|
_ -> return () |
|
|
|
debugM "QuoteSource.Client" "Heartbeat timeout") |
|
|
|
debugM "QuoteSource.Client" "Heartbeat timeout") |
|
|
|
|
|
|
|
|
|
|
|
notTimeout ts = do |
|
|
|
notTimeout ts = do |
|
|
|
@ -91,5 +107,11 @@ startQuoteSourceClient chan tickers ctx endpoint csp = do |
|
|
|
|
|
|
|
|
|
|
|
cleanup compMv = putMVar compMv () |
|
|
|
cleanup compMv = putMVar compMv () |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mkSubCode (tid, BarTimeframe tf) = |
|
|
|
|
|
|
|
if tf == 0 then tid else tid <> ":" <> T.pack (show tf) <> ";" |
|
|
|
|
|
|
|
|
|
|
|
stopQuoteSourceClient :: QuoteSourceClientHandle -> IO () |
|
|
|
stopQuoteSourceClient :: QuoteSourceClientHandle -> IO () |
|
|
|
stopQuoteSourceClient handle = yield >> putMVar (killMVar handle) () >> readMVar (completionMvar handle) |
|
|
|
stopQuoteSourceClient handle = yield >> putMVar (killMVar handle) () >> readMVar (completionMvar handle) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
quoteSourceClientSubscribe :: QuoteSourceClientHandle -> [(TickerId, BarTimeframe)] -> IO () |
|
|
|
|
|
|
|
quoteSourceClientSubscribe handle tickers = writeChan (messageBox handle) (QSSSubscribe tickers) |
|
|
|
|