This commit is contained in:
Stefan Dresselhaus
2022-03-14 20:12:24 +01:00
commit befb1ab1eb
23 changed files with 1973 additions and 0 deletions

View File

@ -0,0 +1,95 @@
{-# LANGUAGE TypeApplications #-}
module IBClient.Connection where
import Import
import Data.Binary
import Network.Socket
import Network.Socket.ByteString
import qualified Control.Exception as E
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.Text as T
forkClient :: App -> IO ()
forkClient app = runRIO app $ withRunInIO $ \run -> withSocketsDo $ do
let refs = twsConnectionRefs $ appRefs app
toSend = twsConnectionSend refs
toRecieve = twsConnectionRecieve refs
cStatus = twsConnectionStatus refs
debugSend x = run $ logDebug (display $ T.pack $ "SENT : " <> show x)
debugRecv x = run $ logDebug (display $ T.pack $ "RECIEVED: " <> show x)
connHost <- readTVarIO $ twsConnectionRefsHost refs
connPort <- readTVarIO $ twsConnectionRefsPort refs
atomically $ modifyTVar' cStatus (const TWSConnecting)
-- TODO: throws IO-Exeption instead of returning empty list -> handle!
addr:_ <- getAddrInfo (Just $ defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV], addrProtocol = 0, addrSocketType = Stream}) (Just connHost) (Just connPort)
run $ logDebug $ displayShow addr
E.bracketOnError (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)) close $ \sock -> do
connect sock $ addrAddress addr
let connStr = BS8.pack "API\0\0\0\0\tv100..157"
sendAll sock connStr
debugSend connStr
answer <- recvAll sock
run $ logDebug (displayShow (decode . LBS.fromStrict <$> answer :: Maybe IBGenericMessage))
-- if we have the answer we are connected
let idStr = LBS.toStrict $ encode $ IB_StartAPI "2" "69" -- version 2, client-id: 69
sendAll sock idStr
debugSend idStr
atomically $ modifyTVar' cStatus (const TWSConnected)
run $ logInfo $ display ("Connected to TWS" :: Text)
let go True = do
-- abort connection, close everything
return ()
go False = do
-- race: wait for MSG in Queue or for answer on socket
input <- race (atomically $ readTQueue toSend)
(recvAll sock)
case input of
-- we want to disconnect
Left IBDisconnect -> go True
Left (Msg_IB_OUT x) -> do
let msg = LBS.toStrict $ encode x
debugSend msg
sendAll sock msg
-- we lost connection
Right Nothing -> do
atomically $ do
writeTQueue toRecieve IBServerGone
modifyTVar' cStatus (const TWSDisconnected)
run $ logWarn $ display $ T.pack "Lost connection to TWS, reconnecting..."
forkClient app
Right (Just x) -> do debugRecv x
parseMessage x
where
parseMessage "" = return ()
parseMessage m = do
let d = decodeOrFail @IB_IN (LBS.fromStrict m)
case d of
Right (rest, offset, result) -> do
atomically $ writeTQueue toRecieve (Msg_IB_IN result)
parseMessage (LBS.toStrict rest)
Left (rest, offset, err) -> do
run $ logInfo (display $ T.pack $ "Could not understand message: "<> ppShow err <> " ... skipping.\nRAW: " <> show m)
if m == "\NUL" then do
run $ logInfo (display $ T.pack "killing NUL")
parseMessage (LBS.toStrict $ LBS.tail rest)
else
parseMessage (LBS.toStrict rest)
go False
go False
recvAll :: Socket -> IO (Maybe ByteString)
recvAll s = do
d <- recv s 4096
let l = BS.length d
if
| l == 0 -> return Nothing
| l < 4096 -> return $ Just d
| l == 4096 -> do
next <- recvAll s
return $ (d<>) <$> next
| otherwise -> error "recvAll: recv got more bytes then requested. Impossible according to RFC"

581
src/IBClient/Types.hs Normal file
View File

