Execution layer for algorithmic trading
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.

241 lines
11 KiB

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
module ATrade.Driver.Junction.QuoteThread
(
QuoteThreadHandle,
startQuoteThread,
stopQuoteThread,
addSubscription,
DownloaderM,
DownloaderEnv(..),
runDownloaderM,
withQThread
) where
import ATrade.Driver.Junction.ProgramConfiguration (ProgramConfiguration (..))
import ATrade.Driver.Junction.QuoteStream (QuoteSubscription (..))
import ATrade.Logging (Message)
import ATrade.Quotes.HistoryProvider (HistoryProvider (..))
import ATrade.Quotes.QHP (QHPHandle, requestHistoryFromQHP)
import ATrade.Quotes.QTIS (TickerInfo (tiLotSize, tiTickSize, tiTicker),
qtisGetTickersInfo)
import ATrade.Quotes.TickerInfoProvider (TickerInfoProvider (..))
import ATrade.QuoteSource.Client (QuoteData (QDBar, QDTick),
QuoteSourceClientHandle,
quoteSourceClientSubscribe,
startQuoteSourceClient,
stopQuoteSourceClient)
import ATrade.RoboCom.Types (Bar (barSecurity),
BarSeries (..),
BarSeriesId (BarSeriesId),
Bars,
InstrumentParameters (InstrumentParameters),
TickerInfoMap)
import ATrade.Types (BarTimeframe (BarTimeframe),
ClientSecurityParams (ClientSecurityParams),
Tick (security),
TickerId)
import Colog (HasLog (getLogAction, setLogAction),
LogAction,
WithLog)
import Control.Concurrent (ThreadId, forkIO,
killThread)
import Control.Concurrent.BoundedChan (BoundedChan,
newBoundedChan,
readChan,
writeChan)
import Control.Exception.Safe (MonadMask,
MonadThrow,
bracket)
import Control.Monad (forM, forever)
import Control.Monad.Reader (MonadIO (liftIO), ReaderT (runReaderT),
lift)
import Control.Monad.Reader.Class (MonadReader, asks)
import qualified Data.HashMap.Strict as HM
import Data.IORef (IORef,
atomicModifyIORef',
newIORef,
readIORef)
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Data.Time (addUTCTime,
getCurrentTime)
import System.ZMQ4 (Context)
import System.ZMQ4.ZAP (loadCertificateFromFile)
data QuoteThreadHandle = QuoteThreadHandle ThreadId ThreadId QuoteThreadEnv
data QuoteThreadEnv =
QuoteThreadEnv
{
bars :: IORef Bars,
endpoints :: IORef (HM.HashMap QuoteSubscription [BoundedChan QuoteData]),
qsclient :: QuoteSourceClientHandle,
paramsCache :: IORef TickerInfoMap,
downloaderChan :: BoundedChan QuoteSubscription
}
startQuoteThread :: (MonadIO m,
MonadIO m1,
WithLog DownloaderEnv Message m1,
HistoryProvider m1,
TickerInfoProvider m1) =>
IORef Bars ->
IORef TickerInfoMap ->
Context ->
T.Text ->
ClientSecurityParams ->
(m1 () -> IO ()) ->
LogAction IO Message ->
m QuoteThreadHandle
startQuoteThread barsRef tiRef ctx ep secparams downloadThreadRunner logger = do
chan <- liftIO $ newBoundedChan 2000
dChan <- liftIO $ newBoundedChan 2000
qsc <- liftIO $ startQuoteSourceClient chan [] ctx ep secparams logger
env <- liftIO $ QuoteThreadEnv barsRef <$> newIORef HM.empty <*> pure qsc <*> pure tiRef <*> pure dChan
tid <- liftIO . forkIO $ quoteThread env chan
downloaderTid <- liftIO . forkIO $ downloadThreadRunner (downloaderThread env dChan)
return $ QuoteThreadHandle tid downloaderTid env
where
downloaderThread env chan = forever $ do
QuoteSubscription tickerid tf <- liftIO $ readChan chan
paramsMap <- liftIO $ readIORef $ paramsCache env
mbParams <- case M.lookup tickerid paramsMap of
Nothing -> do
paramsList <- getInstrumentParameters [tickerid]
case paramsList of
(params:_) -> liftIO $ atomicModifyIORef' (paramsCache env) (\m -> (M.insert tickerid params m, Just params))
_ -> return Nothing
Just params -> return $ Just params
barsMap <- liftIO $ readIORef (bars env)
case M.lookup (BarSeriesId tickerid tf) barsMap of
Just _ -> return () -- already downloaded
Nothing -> case mbParams of
Just params -> do
now <- liftIO getCurrentTime
-- Load data in interval [today - 60days; today + 1day]. +1 day guarantees that we will download data up until current time.
-- If we don't make this adjustment it is possible that we will get data only up to beginning of current day.
barsData <- getHistory tickerid tf ((-86400 * 60) `addUTCTime` now) (86400 `addUTCTime` now)
let barSeries = BarSeries tickerid tf barsData params
liftIO $ atomicModifyIORef' (bars env) (\m -> (M.insert (BarSeriesId tickerid tf) barSeries m, ()))
_ -> return () -- TODO log
quoteThread env chan = flip runReaderT env $ forever $ do
qssData <- lift $ readChan chan
case qssData of
QDBar (tf, bar) -> do
barsRef' <- asks bars
lift $ atomicModifyIORef' barsRef' (\x -> (updateBarsMap x bar tf, ()))
_ -> return () -- TODO pass to bar aggregator
let key = case qssData of
QDTick tick -> QuoteSubscription (security tick) (BarTimeframe 0)
QDBar (tf, bar) -> QuoteSubscription (barSecurity bar) tf
subs <- asks endpoints >>= (lift . readIORef)
case HM.lookup key subs of
Just clientChannels -> do
lift $ mapM_ (`writeChan` qssData) clientChannels
Nothing -> return ()
stopQuoteThread :: (MonadIO m) => QuoteThreadHandle -> m ()
stopQuoteThread (QuoteThreadHandle tid dtid env) = liftIO $ do
killThread tid
killThread dtid
stopQuoteSourceClient (qsclient env)
addSubscription :: (MonadIO m) => QuoteThreadHandle -> TickerId -> BarTimeframe -> BoundedChan QuoteData -> m ()
addSubscription (QuoteThreadHandle _ _ env) tid tf chan = liftIO $ do
writeChan (downloaderChan env) (QuoteSubscription tid tf)
atomicModifyIORef' (endpoints env) (\m -> (doAddSubscription m tid, ()))
quoteSourceClientSubscribe (qsclient env) [(tid, BarTimeframe 0)]
where
doAddSubscription m tickerid =
let m1 = HM.alter (\case
Just chans -> Just (chan : chans)
_ -> Just [chan]) (QuoteSubscription tickerid tf) m in
HM.alter (\case
Just chans -> Just (chan : chans)
_ -> Just [chan]) (QuoteSubscription tickerid (BarTimeframe 0)) m1
updateBarsMap :: Bars -> Bar -> BarTimeframe -> Bars
updateBarsMap barsMap bar tf = M.adjust (addToSeries bar) (BarSeriesId (barSecurity bar) tf) barsMap
addToSeries :: Bar -> BarSeries -> BarSeries
addToSeries bar series = series { bsBars = bar : bsBars series }
data DownloaderEnv =
DownloaderEnv
{
qhp :: QHPHandle,
downloaderContext :: Context,
downloaderQtisEndpoint :: T.Text,
logAction :: LogAction DownloaderM Message
}
newtype DownloaderM a = DownloaderM { unDownloaderM :: ReaderT DownloaderEnv IO a }
deriving (Functor, Applicative, Monad, MonadReader DownloaderEnv, MonadIO, MonadThrow)
instance HasLog DownloaderEnv Message DownloaderM where
getLogAction = logAction
setLogAction a e = e { logAction = a }
instance HistoryProvider DownloaderM where
getHistory tid tf from to = do
q <- asks qhp
requestHistoryFromQHP q tid tf from to
instance TickerInfoProvider DownloaderM where
getInstrumentParameters tickers = do
ctx <- asks downloaderContext
ep <- asks downloaderQtisEndpoint
tis <- forM tickers (qtisGetTickersInfo ctx ep)
pure $ convert `fmap` tis
where
convert ti = InstrumentParameters
(tiTicker ti)
(fromInteger $ tiLotSize ti)
(tiTickSize ti)
withQThread ::
DownloaderEnv
-> IORef Bars
-> IORef TickerInfoMap
-> ProgramConfiguration
-> Context
-> LogAction IO Message
-> (QuoteThreadHandle -> IO ())
-> IO ()
withQThread env barsMap tiMap cfg ctx logger f = do
securityParameters <- loadSecurityParameters
bracket
(startQuoteThread
barsMap
tiMap
ctx
(quotesourceEndpoint cfg)
securityParameters
(runDownloaderM env)
logger)
stopQuoteThread f
where
loadSecurityParameters =
case (quotesourceClientCert cfg, quotesourceServerCert cfg) of
(Just clientCertPath, Just serverCertPath) -> do
eClientCert <- loadCertificateFromFile clientCertPath
eServerCert <- loadCertificateFromFile serverCertPath
case (eClientCert, eServerCert) of
(Right clientCert, Right serverCert) -> return $ ClientSecurityParams (Just clientCert) (Just serverCert)
(_, _) -> return $ ClientSecurityParams Nothing Nothing
_ -> return $ ClientSecurityParams Nothing Nothing
runDownloaderM :: DownloaderEnv -> DownloaderM () -> IO ()
runDownloaderM env = (`runReaderT` env) . unDownloaderM