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.
142 lines
4.7 KiB
142 lines
4.7 KiB
|
9 years ago
|
|
||
|
|
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,
|
||
|
|
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
|
||
|
|
|
||
|
|
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
|
||
|
|
|
||
|
|
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 ()
|
||
|
|
|