|
|
|
|
|
|
|
|
|
module Broker.PaperBroker (
|
|
|
|
|
PaperBrokerState,
|
|
|
|
|
mkPaperBroker
|
|
|
|
|
) where
|
|
|
|
|
|
|
|
|
|
import Data.Hashable
|
|
|
|
|
import Data.Bits
|
|
|
|
|
import Control.Concurrent.BoundedChan
|
|
|
|
|
import Data.ATrade
|
|
|
|
|
import Data.IORef
|
|
|
|
|
import qualified Data.HashMap as M
|
|
|
|
|
import Broker
|
|
|
|
|
import Data.Time.Clock
|
|
|
|
|
import Data.Decimal
|
|
|
|
|
import Control.Monad
|
|
|
|
|
import Control.Concurrent hiding (readChan)
|
|
|
|
|
import System.Log.Logger
|
|
|
|
|
|
|
|
|
|
data TickMapKey = TickMapKey String DataType
|
|
|
|
|
deriving (Show, Eq, Ord)
|
|
|
|
|
|
|
|
|
|
instance Hashable TickMapKey where
|
|
|
|
|
hashWithSalt salt (TickMapKey s dt) = hashWithSalt salt s `xor` hashWithSalt salt (fromEnum dt)
|
|
|
|
|
|
|
|
|
|
data PaperBrokerState = PaperBrokerState {
|
|
|
|
|
pbTid :: Maybe ThreadId,
|
|
|
|
|
tickChannel :: BoundedChan Tick,
|
|
|
|
|
tickMap :: M.Map TickMapKey Tick,
|
|
|
|
|
orders :: M.Map OrderId Order,
|
|
|
|
|
cash :: Decimal,
|
|
|
|
|
orderIdCounter :: OrderId,
|
|
|
|
|
tradeCallback :: Maybe (Trade -> IO ()),
|
|
|
|
|
orderCallback :: Maybe (Order -> IO ())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mkPaperBroker :: BoundedChan Tick -> Decimal -> [String] -> IO Broker
|
|
|
|
|
mkPaperBroker tickChan startCash accounts = do
|
|
|
|
|
state <- newIORef PaperBrokerState {
|
|
|
|
|
pbTid = Nothing,
|
|
|
|
|
tickChannel = tickChan,
|
|
|
|
|
tickMap = M.empty,
|
|
|
|
|
orders = M.empty,
|
|
|
|
|
cash = startCash,
|
|
|
|
|
orderIdCounter = 1,
|
|
|
|
|
tradeCallback = Nothing,
|
|
|
|
|
orderCallback = Nothing }
|
|
|
|
|
|
|
|
|
|
tid <- forkIO $ brokerThread state
|
|
|
|
|
atomicModifyIORef' state (\s -> (s { pbTid = Just tid }, ()) )
|
|
|
|
|
|
|
|
|
|
return Broker {
|
|
|
|
|
accounts = accounts,
|
|
|
|
|
setTradeCallback = pbSetTradeCallback state,
|
|
|
|
|
setOrderCallback = pbSetOrderCallback state,
|
|
|
|
|
submitOrder = pbSubmitOrder state,
|
|
|
|
|
cancelOrder = pbCancelOrder state,
|
|
|
|
|
getOrder = pbGetOrder state,
|
|
|
|
|
destroyBroker = pbDestroyBroker state }
|
|
|
|
|
|
|
|
|
|
brokerThread :: IORef PaperBrokerState -> IO ()
|
|
|
|
|
brokerThread state = do
|
|
|
|
|
chan <- tickChannel <$> readIORef state
|
|
|
|
|
forever $ do
|
|
|
|
|
tick <- readChan chan
|
|
|
|
|
atomicModifyIORef' state (\s -> (s { tickMap = M.insert (makeKey tick) tick (tickMap s) }, ()) )
|
|
|
|
|
where
|
|
|
|
|
makeKey tick = TickMapKey (security tick) (datatype tick)
|
|
|
|
|
|
|
|
|
|
nextOrderId :: IORef PaperBrokerState -> IO OrderId
|
|
|
|
|
nextOrderId state = do
|
|
|
|
|
id <- orderIdCounter <$> readIORef state
|
|
|
|
|
modifyIORef state (\s -> s { orderIdCounter = id + 1 } )
|
|
|
|
|
return id
|
|
|
|
|
|
|
|
|
|
pbSetTradeCallback :: IORef PaperBrokerState -> Maybe (Trade -> IO ()) -> IO()
|
|
|
|
|
pbSetTradeCallback state callback = modifyIORef state (\s -> s { tradeCallback = callback } )
|
|
|
|
|
|
|
|
|
|
pbSetOrderCallback :: IORef PaperBrokerState -> Maybe (Order -> IO ()) -> IO()
|
|
|
|
|
pbSetOrderCallback state callback = modifyIORef state (\s -> s { orderCallback = callback } )
|
|
|
|
|
|
|
|
|
|
pbSubmitOrder :: IORef PaperBrokerState -> Order -> IO OrderId
|
|
|
|
|
pbSubmitOrder state order = do
|
|
|
|
|
curState <- readIORef state
|
|
|
|
|
case orderPrice order of
|
|
|
|
|
Market -> executeMarketOrder state order
|
|
|
|
|
Limit price -> submitLimitOrder state order
|
|
|
|
|
Stop price trigger -> submitStopOrder state order
|
|
|
|
|
StopMarket trigger -> submitStopMarketOrder state order
|
|
|
|
|
|
|
|
|
|
where
|
|
|
|
|
executeMarketOrder state order = do
|
|
|
|
|
tm <- tickMap <$> readIORef state
|
|
|
|
|
oid <- nextOrderId state
|
|
|
|
|
case M.lookup key tm of
|
|
|
|
|
Nothing -> let newOrder = order { orderState = Error "Unable to execute order: no bid/ask", orderId = oid } in
|
|
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert oid newOrder $ orders s }, ()) )
|
|
|
|
|
|
|
|
|
|
Just tick -> let newOrder = order { orderState = Executed, orderId = oid }
|
|
|
|
|
tradeVolume = (realFracToDecimal 10 (fromIntegral $ orderQuantity order) * value tick) in do
|
|
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert oid newOrder $ orders s , cash = cash s - tradeVolume}, ()) )
|
|
|
|
|
ts <- getCurrentTime
|
|
|
|
|
maybeCall tradeCallback state $ mkTrade tick order ts
|
|
|
|
|
|
|
|
|
|
return oid
|
|
|
|
|
|
|
|
|
|
submitLimitOrder = undefined
|
|
|
|
|
submitStopOrder = undefined
|
|
|
|
|
submitStopMarketOrder = undefined
|
|
|
|
|
|
|
|
|
|
orderDatatype order = case orderOperation order of
|
|
|
|
|
Buy -> BestOffer
|
|
|
|
|
Sell -> BestBid
|
|
|
|
|
|
|
|
|
|
key = TickMapKey (orderSecurity order) (orderDatatype order)
|
|
|
|
|
maybeCall proj state arg = do
|
|
|
|
|
cb <- proj <$> readIORef state
|
|
|
|
|
case cb of
|
|
|
|
|
Just callback -> callback arg
|
|
|
|
|
Nothing -> return ()
|
|
|
|
|
|
|
|
|
|
mkTrade :: Tick -> Order -> UTCTime -> Trade
|
|
|
|
|
mkTrade tick order timestamp = Trade {
|
|
|
|
|
tradeOrderId = orderId order,
|
|
|
|
|
tradePrice = value tick,
|
|
|
|
|
tradeQuantity = orderQuantity order,
|
|
|
|
|
tradeVolume = realFracToDecimal 10 (fromIntegral $ orderQuantity order) * value tick,
|
|
|
|
|
tradeVolumeCurrency = "TEST",
|
|
|
|
|
tradeAccount = orderAccountId order,
|
|
|
|
|
tradeSecurity = orderSecurity order,
|
|
|
|
|
tradeTimestamp = timestamp,
|
|
|
|
|
tradeSignalId = orderSignalId order }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pbCancelOrder :: IORef PaperBrokerState -> OrderId -> IO ()
|
|
|
|
|
pbCancelOrder state order = undefined
|
|
|
|
|
|
|
|
|
|
pbDestroyBroker :: IORef PaperBrokerState -> IO ()
|
|
|
|
|
pbDestroyBroker state = do
|
|
|
|
|
maybeTid <- pbTid <$> readIORef state
|
|
|
|
|
case maybeTid of
|
|
|
|
|
Just tid -> killThread tid
|
|
|
|
|
Nothing -> return ()
|
|
|
|
|
|
|
|
|
|
pbGetOrder :: IORef PaperBrokerState -> OrderId -> IO (Maybe Order)
|
|
|
|
|
pbGetOrder state oid = M.lookup oid . orders <$> readIORef state
|
|
|
|
|
|