@ -0,0 +1,581 @@
{-# HLINT ignore "Use camelCase" #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# OPTIONS_GHC -Wno-partial-fields #-}
module IBClient.Types where
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import Data.Text.Encoding
import Data.Maybe
import Data.Default
import Data.Aeson (FromJSON, ToJSON)
import GHC.Enum (Enum(..))
import RIO
import RIO.List
import RIO.List.Partial
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Lazy.Char8 as LBS8
import qualified Debug.Trace as D
data Msg_IB_OUT = IBDisconnect
| Msg_IB_OUT IB_OUT
deriving (Show, Eq)
data Msg_IB_IN = IBServerGone
| Msg_IB_IN IB_IN
deriving (Show, Eq)
data IBTypes = IBString ByteString
| IBBool Bool
| IBArray [ByteString]
deriving (Show, Eq)
toBS :: IBTypes -> ByteString
toBS (IBString t) = t
toBS (IBArray a) = (BS8.pack . show . length $ a) <> BS.intercalate "\0" a <> "\0"
toBS (IBBool True) = "1"--BS.pack [0,0,0,1] -- bool == 32-bit int in IB
toBS (IBBool False) = "0"--BS.pack [0,0,0,0] -- bool == 32-bit int in IB
newtype IBGenericMessage = IBGenericMessage
{ fields :: [IBTypes]
} deriving (Show, Eq)
instance Binary IBGenericMessage where
put (IBGenericMessage f) = do
let msg = BS.intercalate "\0" (toBS <$> f) <> "\0"
putWord32be . fromIntegral . BS.length $ msg
putByteString msg
get = do
len <- getWord32be
D.traceShow len $ return ()
fields <- BS.split 0 . BS.init <$> getByteString (fromIntegral len)
D.traceShow fields $ return ()
return $ IBGenericMessage $ IBString <$> fields
data IB_OUT = IB_StartAPI { version :: Text, clientId :: Text }
| IB_RequestPositions
| IB_RequestAccountData { subscribe :: Bool, acctCode :: Text }
| IB_RequestMatchingSymbol { reqId :: Int, symbol :: Text }
| IB_RequestMarketDataType { dataType :: IBMarketDataType }
| IB_RequestMktData { tickerId :: Int, contract :: IBContract, genericTickList :: Text, snapshot :: Bool, regulatorySnapshot :: Bool }
deriving (Show, Eq)
tToIB :: Text -> IBTypes
tToIB = IBString . encodeUtf8
iToIB :: Int -> IBTypes
iToIB = IBString . BS8.pack . show
fToIB :: Float -> IBTypes
fToIB = IBString . BS8.pack . show
clToIB :: [IBComboLeg] -> [IBTypes]
clToIB as = iToIB (length as) : concatMap (\IBComboLeg{..} -> [iToIB conId, iToIB ratio, tToIB action, tToIB exchange]) as
dncToIB :: Maybe IBDeltaNeutralContract -> [IBTypes]
dncToIB Nothing = []
dncToIB (Just IBDeltaNeutralContract{..}) = [iToIB 1, iToIB conId, fToIB delta, fToIB price]
instance Binary IB_OUT where
put (IB_StartAPI v c) = put (IBGenericMessage [IBString "71", tToIB v, tToIB c, IBString ""])
put IB_RequestPositions = put (IBGenericMessage [IBString "61", IBString "v"])
put (IB_RequestAccountData s a) = put (IBGenericMessage [IBString "6", IBString "2", IBBool s, tToIB a])
put (IB_RequestMatchingSymbol i s) = put (IBGenericMessage [IBString "81", iToIB i, tToIB s])
put (IB_RequestMktData t IBContract{..} l s r) = put $ D.traceShowId (IBGenericMessage $ [IBString "1", IBString "11", iToIB t, iToIB conId, tToIB symbol, tToIB secType, tToIB lastTradeDate, fToIB strike, tToIB right, tToIB multiplier, tToIB exchange, tToIB primaryExchange, tToIB currency, tToIB localSymbol, tToIB tradingClass] <> clToIB comboLegs <> dncToIB deltaNeutralContract <> [tToIB l, iToIB (if s then 1 else 0), iToIB (if r then 1 else 0), tToIB ""])
put (IB_RequestMarketDataType t) = put (IBGenericMessage [IBString "59", IBString "1", iToIB $ fromEnum t])
get = do f <- fmap (\(IBString x) -> x) . fields <$> get
case headMaybe f of
Just "71" -> return $ IB_StartAPI (decodeUtf8 $ f!!1) (decodeUtf8 $ f!!2)
Just "6" -> return $ IB_RequestAccountData {- ignore version -} (all (==0) . BS.unpack $ f!!2) (decodeUtf8 $ f!!3)
Just "59" -> return $ IB_RequestMarketDataType {- ignore version -} (toEnum . fromJust . readMaybe . BS8.unpack $ f!!2)
Just x -> fail $ "unkonwn IB_OUT type" <> BS8.unpack x
Nothing -> fail $ "No Fields"
data IBContract = IBContract
{ conId :: Int
, symbol :: Text
, secType :: Text
, lastTradeDate :: Text
, strike :: Float
, right :: Text
, multiplier :: Text
, exchange :: Text -- ^ can be SMART
, primaryExchange :: Text -- ^ actual exchange - MUST NOT BE SMART
, currency :: Text
, localSymbol :: Text
, tradingClass :: Text
, includeExpired :: Bool
, secIdType :: Text
, secId :: Text
, comboLegsDescrip :: Text -- ^ received in open order 14 and up for all combos
, comboLegs :: [IBComboLeg]
, deltaNeutralContract :: Maybe IBDeltaNeutralContract
} deriving (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Default IBContract where
def = IBContract 0 "" "" "" 0 "" "" "" "" "" "" "" False "" "" "" [] Nothing
data IBComboLeg = IBComboLeg
{ conId :: Int
, ratio :: Int
, action :: Text -- ^ BUY/SELL/SSHORT
, exchange :: Text
, openClose :: Int -- ^ LegOpenClose enum values
, shortSaleSlot :: Int
, designatedLocation :: Text
, exemptCode :: Int
} deriving (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Default IBComboLeg where
def = IBComboLeg 0 0 "" "" 0 0 "" (negate 1)
data IBDeltaNeutralContract = IBDeltaNeutralContract
{ conId :: Int
, delta :: Float
, price :: Float
} deriving (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Default IBDeltaNeutralContract where
def = IBDeltaNeutralContract 0 0 0
data IBMarketDataType = RealTime
| Frozen
| Delayed
| DelayedFrozen
deriving (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
instance Default IBMarketDataType where
def = DelayedFrozen
instance Enum IBMarketDataType where
toEnum 1 = RealTime
toEnum 2 = Frozen
toEnum 3 = Delayed
toEnum 4 = DelayedFrozen
toEnum _ = def
fromEnum RealTime = 1
fromEnum Frozen = 2
fromEnum Delayed = 3
fromEnum DelayedFrozen = 4
data IBTickType = Unknown Int
| IBTickType_Bid_Size
| IBTickType_Bid_Price
| IBTickType_Ask_Price
| IBTickType_Ask_Size
| IBTickType_Last_Price
| IBTickType_Last_Size
| IBTickType_High
| IBTickType_Low
| IBTickType_Volume
| IBTickType_Close_Price
| IBTickType_Bid_Option_Computation
| IBTickType_Ask_Option_Computation
| IBTickType_Last_Option_Computation
| IBTickType_Model_Option_Computation
| IBTickType_Open_Tick
| IBTickType_Low_13_Weeks
| IBTickType_High_13_Weeks
| IBTickType_Low_26_Weeks
| IBTickType_High_26_Weeks
| IBTickType_Low_52_Weeks
| IBTickType_High_52_Weeks
| IBTickType_Average_Volume
| IBTickType_Open_Interest
| IBTickType_Option_Historical_Volatility
| IBTickType_Option_Implied_Volatility
| IBTickType_Option_Bid_Exchange
| IBTickType_Option_Ask_Exchange
| IBTickType_Option_Call_Open_Interest
| IBTickType_Option_Put_Open_Interest
| IBTickType_Option_Call_Volume
| IBTickType_Option_Put_Volume
| IBTickType_Index_Future_Premium
| IBTickType_Bid_Exchange
| IBTickType_Ask_Exchange
| IBTickType_Auction_Volume
| IBTickType_Auction_Price
| IBTickType_Auction_Imbalance
| IBTickType_Mark_Price
| IBTickType_Bid_EFP_Computation
| IBTickType_Ask_EFP_Computation
| IBTickType_Last_EFP_Computation
| IBTickType_Open_EFP_Computation
| IBTickType_High_EFP_Computation
| IBTickType_Low_EFP_Computation
| IBTickType_Close_EFP_Computation
| IBTickType_Last_Timestamp
| IBTickType_Shortable
| IBTickType_RT_Volume
| IBTickType_Halted
| IBTickType_Bid_Yield
| IBTickType_Ask_Yield
| IBTickType_Last_Yield
| IBTickType_Custom_Option_Computation
| IBTickType_Trade_Count
| IBTickType_Trade_Rate
| IBTickType_Volume_Rate
| IBTickType_Last_RTH_Trade
| IBTickType_RT_Historical_Volatility
| IBTickType_IB_Dividends
| IBTickType_Bond_Factor_Multiplier
| IBTickType_Regulatory_Imbalance
| IBTickType_News
| IBTickType_ShortTerm_Volume_3_Minutes
| IBTickType_ShortTerm_Volume_5_Minutes
| IBTickType_ShortTerm_Volume_10_Minutes
| IBTickType_Delayed_Bid
| IBTickType_Delayed_Ask
| IBTickType_Delayed_Last
| IBTickType_Delayed_Bid_Size
| IBTickType_Delayed_Ask_Size
| IBTickType_Delayed_Last_Size
| IBTickType_Delayed_High_Price
| IBTickType_Delayed_Low_Price
| IBTickType_Delayed_Volume
| IBTickType_Delayed_Close
| IBTickType_Delayed_Open
| IBTickType_RT_Trade_Volume
| IBTickType_Creditman_mark_price
| IBTickType_Creditman_slow_mark_price
| IBTickType_Delayed_Bid_Option
| IBTickType_Delayed_Ask_Option
| IBTickType_Delayed_Last_Option
| IBTickType_Delayed_Model_Option
| IBTickType_Last_Exchange
| IBTickType_Last_Regulatory_Time
| IBTickType_Futures_Open_Interest
| IBTickType_Average_Option_Volume
| IBTickType_Delayed_Last_Timestamp
| IBTickType_Shortable_Shares
| IBTickType_ETF_Nav_Close
| IBTickType_ETF_Nav_Prior_Close
| IBTickType_ETF_Nav_Bid
| IBTickType_ETF_Nav_Ask
| IBTickType_ETF_Nav_Last
| IBTickType_ETF_Nav_Frozen_Last
| IBTickType_ETF_Nav_High
| IBTickType_ETF_Nav_Low
deriving (Show, Eq)
instance Enum IBTickType where
toEnum 0 = IBTickType_Bid_Size
toEnum 1 = IBTickType_Bid_Price
toEnum 2 = IBTickType_Ask_Price
toEnum 3 = IBTickType_Ask_Size
toEnum 4 = IBTickType_Last_Price
toEnum 5 = IBTickType_Last_Size
toEnum 6 = IBTickType_High
toEnum 7 = IBTickType_Low
toEnum 8 = IBTickType_Volume
toEnum 9 = IBTickType_Close_Price
toEnum 10 = IBTickType_Bid_Option_Computation
toEnum 11 = IBTickType_Ask_Option_Computation
toEnum 12 = IBTickType_Last_Option_Computation
toEnum 13 = IBTickType_Model_Option_Computation
toEnum 14 = IBTickType_Open_Tick
toEnum 15 = IBTickType_Low_13_Weeks
toEnum 16 = IBTickType_High_13_Weeks
toEnum 17 = IBTickType_Low_26_Weeks
toEnum 18 = IBTickType_High_26_Weeks
toEnum 19 = IBTickType_Low_52_Weeks
toEnum 20 = IBTickType_High_52_Weeks
toEnum 21 = IBTickType_Average_Volume
toEnum 22 = IBTickType_Open_Interest
toEnum 23 = IBTickType_Option_Historical_Volatility
toEnum 24 = IBTickType_Option_Implied_Volatility
toEnum 25 = IBTickType_Option_Bid_Exchange
toEnum 26 = IBTickType_Option_Ask_Exchange
toEnum 27 = IBTickType_Option_Call_Open_Interest
toEnum 28 = IBTickType_Option_Put_Open_Interest
toEnum 29 = IBTickType_Option_Call_Volume
toEnum 30 = IBTickType_Option_Put_Volume
toEnum 31 = IBTickType_Index_Future_Premium
toEnum 32 = IBTickType_Bid_Exchange
toEnum 33 = IBTickType_Ask_Exchange
toEnum 34 = IBTickType_Auction_Volume
toEnum 35 = IBTickType_Auction_Price
toEnum 36 = IBTickType_Auction_Imbalance
toEnum 37 = IBTickType_Mark_Price
toEnum 38 = IBTickType_Bid_EFP_Computation
toEnum 39 = IBTickType_Ask_EFP_Computation
toEnum 40 = IBTickType_Last_EFP_Computation
toEnum 41 = IBTickType_Open_EFP_Computation
toEnum 42 = IBTickType_High_EFP_Computation
toEnum 43 = IBTickType_Low_EFP_Computation
toEnum 44 = IBTickType_Close_EFP_Computation
toEnum 45 = IBTickType_Last_Timestamp
toEnum 46 = IBTickType_Shortable
toEnum 48 = IBTickType_RT_Volume
toEnum 49 = IBTickType_Halted
toEnum 50 = IBTickType_Bid_Yield
toEnum 51 = IBTickType_Ask_Yield
toEnum 52 = IBTickType_Last_Yield
toEnum 53 = IBTickType_Custom_Option_Computation
toEnum 54 = IBTickType_Trade_Count
toEnum 55 = IBTickType_Trade_Rate
toEnum 56 = IBTickType_Volume_Rate
toEnum 57 = IBTickType_Last_RTH_Trade
toEnum 58 = IBTickType_RT_Historical_Volatility
toEnum 59 = IBTickType_IB_Dividends
toEnum 60 = IBTickType_Bond_Factor_Multiplier
toEnum 61 = IBTickType_Regulatory_Imbalance
toEnum 62 = IBTickType_News
toEnum 63 = IBTickType_ShortTerm_Volume_3_Minutes
toEnum 64 = IBTickType_ShortTerm_Volume_5_Minutes
toEnum 65 = IBTickType_ShortTerm_Volume_10_Minutes
toEnum 66 = IBTickType_Delayed_Bid
toEnum 67 = IBTickType_Delayed_Ask
toEnum 68 = IBTickType_Delayed_Last
toEnum 69 = IBTickType_Delayed_Bid_Size
toEnum 70 = IBTickType_Delayed_Ask_Size
toEnum 71 = IBTickType_Delayed_Last_Size
toEnum 72 = IBTickType_Delayed_High_Price
toEnum 73 = IBTickType_Delayed_Low_Price
toEnum 74 = IBTickType_Delayed_Volume
toEnum 75 = IBTickType_Delayed_Close
toEnum 76 = IBTickType_Delayed_Open
toEnum 77 = IBTickType_RT_Trade_Volume
toEnum 78 = IBTickType_Creditman_mark_price
toEnum 79 = IBTickType_Creditman_slow_mark_price
toEnum 80 = IBTickType_Delayed_Bid_Option
toEnum 81 = IBTickType_Delayed_Ask_Option
toEnum 82 = IBTickType_Delayed_Last_Option
toEnum 83 = IBTickType_Delayed_Model_Option
toEnum 84 = IBTickType_Last_Exchange
toEnum 85 = IBTickType_Last_Regulatory_Time
toEnum 86 = IBTickType_Futures_Open_Interest
toEnum 87 = IBTickType_Average_Option_Volume
toEnum 88 = IBTickType_Delayed_Last_Timestamp
toEnum 89 = IBTickType_Shortable_Shares
toEnum 92 = IBTickType_ETF_Nav_Close
toEnum 93 = IBTickType_ETF_Nav_Prior_Close
toEnum 94 = IBTickType_ETF_Nav_Bid
toEnum 95 = IBTickType_ETF_Nav_Ask
toEnum 96 = IBTickType_ETF_Nav_Last
toEnum 97 = IBTickType_ETF_Nav_Frozen_Last
toEnum 98 = IBTickType_ETF_Nav_High
toEnum 99 = IBTickType_ETF_Nav_Low
toEnum x = D.trace ("Unknown tick-type-id: " <> show x) $ Unknown x
fromEnum (Unknown x) = x
fromEnum IBTickType_Bid_Size = 0
fromEnum IBTickType_Bid_Price = 1
fromEnum IBTickType_Ask_Price = 2
fromEnum IBTickType_Ask_Size = 3
fromEnum IBTickType_Last_Price = 4
fromEnum IBTickType_Last_Size = 5
fromEnum IBTickType_High = 6
fromEnum IBTickType_Low = 7
fromEnum IBTickType_Volume = 8
fromEnum IBTickType_Close_Price = 9
fromEnum IBTickType_Bid_Option_Computation = 10
fromEnum IBTickType_Ask_Option_Computation = 11
fromEnum IBTickType_Last_Option_Computation = 12
fromEnum IBTickType_Model_Option_Computation = 13
fromEnum IBTickType_Open_Tick = 14
fromEnum IBTickType_Low_13_Weeks = 15
fromEnum IBTickType_High_13_Weeks = 16
fromEnum IBTickType_Low_26_Weeks = 17
fromEnum IBTickType_High_26_Weeks = 18
fromEnum IBTickType_Low_52_Weeks = 19
fromEnum IBTickType_High_52_Weeks = 20
fromEnum IBTickType_Average_Volume = 21
fromEnum IBTickType_Open_Interest = 22
fromEnum IBTickType_Option_Historical_Volatility = 23
fromEnum IBTickType_Option_Implied_Volatility = 24
fromEnum IBTickType_Option_Bid_Exchange = 25
fromEnum IBTickType_Option_Ask_Exchange = 26
fromEnum IBTickType_Option_Call_Open_Interest = 27
fromEnum IBTickType_Option_Put_Open_Interest = 28
fromEnum IBTickType_Option_Call_Volume = 29
fromEnum IBTickType_Option_Put_Volume = 30
fromEnum IBTickType_Index_Future_Premium = 31
fromEnum IBTickType_Bid_Exchange = 32
fromEnum IBTickType_Ask_Exchange = 33
fromEnum IBTickType_Auction_Volume = 34
fromEnum IBTickType_Auction_Price = 35
fromEnum IBTickType_Auction_Imbalance = 36
fromEnum IBTickType_Mark_Price = 37
fromEnum IBTickType_Bid_EFP_Computation = 38
fromEnum IBTickType_Ask_EFP_Computation = 39
fromEnum IBTickType_Last_EFP_Computation = 40
fromEnum IBTickType_Open_EFP_Computation = 41
fromEnum IBTickType_High_EFP_Computation = 42
fromEnum IBTickType_Low_EFP_Computation = 43
fromEnum IBTickType_Close_EFP_Computation = 44
fromEnum IBTickType_Last_Timestamp = 45
fromEnum IBTickType_Shortable = 46
fromEnum IBTickType_RT_Volume = 48
fromEnum IBTickType_Halted = 49
fromEnum IBTickType_Bid_Yield = 50
fromEnum IBTickType_Ask_Yield = 51
fromEnum IBTickType_Last_Yield = 52
fromEnum IBTickType_Custom_Option_Computation = 53
fromEnum IBTickType_Trade_Count = 54
fromEnum IBTickType_Trade_Rate = 55
fromEnum IBTickType_Volume_Rate = 56
fromEnum IBTickType_Last_RTH_Trade = 57
fromEnum IBTickType_RT_Historical_Volatility = 58
fromEnum IBTickType_IB_Dividends = 59
fromEnum IBTickType_Bond_Factor_Multiplier = 60
fromEnum IBTickType_Regulatory_Imbalance = 61
fromEnum IBTickType_News = 62
fromEnum IBTickType_ShortTerm_Volume_3_Minutes = 63
fromEnum IBTickType_ShortTerm_Volume_5_Minutes = 64
fromEnum IBTickType_ShortTerm_Volume_10_Minutes = 65
fromEnum IBTickType_Delayed_Bid = 66
fromEnum IBTickType_Delayed_Ask = 67
fromEnum IBTickType_Delayed_Last = 68
fromEnum IBTickType_Delayed_Bid_Size = 69
fromEnum IBTickType_Delayed_Ask_Size = 70
fromEnum IBTickType_Delayed_Last_Size = 71
fromEnum IBTickType_Delayed_High_Price = 72
fromEnum IBTickType_Delayed_Low_Price = 73
fromEnum IBTickType_Delayed_Volume = 74
fromEnum IBTickType_Delayed_Close = 75
fromEnum IBTickType_Delayed_Open = 76
fromEnum IBTickType_RT_Trade_Volume = 77
fromEnum IBTickType_Creditman_mark_price = 78
fromEnum IBTickType_Creditman_slow_mark_price = 79
fromEnum IBTickType_Delayed_Bid_Option = 80
fromEnum IBTickType_Delayed_Ask_Option = 81
fromEnum IBTickType_Delayed_Last_Option = 82
fromEnum IBTickType_Delayed_Model_Option = 83
fromEnum IBTickType_Last_Exchange = 84
fromEnum IBTickType_Last_Regulatory_Time = 85
fromEnum IBTickType_Futures_Open_Interest = 86
fromEnum IBTickType_Average_Option_Volume = 87
fromEnum IBTickType_Delayed_Last_Timestamp = 88
fromEnum IBTickType_Shortable_Shares = 89
fromEnum IBTickType_ETF_Nav_Close = 92
fromEnum IBTickType_ETF_Nav_Prior_Close = 93
fromEnum IBTickType_ETF_Nav_Bid = 94
fromEnum IBTickType_ETF_Nav_Ask = 95
fromEnum IBTickType_ETF_Nav_Last = 96
fromEnum IBTickType_ETF_Nav_Frozen_Last = 97
fromEnum IBTickType_ETF_Nav_High = 98
fromEnum IBTickType_ETF_Nav_Low = 99
type IB_DerivativeSecType = Text
data IB_SymbolSample = IB_SymbolSample
{ symId :: Int
, symbol :: Text
, secType :: Text
, primaryExchange :: Text
, currency :: Text
, derivatives :: [IB_DerivativeSecType]
} deriving (Show, Eq)
data IB_IN = IB_PositionData
| IB_ManagedAccts { accounts :: [Text] }
| IB_NextValidID { orderID :: Int }
| IB_ErrorMsg { errorID :: Int, errorCode :: Int, errorMsg :: Text }
| IB_AccountValue { key :: Text, value :: Text, currency :: Text, accountName :: Text }
| IB_AccountUpdateTime { time :: Text }
| IB_PortfolioValue { contract :: IBContract, position :: Float, marketPrice :: Float, marketValue :: Float, averageCost :: Float, unrealizedPNL :: Float, realizedPNL :: Float, accountName :: Text }
| IB_SymbolSamples { nextId :: Int, samples :: [IB_SymbolSample] }
| IB_MarketDataType { tickerId :: Int, dataType :: IBMarketDataType }
| IB_TickReqParams { tickerId :: Int, minTick :: Float, bboExchange :: Text, snapshotPermissions :: Int }
| IB_TickPrice { tickerId :: Int, tickType :: IBTickType, price :: Float, size :: Int, attrMask :: Int }
| IB_TickSize { tickerId :: Int, fieldId :: Int, size :: Int } -- TODO: field is an enum
| IB_TickString { tickerId :: Int, tickType :: IBTickType, content :: Text }
deriving (Show, Eq)
voidVersion :: LBS.ByteString -> LBS.ByteString -> Get ()
voidVersion t v = do
version <- getLazyByteStringNul
when (version /= v) $ D.trace ("Unexpected Version '" <> LBS8.unpack version <> "' for Message-Type " <> LBS8.unpack t <> ". Expected: '" <> LBS8.unpack v <> "'.") (return ())
instance Binary IB_IN where
put (IB_ErrorMsg i c m) = put (IBGenericMessage [IBString "4", IBString "2", IBString $ BS8.pack $ show i, IBString $ BS8.pack $ show c, tToIB m])
put (IB_AccountValue k v c n) = put (IBGenericMessage [IBString "6", IBString "2", tToIB k, tToIB v, tToIB c, tToIB n])
put (IB_PortfolioValue IBContract{..} p pp v c u r n) = put $ IBGenericMessage [ IBString "7", IBString "8" -- id/version
, iToIB conId, tToIB symbol, tToIB secType, tToIB lastTradeDate, fToIB strike, tToIB right, tToIB multiplier, tToIB primaryExchange, tToIB currency, tToIB localSymbol, tToIB tradingClass -- contract
, fToIB p, fToIB pp, fToIB v, fToIB c, fToIB u, fToIB r, tToIB n
]
put (IB_AccountUpdateTime t) = put (IBGenericMessage [IBString "8", IBString "1", tToIB t])
put (IB_NextValidID v) = put (IBGenericMessage [IBString "9", IBString "1", IBString $ BS8.pack $ show v])
put (IB_ManagedAccts a) = put (IBGenericMessage [IBString "15", IBArray $ encodeUtf8 <$> a])
put IB_PositionData = put (IBGenericMessage [IBString "61"])
put IB_SymbolSamples{} = error "not implemented"
put IB_MarketDataType{} = error "not implemented"
put IB_TickReqParams{} = error "not implemented"
put IB_TickPrice{} = error "not implemented"
put IB_TickSize{} = error "not implemented"
put IB_TickString{} = error "not implemented"
--put (IB_SymbolSamples r s) = put (IBGenericMessage [IBString "79", IBString "1", iToIB r, IBArray $ s]) TODO: FIXME
get = do
msglen <- getWord32be
when (msglen == 0) $ fail "empty message"
ident <- return <$> getLazyByteStringNul
case ident of
Just "1" -> do
voidVersion "1" "6"
IB_TickPrice <$> ib2int <*> (toEnum <$> ib2int) <*> ib2f <*> ib2int <*> ib2int
Just "2" -> do
voidVersion "2" "6"
IB_TickSize <$> ib2int <*> ib2int <*> ib2int
Just "4" -> do
voidVersion "4" "2"
IB_ErrorMsg <$> ib2int <*> ib2int <*> ib2txt
Just "6" -> do
voidVersion "6" "2"
IB_AccountValue <$> ib2txt <*> ib2txt <*> ib2txt <*> ib2txt
Just "7" -> do
voidVersion "7" "8"
c <- IBContract <$> ib2int <*> ib2txt <*> ib2txt <*> ib2txt <*> ib2f <*> ib2txt <*> ib2txt <*> pure "" <*> ib2txt <*> ib2txt <*> ib2txt <*> ib2txt
IB_PortfolioValue (c False "" "" "" [] Nothing) <$> ib2f <*> ib2f <*> ib2f <*> ib2f <*> ib2f <*> ib2f <*> ib2txt
Just "8" -> do
voidVersion "8" "1"
IB_AccountUpdateTime <$> ib2txt
Just "9" -> do
voidVersion "9" "1"
IB_NextValidID <$> ib2int
Just "15" -> do
len <- ib2int
IB_ManagedAccts <$> forM [1..len] (const ib2txt)
Just "46" -> do
voidVersion "46" "6"
IB_TickString <$> ib2int <*> (toEnum <$> ib2int) <*> ib2txt
Just "58" -> do
voidVersion "58" "1"
IB_MarketDataType <$> ib2int <*> (toEnum <$> ib2int)
Just "61" -> return IB_PositionData
Just "79" -> do
reqId <- ib2int
len <- ib2int
symsamples <- forM [1..len] $ const $ do
f <- IB_SymbolSample <$> ib2int <*> ib2txt <*> ib2txt <*> ib2txt <*> ib2txt
n <- ib2int
derivatives <- forM [1..n] $ const ib2txt
return $ f derivatives
return $ IB_SymbolSamples reqId symsamples
Just "81" -> do
IB_TickReqParams <$> ib2int <*> ib2f <*> ib2txt <*> ib2int
Just x -> do
payload <- getByteString (fromIntegral msglen - (if null ident then 0 else length ident + 1) - 1) -- drop rest of message
D.trace ("Payload for "<> LBS8.unpack x <> " not understood: " <> show (IBGenericMessage $ fmap IBString . BS.split 0 . BS.init $ payload)) $ return ()
fail $ "unkonwn IB_IN type " <> LBS8.unpack x
Nothing -> fail "Cannot decode Message: no identifier"
ib2int :: Get Int
ib2int = fromMaybe (-1) . readMaybe . LBS8.unpack <$> getLazyByteStringNul
ib2f :: Get Float
ib2f = fromMaybe (-1) . readMaybe . LBS8.unpack <$> getLazyByteStringNul
ib2txt :: Get Text
ib2txt = decodeUtf8 . LBS.toStrict <$> getLazyByteStringNul