|
|
|
@ -1,5 +1,8 @@ |
|
|
|
{-# LANGUAGE DeriveGeneric #-} |
|
|
|
{-# LANGUAGE DeriveGeneric #-} |
|
|
|
{-# LANGUAGE DuplicateRecordFields #-} |
|
|
|
{-# LANGUAGE DuplicateRecordFields #-} |
|
|
|
|
|
|
|
{-# LANGUAGE FlexibleInstances #-} |
|
|
|
|
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-} |
|
|
|
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-} |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
|
|
|
|
|
|
|
|
module ATrade.Driver.Junction |
|
|
|
module ATrade.Driver.Junction |
|
|
|
@ -7,119 +10,173 @@ module ATrade.Driver.Junction |
|
|
|
junctionMain |
|
|
|
junctionMain |
|
|
|
) where |
|
|
|
) where |
|
|
|
|
|
|
|
|
|
|
|
import ATrade.Driver.Junction.Types (StrategyDescriptor (..), |
|
|
|
import ATrade.Broker.Client (startBrokerClient, |
|
|
|
StrategyInstance (..), |
|
|
|
stopBrokerClient) |
|
|
|
StrategyInstanceDescriptor (..)) |
|
|
|
import ATrade.Driver.Junction.ProgramConfiguration (ProgramConfiguration (brokerEndpoint, brokerNotificationEndpoint, instances, qhpEndpoint, qtisEndpoint, redisSocket, robotsConfigsPath), |
|
|
|
import ATrade.RoboCom.Types (Ticker (..)) |
|
|
|
ProgramOptions (ProgramOptions, configPath)) |
|
|
|
import Control.Concurrent (forkIO) |
|
|
|
import ATrade.Driver.Junction.QuoteStream (QuoteStream (addSubscription, removeSubscription)) |
|
|
|
import Control.Concurrent.Async (forConcurrently_) |
|
|
|
import ATrade.Driver.Junction.QuoteThread (DownloaderEnv (DownloaderEnv), |
|
|
|
import Control.Concurrent.STM (atomically) |
|
|
|
withQThread) |
|
|
|
import Control.Concurrent.STM.TQueue (newTQueueIO) |
|
|
|
import ATrade.Driver.Junction.RobotDriverThread (createRobotDriverThread) |
|
|
|
import Control.Concurrent.STM.TVar (newTVarIO) |
|
|
|
import ATrade.Driver.Junction.Types (StrategyDescriptorE (StrategyDescriptorE), |
|
|
|
import Data.Aeson (FromJSON (..), ToJSON (..), |
|
|
|
StrategyInstanceDescriptor (..), |
|
|
|
decode, object, withObject, |
|
|
|
confStrategy) |
|
|
|
(.:), (.=)) |
|
|
|
import ATrade.Quotes.QHP (mkQHPHandle) |
|
|
|
import qualified Data.ByteString as B |
|
|
|
import ATrade.RoboCom.ConfigStorage (ConfigStorage (loadConfig)) |
|
|
|
|
|
|
|
import ATrade.RoboCom.Monad (MonadRobot (..)) |
|
|
|
|
|
|
|
import ATrade.RoboCom.Persistence (MonadPersistence (loadState, saveState)) |
|
|
|
|
|
|
|
import ATrade.Types (ClientSecurityParams (ClientSecurityParams)) |
|
|
|
|
|
|
|
import Control.Exception.Safe (MonadThrow, |
|
|
|
|
|
|
|
bracket) |
|
|
|
|
|
|
|
import Control.Monad (forM_) |
|
|
|
|
|
|
|
import Control.Monad.IO.Class (MonadIO (liftIO)) |
|
|
|
|
|
|
|
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT), |
|
|
|
|
|
|
|
asks) |
|
|
|
|
|
|
|
import Data.Aeson (eitherDecode, |
|
|
|
|
|
|
|
encode) |
|
|
|
import qualified Data.ByteString.Lazy as BL |
|
|
|
import qualified Data.ByteString.Lazy as BL |
|
|
|
import Data.IORef |
|
|
|
import Data.Default (Default (def)) |
|
|
|
|
|
|
|
import Data.IORef (IORef, newIORef) |
|
|
|
import qualified Data.Map.Strict as M |
|
|
|
import qualified Data.Map.Strict as M |
|
|
|
import Data.Maybe (fromMaybe) |
|
|
|
|
|
|
|
import Data.Semigroup ((<>)) |
|
|
|
|
|
|
|
import qualified Data.Text as T |
|
|
|
import qualified Data.Text as T |
|
|
|
|
|
|
|
import Data.Text.Encoding (encodeUtf8) |
|
|
|
import Data.Text.IO (readFile) |
|
|
|
import Data.Text.IO (readFile) |
|
|
|
import Dhall (FromDhall, auto, input) |
|
|
|
import Data.Time.Clock.POSIX (getPOSIXTime) |
|
|
|
import GHC.Generics (Generic) |
|
|
|
import Database.Redis (ConnectInfo (..), |
|
|
|
import Options.Applicative (Parser, execParser, fullDesc, |
|
|
|
Connection, |
|
|
|
header, help, helper, info, |
|
|
|
PortID (UnixSocket), |
|
|
|
long, metavar, progDesc, short, |
|
|
|
checkedConnect, |
|
|
|
strOption, (<**>)) |
|
|
|
defaultConnectInfo, |
|
|
|
|
|
|
|
get, mset, |
|
|
|
|
|
|
|
runRedis) |
|
|
|
|
|
|
|
import Dhall (auto, input) |
|
|
|
|
|
|
|
import Options.Applicative (Parser, |
|
|
|
|
|
|
|
execParser, |
|
|
|
|
|
|
|
fullDesc, header, |
|
|
|
|
|
|
|
help, helper, |
|
|
|
|
|
|
|
info, long, |
|
|
|
|
|
|
|
metavar, progDesc, |
|
|
|
|
|
|
|
short, strOption, |
|
|
|
|
|
|
|
(<**>)) |
|
|
|
import Prelude hiding (readFile) |
|
|
|
import Prelude hiding (readFile) |
|
|
|
|
|
|
|
import System.Log.Logger (warningM) |
|
|
|
|
|
|
|
import System.ZMQ4 (withContext) |
|
|
|
|
|
|
|
|
|
|
|
data BigConfig c = BigConfig { |
|
|
|
data PersistenceEnv = |
|
|
|
confTickers :: [Ticker], |
|
|
|
PersistenceEnv |
|
|
|
confStrategy :: c |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance (FromJSON c) => FromJSON (BigConfig c) where |
|
|
|
|
|
|
|
parseJSON = withObject "object" (\obj -> BigConfig <$> |
|
|
|
|
|
|
|
obj .: "tickers" <*> |
|
|
|
|
|
|
|
obj .: "params") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance (ToJSON c) => ToJSON (BigConfig c) where |
|
|
|
|
|
|
|
toJSON conf = object ["tickers" .= confTickers conf, |
|
|
|
|
|
|
|
"params" .= confStrategy conf ] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data ProgramOptions = |
|
|
|
|
|
|
|
ProgramOptions |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
configPath :: FilePath |
|
|
|
peRedisSocket :: Connection, |
|
|
|
|
|
|
|
peConfigPath :: FilePath |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
data ProgramConfiguration = |
|
|
|
newtype PersistenceT a = PersistenceT { unPersistenceT :: ReaderT PersistenceEnv IO a } |
|
|
|
ProgramConfiguration |
|
|
|
deriving (Functor, Applicative, Monad, MonadReader PersistenceEnv, MonadIO, MonadThrow) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance ConfigStorage PersistenceT where |
|
|
|
|
|
|
|
loadConfig key = do |
|
|
|
|
|
|
|
basePath <- asks peConfigPath |
|
|
|
|
|
|
|
let path = basePath <> "/" <> T.unpack key -- TODO fix path construction |
|
|
|
|
|
|
|
liftIO $ readFile path >>= input auto |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance MonadPersistence PersistenceT where |
|
|
|
|
|
|
|
saveState newState key = do |
|
|
|
|
|
|
|
conn <- asks peRedisSocket |
|
|
|
|
|
|
|
now <- liftIO getPOSIXTime |
|
|
|
|
|
|
|
res <- liftIO $ runRedis conn $ mset [(encodeUtf8 key, BL.toStrict $ encode newState), |
|
|
|
|
|
|
|
(encodeUtf8 (key <> ":last_store") , encodeUtf8 . T.pack . show $ now)] |
|
|
|
|
|
|
|
case res of |
|
|
|
|
|
|
|
Left _ -> liftIO $ warningM "main" "Unable to save state" |
|
|
|
|
|
|
|
Right _ -> return () |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadState key = do |
|
|
|
|
|
|
|
conn <- asks peRedisSocket |
|
|
|
|
|
|
|
res <- liftIO $ runRedis conn $ get (encodeUtf8 key) |
|
|
|
|
|
|
|
-- TODO: just chain eithers |
|
|
|
|
|
|
|
case res of |
|
|
|
|
|
|
|
Left _ -> do |
|
|
|
|
|
|
|
liftIO $ warningM "main" "Unable to load state" |
|
|
|
|
|
|
|
return def |
|
|
|
|
|
|
|
Right maybeRawState -> |
|
|
|
|
|
|
|
case maybeRawState of |
|
|
|
|
|
|
|
Just rawState -> case eitherDecode $ BL.fromStrict rawState of |
|
|
|
|
|
|
|
Left _ -> do |
|
|
|
|
|
|
|
liftIO $ warningM "main" "Unable to decode state" |
|
|
|
|
|
|
|
return def |
|
|
|
|
|
|
|
Right decodedState -> return decodedState |
|
|
|
|
|
|
|
Nothing -> do |
|
|
|
|
|
|
|
liftIO $ warningM "main" "Unable to decode state" |
|
|
|
|
|
|
|
return def |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
instance QuoteStream PersistenceT where |
|
|
|
|
|
|
|
addSubscription sub chan = undefined |
|
|
|
|
|
|
|
removeSubscription sub = undefined |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data RobotEnv c s = |
|
|
|
|
|
|
|
RobotEnv |
|
|
|
{ |
|
|
|
{ |
|
|
|
brokerEndpoint :: T.Text, |
|
|
|
stateRef :: IORef s, |
|
|
|
brokerServerCert :: Maybe FilePath, |
|
|
|
configRef :: IORef c |
|
|
|
brokerClientCert :: Maybe FilePath, |
|
|
|
} |
|
|
|
quotesourceEndpoint :: T.Text, |
|
|
|
|
|
|
|
quotesourceServerCert :: Maybe FilePath, |
|
|
|
newtype RobotM c s a = RobotM { unRobotM :: ReaderT (RobotEnv c s) IO a } |
|
|
|
quotesourceClientCert :: Maybe FilePath, |
|
|
|
deriving (Functor, Applicative, Monad, MonadReader (RobotEnv c s), MonadIO, MonadThrow) |
|
|
|
qhpEndpoint :: T.Text, |
|
|
|
|
|
|
|
qtisEndpoint :: T.Text, |
|
|
|
instance MonadRobot (RobotM c s) c s where |
|
|
|
redisSocket :: T.Text, |
|
|
|
submitOrder = undefined |
|
|
|
globalLog :: FilePath, |
|
|
|
cancelOrder = undefined |
|
|
|
instances :: [StrategyInstanceDescriptor] |
|
|
|
appendToLog = undefined |
|
|
|
} deriving (Generic, Show) |
|
|
|
setupTimer = undefined |
|
|
|
|
|
|
|
enqueueIOAction = undefined |
|
|
|
instance FromDhall ProgramConfiguration |
|
|
|
getConfig = undefined |
|
|
|
|
|
|
|
getState = undefined |
|
|
|
load :: T.Text -> IO (Maybe B.ByteString) |
|
|
|
setState = undefined |
|
|
|
load = undefined |
|
|
|
getEnvironment = undefined |
|
|
|
|
|
|
|
getTicker = undefined |
|
|
|
junctionMain :: M.Map T.Text StrategyDescriptor -> IO () |
|
|
|
|
|
|
|
|
|
|
|
junctionMain :: M.Map T.Text StrategyDescriptorE -> IO () |
|
|
|
junctionMain descriptors = do |
|
|
|
junctionMain descriptors = do |
|
|
|
opts <- parseOptions |
|
|
|
opts <- parseOptions |
|
|
|
|
|
|
|
|
|
|
|
cfg <- readFile (configPath opts) >>= input auto |
|
|
|
cfg <- readFile (configPath opts) >>= input auto |
|
|
|
|
|
|
|
|
|
|
|
bars <- newTVarIO M.empty |
|
|
|
barsMap <- newIORef M.empty |
|
|
|
|
|
|
|
|
|
|
|
strategies <- mkStrategies (instances cfg) |
|
|
|
redis <- checkedConnect (defaultConnectInfo { connectPort = UnixSocket (T.unpack $ redisSocket cfg) }) |
|
|
|
|
|
|
|
withContext $ \ctx -> do |
|
|
|
start strategies bars |
|
|
|
let env = DownloaderEnv (mkQHPHandle ctx (qhpEndpoint cfg)) ctx (qtisEndpoint cfg) |
|
|
|
|
|
|
|
withBroker cfg ctx $ \bro -> |
|
|
|
|
|
|
|
withQThread env barsMap cfg ctx $ \qt -> |
|
|
|
|
|
|
|
withPersistence (PersistenceEnv redis $ robotsConfigsPath cfg) $ |
|
|
|
|
|
|
|
forM_ (instances cfg) $ \inst -> |
|
|
|
|
|
|
|
case M.lookup (strategyBaseName inst) descriptors of |
|
|
|
|
|
|
|
Just (StrategyDescriptorE desc) -> do |
|
|
|
|
|
|
|
bigConf <- loadConfig (configKey inst) |
|
|
|
|
|
|
|
rConf <- liftIO $ newIORef (confStrategy bigConf) |
|
|
|
|
|
|
|
rState <- loadState (stateKey inst) >>= liftIO . newIORef |
|
|
|
|
|
|
|
let robotEnv = RobotEnv rState rConf |
|
|
|
|
|
|
|
createRobotDriverThread inst desc (flip runReaderT robotEnv . unRobotM) bigConf rConf rState |
|
|
|
|
|
|
|
Nothing -> error "Unknown strategy" |
|
|
|
where |
|
|
|
where |
|
|
|
|
|
|
|
withPersistence :: PersistenceEnv -> PersistenceT () -> IO () |
|
|
|
|
|
|
|
withPersistence env = (`runReaderT` env) . unPersistenceT |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
withBroker cfg ctx f = bracket |
|
|
|
|
|
|
|
(startBrokerClient |
|
|
|
|
|
|
|
"broker" |
|
|
|
|
|
|
|
ctx |
|
|
|
|
|
|
|
(brokerEndpoint cfg) |
|
|
|
|
|
|
|
(brokerNotificationEndpoint cfg) |
|
|
|
|
|
|
|
[] |
|
|
|
|
|
|
|
(ClientSecurityParams -- TODO load certificates from file |
|
|
|
|
|
|
|
Nothing |
|
|
|
|
|
|
|
Nothing)) |
|
|
|
|
|
|
|
stopBrokerClient f |
|
|
|
parseOptions = execParser options |
|
|
|
parseOptions = execParser options |
|
|
|
options = info (optionsParser <**> helper) |
|
|
|
options = info (optionsParser <**> helper) |
|
|
|
(fullDesc <> |
|
|
|
(fullDesc <> |
|
|
|
progDesc "Robocom-zero junction mode driver" <> |
|
|
|
progDesc "Robocom-zero junction mode driver" <> |
|
|
|
header "robocom-zero-junction") |
|
|
|
header "robocom-zero-junction") |
|
|
|
|
|
|
|
|
|
|
|
mkStrategies :: [StrategyInstanceDescriptor] -> IO [StrategyInstance] |
|
|
|
|
|
|
|
mkStrategies = mapM mkStrategy |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mkStrategy :: StrategyInstanceDescriptor -> IO StrategyInstance |
|
|
|
|
|
|
|
mkStrategy desc = do |
|
|
|
|
|
|
|
sState <- load (stateKey desc) |
|
|
|
|
|
|
|
sCfg <- load (configKey desc) |
|
|
|
|
|
|
|
case M.lookup (strategyId desc) descriptors of |
|
|
|
|
|
|
|
Just (StrategyDescriptor _sName sCallback sDefState) -> |
|
|
|
|
|
|
|
case (sCfg >>= decode . BL.fromStrict, fromMaybe sDefState (sState >>= decode . BL.fromStrict)) of |
|
|
|
|
|
|
|
(Just bigConfig, pState) -> do |
|
|
|
|
|
|
|
cfgRef <- newIORef (confStrategy bigConfig) |
|
|
|
|
|
|
|
stateRef <- newIORef pState |
|
|
|
|
|
|
|
return $ StrategyInstance |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
strategyInstanceId = strategyName desc, |
|
|
|
|
|
|
|
strategyEventCallback = sCallback, |
|
|
|
|
|
|
|
strategyState = stateRef, |
|
|
|
|
|
|
|
strategyConfig = cfgRef |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
_ -> error "Can't read state and config" |
|
|
|
|
|
|
|
_ -> error $ "Can't find strategy: " ++ T.unpack (strategyId desc) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start strategies bars = undefined |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optionsParser :: Parser ProgramOptions |
|
|
|
optionsParser :: Parser ProgramOptions |
|
|
|
optionsParser = ProgramOptions |
|
|
|
optionsParser = ProgramOptions |
|
|
|
<$> strOption |
|
|
|
<$> strOption |
|
|
|
|