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.
 

206 lines
8.0 KiB

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiWayIf #-}
module Broker.QuikBroker (
mkQuikBroker
) where
import ATrade.Types
import ATrade.Broker.Protocol
import ATrade.Broker.Server
import Broker.QuikBroker.Trans2QuikApi hiding (tradeAccount)
import Data.Decimal
import Data.IORef
import Data.List.Split
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Bimap as BM
import qualified Data.Text as T
import Control.Monad.Trans.Except
import Control.Monad.IO.Class
import System.Log.Logger
import Safe
type QuikOrderId = Integer
data QuikBrokerState = QuikBrokerState {
notificationCallback :: Maybe (Notification -> IO ()),
quik :: IORef Quik,
orderMap :: M.Map OrderId Order,
orderIdMap :: BM.Bimap QuikOrderId OrderId,
trans2orderid :: M.Map Integer Order,
transIdCounter :: Integer
}
nextTransId state = atomicModifyIORef' state (\s -> (s { transIdCounter = transIdCounter s + 1 }, transIdCounter s))
maybeCall proj state arg = do
cb <- proj <$> readIORef state
case cb of
Just callback -> callback arg
Nothing -> return ()
mkQuikBroker :: FilePath -> FilePath -> [T.Text] -> ExceptT T.Text IO BrokerInterface
mkQuikBroker dllPath quikPath accs = do
q <- mkQuik dllPath quikPath
state <- liftIO $ newIORef QuikBrokerState {
notificationCallback = Nothing,
quik = q,
orderMap = M.empty,
orderIdMap = BM.empty,
trans2orderid = M.empty,
transIdCounter = 1
}
setCallbacks q (qbTransactionCallback state) (qbOrderCallback state) (qbTradeCallback state)
return BrokerInterface {
accounts = accs,
setNotificationCallback = qbSetNotificationCallback state,
submitOrder = qbSubmitOrder state,
cancelOrder = qbCancelOrder state,
stopBroker = qbStopBroker state
}
qbSetNotificationCallback state maybecb = atomicModifyIORef' state (\s -> (s {
notificationCallback = maybecb }, ()))
qbSubmitOrder state order = do
q <- quik <$> readIORef state
transId <- nextTransId state
atomicModifyIORef' state (\s -> (s {
trans2orderid = M.insert transId order (trans2orderid s) }, ()))
case makeTransactionString transId order of
Just transStr -> do
rc <- quikSendTransaction q transStr
case rc of
Left errmsg -> warningM "Quik" $ "Unable to send transaction: " ++ T.unpack errmsg
Right _ -> debugM "Quik" $ "Order submitted: " ++ show order
Nothing -> warningM "Quik" $ "Unable to compose transaction string: " ++ show order
qbCancelOrder state orderid = do
q <- quik <$> readIORef state
transId <- nextTransId state
idMap <- orderIdMap <$> readIORef state
orders <- orderMap <$> readIORef state
case (BM.lookupR orderid idMap, M.lookup orderid orders) of
(Just quikOrderId, Just order) -> case makeCancelTransactionString transId order quikOrderId of
Just transString -> do
rc <- quikSendTransaction q transString
case rc of
Left errmsg -> warningM "Quik" ("Unable to send transaction: " ++ T.unpack errmsg) >> return False
Right _ -> debugM "Quik" ("Order cancelled: " ++ show orderid) >> return True
Nothing -> warningM "Quik" ("Unable to compose transaction string: " ++ show orderid) >> return False
_ -> warningM "Quik" ("Got request to cancel unknown order: " ++ show orderid) >> return False
qbStopBroker state = return ()
makeTransactionString transId order =
case (classcode, seccode) of
(Just cCode, Just sCode) -> Just $
"ACCOUNT=" ++ T.unpack (orderAccountId order) ++ ";" ++
"TYPE=" ++ orderTypeCode ++ ";" ++
"TRANS_ID=" ++ show transId ++ ";" ++
"CLASSCODE=" ++ cCode ++ ";" ++
"SECCODE=" ++ sCode ++ ";" ++
"ACTION=NEW_ORDER;OPERATION=" ++ operationCode ++ ";" ++
"PRICE=" ++ price ++ ";" ++
"QUANTITY=" ++ show (orderQuantity order) ++ ";"
_ -> Nothing
where
orderTypeCode = case orderPrice order of
Market -> "M"
Limit _ -> "L"
_ -> "X"
operationCode = case orderOperation order of
Buy -> "B"
Sell -> "S"
classcode = headMay . splitOn "#" . T.unpack $ orderSecurity order
seccode = (`atMay` 1) . splitOn "#" . T.unpack $ orderSecurity order
price = case orderPrice order of
Market -> "0"
Limit p -> removeTrailingZeros . show $ p
_ -> "0"
removeTrailingZeros v = if '.' `L.elem` v then L.dropWhileEnd (== '.') . L.dropWhileEnd (== '0') $ v else v
makeCancelTransactionString transId order orderId =
case (classcode, seccode) of
(Just cCode, Just sCode) -> Just $
"TRANS_ID=" ++ show transId ++ ";" ++
"CLASSCODE=" ++ cCode ++ ";" ++
"SECCODE=" ++ sCode ++ ";" ++
"ACTION=KILL_ORDER;ORDER_KEY=" ++ show orderId ++ ";"
_ -> Nothing
where
classcode = headMay . splitOn "#" . T.unpack $ orderSecurity order
seccode = (`atMay` 1) . splitOn "#" . T.unpack $ orderSecurity order
qbTransactionCallback state success transactionId orderNum = do
t2oid <- trans2orderid <$> readIORef state
case M.lookup transactionId t2oid of
Just order -> do
atomicModifyIORef' state (\s -> (s { trans2orderid = M.delete transactionId t2oid }, ()) )
newOrder <- if success
then registerOrder orderNum $ order { orderState = Unsubmitted }
else registerOrder orderNum $ order { orderState = Rejected }
maybeCall notificationCallback state (OrderNotification (orderId newOrder) (orderState newOrder))
Nothing -> return ()
where
registerOrder quikOrderId order = atomicModifyIORef' state (\s ->
(s { orderIdMap = BM.insert quikOrderId (orderId order) (orderIdMap s),
orderMap = M.insert (orderId order) order (orderMap s) }, order) )
qbOrderCallback state quikorder = do
orders <- orderMap <$> readIORef state
idMap <- orderIdMap <$> readIORef state
debugM "Quik" $ "Order: " ++ show quikorder
case BM.lookup (qoOrderId quikorder) idMap >>= flip M.lookup orders of
Just order -> do
updatedOrder <- if | qoStatus quikorder /= 1 && qoStatus quikorder /= 2 ->
if qoBalance quikorder == 0
then fullyExecuted order
else partiallyExecuted order (orderExecutedQuantity order - qoBalance quikorder)
| qoStatus quikorder == 1 ->
submitted order
| qoStatus quikorder == 2 ->
cancelled order
maybeCall notificationCallback state (OrderNotification (orderId updatedOrder) (orderState updatedOrder))
Nothing -> warningM "Quik" $ "Unknown order: state callback called: " ++ show quikorder
where
updateOrder :: Order -> IO Order
updateOrder updatedOrder =
atomicModifyIORef' state (\s -> (s { orderMap = M.insert (orderId updatedOrder) updatedOrder (orderMap s)}, updatedOrder))
fullyExecuted order = updateOrder $ order { orderState = Executed, orderExecutedQuantity = orderQuantity order }
partiallyExecuted order quan = updateOrder $ order { orderState = PartiallyExecuted, orderExecutedQuantity = quan }
submitted order = updateOrder $ order { orderState = Submitted }
cancelled order = updateOrder $ order { orderState = Cancelled }
qbTradeCallback state quiktrade = do
orders <- orderMap <$> readIORef state
idMap <- orderIdMap <$> readIORef state
debugM "Quik" $ "Trade: " ++ show quiktrade
case BM.lookup (qtOrderId quiktrade) idMap >>= flip M.lookup orders of
Just order -> maybeCall notificationCallback state (TradeNotification $ tradeFor order)
Nothing -> warningM "Quik" $ "Incoming trade for unknown order: " ++ show quiktrade
where
tradeFor order = Trade {
tradeOrderId = orderId order,
tradePrice = realFracToDecimal 10 $ qtPrice quiktrade,
tradeQuantity = qtQuantity quiktrade,
tradeVolume = realFracToDecimal 10 $ qtVolume quiktrade,
tradeVolumeCurrency = T.pack $ qtVolumeCurrency quiktrade,
tradeOperation = if qtSell quiktrade then Sell else Buy,
tradeAccount = orderAccountId order,
tradeSecurity = orderSecurity order,
tradeTimestamp = qtTimestamp quiktrade,
tradeSignalId = orderSignalId order }