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.
194 lines
8.6 KiB
194 lines
8.6 KiB
{-# LANGUAGE ExistentialQuantification #-} |
|
{-# LANGUAGE FlexibleContexts #-} |
|
{-# LANGUAGE FlexibleInstances #-} |
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-} |
|
{-# LANGUAGE MultiParamTypeClasses #-} |
|
{-# LANGUAGE OverloadedStrings #-} |
|
{-# LANGUAGE RankNTypes #-} |
|
|
|
module ATrade.Driver.Junction.RobotDriverThread |
|
( |
|
createRobotDriverThread, |
|
RobotEnv(..), |
|
RobotM(..), |
|
RobotDriverHandle, |
|
onStrategyInstance, |
|
postNotificationEvent) where |
|
|
|
import ATrade.Broker.Protocol (Notification (OrderNotification, TradeNotification)) |
|
import qualified ATrade.Driver.Junction.BrokerService as Bro |
|
import ATrade.Driver.Junction.QuoteStream (QuoteStream (addSubscription), |
|
QuoteSubscription (QuoteSubscription)) |
|
import ATrade.Driver.Junction.Types (BigConfig, |
|
StrategyDescriptor, |
|
StrategyInstance (StrategyInstance, strategyEventCallback), |
|
StrategyInstanceDescriptor (configKey), |
|
confStrategy, |
|
confTickers, |
|
eventCallback, stateKey, |
|
strategyId, tickerId, |
|
timeframe) |
|
import ATrade.Logging (Message, log, logDebug, |
|
logInfo, logWarning) |
|
import ATrade.QuoteSource.Client (QuoteData (..)) |
|
import ATrade.RoboCom.ConfigStorage (ConfigStorage) |
|
import ATrade.RoboCom.Monad (Event (NewBar, NewTick, NewTrade, OrderSubmitted, OrderUpdate), |
|
MonadRobot (..), |
|
StrategyEnvironment (StrategyEnvironment, _seInstanceId, _seLastTimestamp)) |
|
import ATrade.RoboCom.Persistence (MonadPersistence) |
|
import ATrade.RoboCom.Types (BarSeriesId (BarSeriesId), |
|
Bars) |
|
import ATrade.Types (Order (orderId), OrderId, |
|
OrderState, Trade) |
|
import Colog (HasLog (getLogAction, setLogAction), |
|
LogAction) |
|
import Control.Concurrent (ThreadId, forkIO) |
|
import Control.Concurrent.BoundedChan (BoundedChan, |
|
newBoundedChan, readChan, |
|
writeChan) |
|
import Control.Exception.Safe (MonadThrow) |
|
import Control.Monad (forM_, forever, void) |
|
import Control.Monad.IO.Class (MonadIO, liftIO) |
|
import Control.Monad.Reader (MonadReader (local), |
|
ReaderT, asks) |
|
import Data.Aeson (FromJSON, ToJSON) |
|
import Data.Default |
|
import Data.IORef (IORef, |
|
atomicModifyIORef', |
|
readIORef, writeIORef) |
|
import Data.List.NonEmpty (NonEmpty) |
|
import qualified Data.Map.Strict as M |
|
import qualified Data.Text.Lazy as TL |
|
import Data.Time (UTCTime, getCurrentTime) |
|
import Dhall (FromDhall) |
|
import Prelude hiding (log) |
|
|
|
data RobotDriverHandle = forall c s. (FromDhall c, Default s, FromJSON s, ToJSON s) => |
|
RobotDriverHandle (StrategyInstance c s) ThreadId ThreadId (BoundedChan RobotDriverEvent) |
|
|
|
data RobotDriverRequest |
|
|
|
data RobotDriverEvent = |
|
EventRequest RobotDriverRequest |
|
| QuoteEvent QuoteData |
|
| NewTradeEvent Trade |
|
| OrderEvent OrderId OrderState |
|
|
|
|
|
robotDriverThread :: (MonadIO m, |
|
MonadRobot m c s) => |
|
StrategyInstance c s -> |
|
BoundedChan RobotDriverEvent -> |
|
m () |
|
|
|
robotDriverThread inst eventQueue = |
|
forever $ liftIO (readChan eventQueue) >>= handleEvent |
|
where |
|
handleEvent (EventRequest _) = return () |
|
handleEvent (QuoteEvent d) = |
|
case d of |
|
QDTick tick -> strategyEventCallback inst (NewTick tick) |
|
QDBar (tf, bar) -> strategyEventCallback inst (NewBar (tf, bar)) |
|
handleEvent (NewTradeEvent trade) = strategyEventCallback inst (NewTrade trade) |
|
handleEvent (OrderEvent oid newState) = strategyEventCallback inst (OrderUpdate oid newState) |
|
|
|
createRobotDriverThread :: (MonadIO m1, |
|
ConfigStorage m1, |
|
MonadPersistence m1, |
|
QuoteStream m1, |
|
Default s, |
|
FromJSON s, |
|
ToJSON s, |
|
FromDhall c, |
|
MonadIO m, |
|
MonadReader (RobotEnv c s) m, |
|
MonadRobot m c s) => |
|
StrategyInstanceDescriptor |
|
-> StrategyDescriptor c s |
|
-> (m () -> IO ()) |
|
-> BigConfig c |
|
-> IORef c |
|
-> IORef s |
|
-> IORef [UTCTime] |
|
-> m1 RobotDriverHandle |
|
|
|
createRobotDriverThread instDesc strDesc runner bigConf rConf rState rTimers = do |
|
eventQueue <- liftIO $ newBoundedChan 2000 |
|
|
|
let inst = StrategyInstance (strategyId instDesc) (eventCallback strDesc) rState rConf rTimers |
|
|
|
quoteQueue <- liftIO $ newBoundedChan 2000 |
|
forM_ (confTickers bigConf) (\x -> addSubscription (QuoteSubscription (tickerId x) (timeframe x)) quoteQueue) |
|
qthread <- liftIO . forkIO $ forever $ passQuoteEvents eventQueue quoteQueue |
|
|
|
driver <- liftIO . forkIO $ runner $ robotDriverThread inst eventQueue |
|
return $ RobotDriverHandle inst driver qthread eventQueue |
|
|
|
where |
|
passQuoteEvents eventQueue quoteQueue = do |
|
v <- readChan quoteQueue |
|
writeChan eventQueue (QuoteEvent v) |
|
|
|
onStrategyInstance :: RobotDriverHandle -> forall r. (forall c s. (FromDhall c, Default s, FromJSON s, ToJSON s) => StrategyInstance c s -> r) -> r |
|
onStrategyInstance (RobotDriverHandle inst _ _ _) f = f inst |
|
|
|
data RobotEnv c s = |
|
RobotEnv |
|
{ |
|
stateRef :: IORef s, |
|
configRef :: IORef c, |
|
timersRef :: IORef [UTCTime], |
|
bars :: IORef Bars, |
|
env :: IORef StrategyEnvironment, |
|
logAction :: LogAction (RobotM c s) Message, |
|
brokerService :: Bro.BrokerService, |
|
tickers :: NonEmpty BarSeriesId |
|
} |
|
|
|
newtype RobotM c s a = RobotM { unRobotM :: ReaderT (RobotEnv c s) IO a } |
|
deriving (Functor, Applicative, Monad, MonadReader (RobotEnv c s), MonadIO, MonadThrow) |
|
|
|
instance HasLog (RobotEnv c s) Message (RobotM c s) where |
|
getLogAction = logAction |
|
setLogAction a e = e { logAction = a } |
|
|
|
instance MonadRobot (RobotM c s) c s where |
|
submitOrder order = do |
|
instId <- _seInstanceId <$> (asks env >>= liftIO . readIORef) |
|
bro <- asks brokerService |
|
Bro.submitOrder bro instId order |
|
|
|
cancelOrder oid = do |
|
bro <- asks brokerService |
|
liftIO . void $ Bro.cancelOrder bro oid |
|
|
|
appendToLog s t = do |
|
instId <- _seInstanceId <$> (asks env >>= liftIO . readIORef) |
|
log s instId $ TL.toStrict t |
|
|
|
setupTimer t = do |
|
ref <- asks timersRef |
|
liftIO $ atomicModifyIORef' ref (\s -> (t : s, ())) |
|
|
|
enqueueIOAction = undefined |
|
getConfig = asks configRef >>= liftIO . readIORef |
|
getState = asks stateRef >>= liftIO . readIORef |
|
setState newState = asks stateRef >>= liftIO . flip writeIORef newState |
|
getEnvironment = do |
|
ref <- asks env |
|
now <- liftIO getCurrentTime |
|
liftIO $ atomicModifyIORef' ref (\e -> (e { _seLastTimestamp = now }, e { _seLastTimestamp = now})) |
|
|
|
getTicker tid tf = do |
|
b <- asks bars >>= liftIO . readIORef |
|
return $ M.lookup (BarSeriesId tid tf) b |
|
|
|
getAvailableTickers = asks tickers |
|
|
|
postNotificationEvent :: (MonadIO m) => RobotDriverHandle -> Notification -> m () |
|
postNotificationEvent (RobotDriverHandle _ _ _ eventQueue) notification = liftIO $ |
|
case notification of |
|
OrderNotification _ oid state -> writeChan eventQueue (OrderEvent oid state) |
|
TradeNotification _ trade -> writeChan eventQueue (NewTradeEvent trade) |
|
|
|
|
|
|