|
|
|
@ -1,4 +1,5 @@ |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
{-# LANGUAGE OverloadedStrings #-} |
|
|
|
|
|
|
|
{-# LANGUAGE QuasiQuotes #-} |
|
|
|
{-# LANGUAGE Strict #-} |
|
|
|
{-# LANGUAGE Strict #-} |
|
|
|
|
|
|
|
|
|
|
|
module Broker.PaperBroker ( |
|
|
|
module Broker.PaperBroker ( |
|
|
|
@ -6,10 +7,15 @@ module Broker.PaperBroker ( |
|
|
|
mkPaperBroker |
|
|
|
mkPaperBroker |
|
|
|
) where |
|
|
|
) where |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import ATrade.Broker.Backend |
|
|
|
import ATrade.Broker.Protocol |
|
|
|
import ATrade.Broker.Protocol |
|
|
|
import ATrade.Broker.Server |
|
|
|
import ATrade.Broker.Server |
|
|
|
|
|
|
|
import ATrade.Logging (Message, Severity (..), |
|
|
|
|
|
|
|
logWith) |
|
|
|
import ATrade.Quotes.QTIS |
|
|
|
import ATrade.Quotes.QTIS |
|
|
|
import ATrade.Types |
|
|
|
import ATrade.Types |
|
|
|
|
|
|
|
import Colog (LogAction) |
|
|
|
|
|
|
|
import Commissions (CommissionConfig (..)) |
|
|
|
import Control.Concurrent hiding (readChan, writeChan) |
|
|
|
import Control.Concurrent hiding (readChan, writeChan) |
|
|
|
import Control.Concurrent.BoundedChan |
|
|
|
import Control.Concurrent.BoundedChan |
|
|
|
import Control.Monad |
|
|
|
import Control.Monad |
|
|
|
@ -20,11 +26,10 @@ import qualified Data.List as L |
|
|
|
import qualified Data.Map.Strict as M |
|
|
|
import qualified Data.Map.Strict as M |
|
|
|
import Data.Maybe |
|
|
|
import Data.Maybe |
|
|
|
import qualified Data.Text as T |
|
|
|
import qualified Data.Text as T |
|
|
|
|
|
|
|
import qualified Data.Text.Lazy as TL |
|
|
|
import Data.Time.Clock |
|
|
|
import Data.Time.Clock |
|
|
|
import System.Log.Logger |
|
|
|
import Language.Haskell.Printf (t) |
|
|
|
import System.ZMQ4 |
|
|
|
import System.ZMQ4 |
|
|
|
|
|
|
|
|
|
|
|
import Commissions (CommissionConfig (..)) |
|
|
|
|
|
|
|
import TickTable (TickKey (..), TickTableH, |
|
|
|
import TickTable (TickKey (..), TickTableH, |
|
|
|
getTick, getTickerInfo) |
|
|
|
getTick, getTickerInfo) |
|
|
|
|
|
|
|
|
|
|
|
@ -33,7 +38,7 @@ data PaperBrokerState = PaperBrokerState { |
|
|
|
tickTable :: TickTableH, |
|
|
|
tickTable :: TickTableH, |
|
|
|
orders :: M.Map OrderId Order, |
|
|
|
orders :: M.Map OrderId Order, |
|
|
|
cash :: !Price, |
|
|
|
cash :: !Price, |
|
|
|
notificationCallback :: Maybe (Notification -> IO ()), |
|
|
|
notificationCallback :: Maybe (BrokerBackendNotification -> IO ()), |
|
|
|
pendingOrders :: [Order], |
|
|
|
pendingOrders :: [Order], |
|
|
|
|
|
|
|
|
|
|
|
fortsClassCodes :: [T.Text], |
|
|
|
fortsClassCodes :: [T.Text], |
|
|
|
@ -45,14 +50,15 @@ data PaperBrokerState = PaperBrokerState { |
|
|
|
postMarketStartTime :: DiffTime, |
|
|
|
postMarketStartTime :: DiffTime, |
|
|
|
postMarketFixTime :: DiffTime, |
|
|
|
postMarketFixTime :: DiffTime, |
|
|
|
postMarketCloseTime :: DiffTime, |
|
|
|
postMarketCloseTime :: DiffTime, |
|
|
|
commissions :: [CommissionConfig] |
|
|
|
commissions :: [CommissionConfig], |
|
|
|
|
|
|
|
logger :: LogAction IO Message |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
hourMin :: Integer -> Integer -> DiffTime |
|
|
|
hourMin :: Integer -> Integer -> DiffTime |
|
|
|
hourMin h m = fromIntegral $ h * 3600 + m * 60 |
|
|
|
hourMin h m = fromIntegral $ h * 3600 + m * 60 |
|
|
|
|
|
|
|
|
|
|
|
mkPaperBroker :: TickTableH -> BoundedChan Tick -> Price -> [T.Text] -> [CommissionConfig] -> IO BrokerInterface |
|
|
|
mkPaperBroker :: TickTableH -> BoundedChan Tick -> Price -> [T.Text] -> [CommissionConfig] -> LogAction IO Message -> IO BrokerBackend |
|
|
|
mkPaperBroker tickTableH tickChan startCash accounts comms = do |
|
|
|
mkPaperBroker tickTableH tickChan startCash accounts comms l = do |
|
|
|
state <- newIORef PaperBrokerState { |
|
|
|
state <- newIORef PaperBrokerState { |
|
|
|
pbTid = Nothing, |
|
|
|
pbTid = Nothing, |
|
|
|
tickTable = tickTableH, |
|
|
|
tickTable = tickTableH, |
|
|
|
@ -68,18 +74,19 @@ mkPaperBroker tickTableH tickChan startCash accounts comms = do |
|
|
|
postMarketStartTime = hourMin 15 40, |
|
|
|
postMarketStartTime = hourMin 15 40, |
|
|
|
postMarketFixTime = hourMin 15 45, |
|
|
|
postMarketFixTime = hourMin 15 45, |
|
|
|
postMarketCloseTime = hourMin 15 50, |
|
|
|
postMarketCloseTime = hourMin 15 50, |
|
|
|
commissions = comms |
|
|
|
commissions = comms, |
|
|
|
|
|
|
|
logger = l |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
tid <- forkIO $ brokerThread tickChan state |
|
|
|
tid <- forkIO $ brokerThread tickChan state |
|
|
|
atomicModifyIORef' state (\s -> (s { pbTid = Just tid }, ())) |
|
|
|
atomicModifyIORef' state (\s -> (s { pbTid = Just tid }, ())) |
|
|
|
|
|
|
|
|
|
|
|
return BrokerInterface { |
|
|
|
return BrokerBackend { |
|
|
|
accounts = accounts, |
|
|
|
accounts = accounts, |
|
|
|
setNotificationCallback = pbSetNotificationCallback state, |
|
|
|
setNotificationCallback = pbSetNotificationCallback state, |
|
|
|
submitOrder = pbSubmitOrder state, |
|
|
|
submitOrder = pbSubmitOrder state, |
|
|
|
cancelOrder = pbCancelOrder state, |
|
|
|
cancelOrder = void . pbCancelOrder state, |
|
|
|
stopBroker = pbDestroyBroker state } |
|
|
|
stop = pbDestroyBroker state } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
brokerThread :: BoundedChan Tick -> IORef PaperBrokerState -> IO () |
|
|
|
brokerThread :: BoundedChan Tick -> IORef PaperBrokerState -> IO () |
|
|
|
@ -101,7 +108,7 @@ executePendingOrders tick state = do |
|
|
|
then |
|
|
|
then |
|
|
|
case orderPrice order of |
|
|
|
case orderPrice order of |
|
|
|
Market -> do |
|
|
|
Market -> do |
|
|
|
debugM "PaperBroker" "Executing: pending market order" |
|
|
|
log Debug "PaperBroker" "Executing: pending market order" |
|
|
|
executeAtTick state order tick |
|
|
|
executeAtTick state order tick |
|
|
|
return $ Just $ orderId order |
|
|
|
return $ Just $ orderId order |
|
|
|
Limit price -> |
|
|
|
Limit price -> |
|
|
|
@ -109,22 +116,27 @@ executePendingOrders tick state = do |
|
|
|
_ -> return Nothing |
|
|
|
_ -> return Nothing |
|
|
|
else return Nothing |
|
|
|
else return Nothing |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log sev comp txt = do |
|
|
|
|
|
|
|
l <- logger <$> readIORef state |
|
|
|
|
|
|
|
logWith l sev comp txt |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
executeLimitAt price order = case orderOperation order of |
|
|
|
executeLimitAt price order = case orderOperation order of |
|
|
|
Buy -> if (datatype tick == LastTradePrice && price > value tick && value tick > 0) || (datatype tick == BestOffer && price > value tick && value tick > 0) |
|
|
|
Buy -> if (datatype tick == LastTradePrice && price > value tick && value tick > 0) || |
|
|
|
|
|
|
|
(datatype tick == BestOffer && price > value tick && value tick > 0) |
|
|
|
then do |
|
|
|
then do |
|
|
|
debugM "PaperBroker" $ "[1]Executing: pending limit order: " ++ show (security tick) ++ "/" ++ show (orderSecurity order) |
|
|
|
log Debug "PaperBroker" $ TL.toStrict $ [t|[1]Executing: pending limit order: %Q/%Q|] (security tick) (orderSecurity order) |
|
|
|
executeAtTick state order $ tick { value = price } |
|
|
|
executeAtTick state order $ tick { value = price } |
|
|
|
return $ Just $ orderId order |
|
|
|
return $ Just $ orderId order |
|
|
|
else return Nothing |
|
|
|
else return Nothing |
|
|
|
Sell -> if (datatype tick == LastTradePrice && price < value tick && value tick > 0) || (datatype tick == BestBid && price < value tick && value tick > 0) |
|
|
|
Sell -> if (datatype tick == LastTradePrice && price < value tick && value tick > 0) || (datatype tick == BestBid && price < value tick && value tick > 0) |
|
|
|
then do |
|
|
|
then do |
|
|
|
debugM "PaperBroker" $ "[2]Executing: pending limit order: " ++ show (security tick) ++ "/" ++ show (orderSecurity order) |
|
|
|
log Debug "PaperBroker" $ TL.toStrict $ [t|[2]Executing: pending limit order: %Q/%Q|] (security tick) (orderSecurity order) |
|
|
|
executeAtTick state order $ tick { value = price } |
|
|
|
executeAtTick state order $ tick { value = price } |
|
|
|
return $ Just $ orderId order |
|
|
|
return $ Just $ orderId order |
|
|
|
else return Nothing |
|
|
|
else return Nothing |
|
|
|
|
|
|
|
|
|
|
|
pbSetNotificationCallback :: IORef PaperBrokerState -> Maybe (Notification -> IO ()) -> IO() |
|
|
|
pbSetNotificationCallback :: IORef PaperBrokerState -> Maybe (BrokerBackendNotification -> IO ()) -> IO() |
|
|
|
pbSetNotificationCallback state callback = atomicModifyIORef' state (\s -> (s { notificationCallback = callback }, ()) ) |
|
|
|
pbSetNotificationCallback state callback = atomicModifyIORef' state (\s -> (s { notificationCallback = callback }, ()) ) |
|
|
|
|
|
|
|
|
|
|
|
mkTrade :: TickerInfo -> Tick -> Order -> UTCTime -> Maybe CommissionConfig -> Trade |
|
|
|
mkTrade :: TickerInfo -> Tick -> Order -> UTCTime -> Maybe CommissionConfig -> Trade |
|
|
|
@ -157,10 +169,10 @@ executeAtTick state order tick = do |
|
|
|
comm <- L.find (\comdef -> comPrefix comdef `T.isPrefixOf` security tick) . commissions <$> readIORef state |
|
|
|
comm <- L.find (\comdef -> comPrefix comdef `T.isPrefixOf` security tick) . commissions <$> readIORef state |
|
|
|
let tradeVolume = fromInteger (orderQuantity order) * value tick * fromInteger (tiLotSize tickerInfo) |
|
|
|
let tradeVolume = fromInteger (orderQuantity order) * value tick * fromInteger (tiLotSize tickerInfo) |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s , cash = cash s - tradeVolume}, ())) |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s , cash = cash s - tradeVolume}, ())) |
|
|
|
debugM "PaperBroker" $ "Executed: " ++ show newOrder ++ "; at tick: " ++ show tick |
|
|
|
log Debug "PaperBroker" $ TL.toStrict $ [t|Executed: %? at tick: %?|] newOrder tick |
|
|
|
ts <- getCurrentTime |
|
|
|
ts <- getCurrentTime |
|
|
|
maybeCall notificationCallback state $ TradeNotification $ mkTrade tickerInfo tick order ts comm |
|
|
|
maybeCall notificationCallback state $ BackendTradeNotification $ mkTrade tickerInfo tick order ts comm |
|
|
|
maybeCall notificationCallback state $ OrderNotification (orderId order) Executed |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification (orderId order) Executed |
|
|
|
where |
|
|
|
where |
|
|
|
obtainTickerInfo tickerId = do |
|
|
|
obtainTickerInfo tickerId = do |
|
|
|
table <- tickTable <$> readIORef state |
|
|
|
table <- tickTable <$> readIORef state |
|
|
|
@ -170,16 +182,20 @@ executeAtTick state order tick = do |
|
|
|
_ -> return TickerInfo { tiTicker = tickerId, |
|
|
|
_ -> return TickerInfo { tiTicker = tickerId, |
|
|
|
tiLotSize = 1, |
|
|
|
tiLotSize = 1, |
|
|
|
tiTickSize = 1 } |
|
|
|
tiTickSize = 1 } |
|
|
|
|
|
|
|
log sev comp txt = do |
|
|
|
|
|
|
|
l <- logger <$> readIORef state |
|
|
|
|
|
|
|
logWith l sev comp txt |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rejectOrder state order = do |
|
|
|
rejectOrder state order = do |
|
|
|
let newOrder = order { orderState = Rejected } in |
|
|
|
let newOrder = order { orderState = Rejected } in |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s }, ())) |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s }, ())) |
|
|
|
maybeCall notificationCallback state $ OrderNotification (orderId order) Submitted |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification (orderId order) Submitted |
|
|
|
maybeCall notificationCallback state $ OrderNotification (orderId order) Rejected |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification (orderId order) Rejected |
|
|
|
|
|
|
|
|
|
|
|
pbSubmitOrder :: IORef PaperBrokerState -> Order -> IO () |
|
|
|
pbSubmitOrder :: IORef PaperBrokerState -> Order -> IO () |
|
|
|
pbSubmitOrder state order = do |
|
|
|
pbSubmitOrder state order = do |
|
|
|
infoM "PaperBroker" $ "Submitted order: " ++ show order |
|
|
|
log Info "PaperBroker" $ "Submitted order: " <> (T.pack . show) order |
|
|
|
case orderPrice order of |
|
|
|
case orderPrice order of |
|
|
|
Market -> executeMarketOrder state order |
|
|
|
Market -> executeMarketOrder state order |
|
|
|
Limit price -> submitLimitOrder price state order |
|
|
|
Limit price -> submitLimitOrder price state order |
|
|
|
@ -187,6 +203,9 @@ pbSubmitOrder state order = do |
|
|
|
StopMarket trigger -> submitStopMarketOrder state order |
|
|
|
StopMarket trigger -> submitStopMarketOrder state order |
|
|
|
|
|
|
|
|
|
|
|
where |
|
|
|
where |
|
|
|
|
|
|
|
log sev comp txt = do |
|
|
|
|
|
|
|
l <- logger <$> readIORef state |
|
|
|
|
|
|
|
logWith l sev comp txt |
|
|
|
executeMarketOrder state order = do |
|
|
|
executeMarketOrder state order = do |
|
|
|
tm <- tickTable <$> readIORef state |
|
|
|
tm <- tickTable <$> readIORef state |
|
|
|
tickMb <- getTick tm key |
|
|
|
tickMb <- getTick tm key |
|
|
|
@ -200,25 +219,26 @@ pbSubmitOrder state order = do |
|
|
|
else do |
|
|
|
else do |
|
|
|
tm <- tickTable <$> readIORef state |
|
|
|
tm <- tickTable <$> readIORef state |
|
|
|
tickMb <- getTick tm key |
|
|
|
tickMb <- getTick tm key |
|
|
|
debugM "PaperBroker" $ "Limit order submitted, looking up: " ++ show key |
|
|
|
log Debug "PaperBroker" $ "Limit order submitted, looking up: " <> (T.pack . show) key |
|
|
|
case tickMb of |
|
|
|
case tickMb of |
|
|
|
Nothing -> do |
|
|
|
Nothing -> do |
|
|
|
let newOrder = order { orderState = Submitted } |
|
|
|
let newOrder = order { orderState = Submitted } |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s }, ())) |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s }, ())) |
|
|
|
maybeCall notificationCallback state $ OrderNotification (orderId order) Submitted |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification (orderId order) Submitted |
|
|
|
Just tick -> do |
|
|
|
Just tick -> do |
|
|
|
marketOpenTime' <- marketOpenTime <$> readIORef state |
|
|
|
marketOpenTime' <- marketOpenTime <$> readIORef state |
|
|
|
if (((orderOperation order == Buy) && (value tick < price)) || ((orderOperation order == Sell) && (value tick > price)) && (utctDayTime (timestamp tick) >= marketOpenTime')) |
|
|
|
if (((orderOperation order == Buy) && (value tick < price)) || |
|
|
|
|
|
|
|
((orderOperation order == Sell) && (value tick > price)) && (utctDayTime (timestamp tick) >= marketOpenTime')) |
|
|
|
then do |
|
|
|
then do |
|
|
|
maybeCall notificationCallback state $ OrderNotification (orderId order) Submitted |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification (orderId order) Submitted |
|
|
|
executeAtTick state order tick |
|
|
|
executeAtTick state order tick |
|
|
|
else do |
|
|
|
else do |
|
|
|
let newOrder = order { orderState = Submitted } |
|
|
|
let newOrder = order { orderState = Submitted } |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s , pendingOrders = newOrder : pendingOrders s}, ())) |
|
|
|
atomicModifyIORef' state (\s -> (s { orders = M.insert (orderId order) newOrder $ orders s , pendingOrders = newOrder : pendingOrders s}, ())) |
|
|
|
maybeCall notificationCallback state $ OrderNotification (orderId order) Submitted |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification (orderId order) Submitted |
|
|
|
|
|
|
|
|
|
|
|
submitStopOrder _ _ = warningM "PaperBroker" $ "Not implemented: Submitted order: " ++ show order |
|
|
|
submitStopOrder _ _ = log Warning "PaperBroker" $ "Not implemented: Submitted order: " <> (T.pack . show) order |
|
|
|
submitStopMarketOrder _ _ = warningM "PaperBroker" $ "Not implemented: Submitted order: " ++ show order |
|
|
|
submitStopMarketOrder _ _ = log Warning "PaperBroker" $ "Not implemented: Submitted order: " <> (T.pack . show) order |
|
|
|
|
|
|
|
|
|
|
|
orderDatatype = case orderOperation order of |
|
|
|
orderDatatype = case orderOperation order of |
|
|
|
Buy -> BestOffer |
|
|
|
Buy -> BestOffer |
|
|
|
@ -230,7 +250,7 @@ pbCancelOrder :: IORef PaperBrokerState -> OrderId -> IO Bool |
|
|
|
pbCancelOrder state oid = do |
|
|
|
pbCancelOrder state oid = do |
|
|
|
atomicModifyIORef' state (\s -> (s { pendingOrders = L.filter (\o -> orderId o /= oid) (pendingOrders s), |
|
|
|
atomicModifyIORef' state (\s -> (s { pendingOrders = L.filter (\o -> orderId o /= oid) (pendingOrders s), |
|
|
|
orders = M.adjustWithKey (\_ v -> v { orderState = Cancelled }) oid (orders s) }, ())) |
|
|
|
orders = M.adjustWithKey (\_ v -> v { orderState = Cancelled }) oid (orders s) }, ())) |
|
|
|
maybeCall notificationCallback state $ OrderNotification oid Cancelled |
|
|
|
maybeCall notificationCallback state $ BackendOrderNotification oid Cancelled |
|
|
|
return True |
|
|
|
return True |
|
|
|
|
|
|
|
|
|
|
|
pbDestroyBroker :: IORef PaperBrokerState -> IO () |
|
|
|
pbDestroyBroker :: IORef PaperBrokerState -> IO () |
|
|
|
@ -240,8 +260,3 @@ pbDestroyBroker state = do |
|
|
|
Just tid -> killThread tid |
|
|
|
Just tid -> killThread tid |
|
|
|
Nothing -> return () |
|
|
|
Nothing -> return () |
|
|
|
|
|
|
|
|
|
|
|
{- |
|
|
|
|
|
|
|
pbGetOrder :: IORef PaperBrokerState -> OrderId -> IO (Maybe Order) |
|
|
|
|
|
|
|
pbGetOrder state oid = M.lookup oid . orders <$> readIORef state |
|
|
|
|
|
|
|
-} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|