ATrade-QUIK connector
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.

148 lines
4.9 KiB

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