Skip to content

Commit fb676fb

Browse files
committed
Improve performance of receiving more tick types
Same pattern as before, except we have some special cases _before_ the generic tick maps of just setting values from ids. ALSO includes 5 new tick types we didn't handle before in addition to more verbose error handling if the client receives unknown ticks (so we can add them when we find out about them instead of ignoring them for years by mistake). Now the Ticker object also has (if you subscribe to these ticks): - shortable: a SCORE of how shortable an instrument is (not shares, just 0-3) - volumeRate3Min: IBKR-provided volume metrics for finding acceleration opportunities at 3, 5, and 10 min average volume rates. - volumeRate5Min - volumeRate10Min - lastTimestamp: timestamp of the last trade event
1 parent a7e8eb6 commit fb676fb

File tree

2 files changed

+65
-54
lines changed

2 files changed

+65
-54
lines changed

ib_async/ticker.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class Ticker:
6161
last: float = nan
6262
lastSize: float = nan
6363
lastExchange: str = ""
64+
lastTimestamp: datetime | None = None
6465
prevBid: float = nan
6566
prevBidSize: float = nan
6667
prevAsk: float = nan
@@ -92,6 +93,10 @@ class Ticker:
9293
tradeCount: float = nan
9394
tradeRate: float = nan
9495
volumeRate: float = nan
96+
volumeRate3Min: float = nan
97+
volumeRate5Min: float = nan
98+
volumeRate10Min: float = nan
99+
shortable: float = nan
95100
shortableShares: float = nan
96101
indexFuturePremium: float = nan
97102
futuresOpenInterest: float = nan

ib_async/wrapper.py

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,50 @@
8888
OrderKeyType: TypeAlias = int | tuple[int, int]
8989
TickDict: TypeAlias = dict[int, str]
9090

91+
PRICE_TICK_MAP: Final[TickDict] = {
92+
6: "high",
93+
72: "high",
94+
7: "low",
95+
73: "low",
96+
9: "close",
97+
75: "close",
98+
14: "open",
99+
76: "open",
100+
15: "low13week",
101+
16: "high13week",
102+
17: "low26week",
103+
18: "high26week",
104+
19: "low52week",
105+
20: "high52week",
106+
35: "auctionPrice",
107+
37: "markPrice",
108+
50: "bidYield",
109+
103: "bidYield",
110+
51: "askYield",
111+
104: "askYield",
112+
52: "lastYield",
113+
}
114+
115+
116+
SIZE_TICK_MAP: Final[TickDict] = {
117+
8: "volume",
118+
74: "volume",
119+
63: "volumeRate3Min",
120+
64: "volumeRate5Min",
121+
65: "volumeRate10Min",
122+
21: "avVolume",
123+
27: "callOpenInterest",
124+
28: "putOpenInterest",
125+
29: "callVolume",
126+
30: "putVolume",
127+
34: "auctionVolume",
128+
36: "auctionImbalance",
129+
61: "regulatoryImbalance",
130+
86: "futuresOpenInterest",
131+
87: "avOptionVolume",
132+
89: "shortableShares",
133+
}
134+
91135
GENERIC_TICK_MAP: Final[TickDict] = {
92136
23: "histVolatility",
93137
24: "impliedVolatility",
@@ -860,36 +904,12 @@ def priceSizeTick(self, reqId: int, tickType: int, price: float, size: float):
860904
if size != ticker.lastSize:
861905
ticker.prevLastSize = ticker.lastSize
862906
ticker.lastSize = size
863-
elif tickType in {6, 72}:
864-
ticker.high = price
865-
elif tickType in {7, 73}:
866-
ticker.low = price
867-
elif tickType in {9, 75}:
868-
ticker.close = price
869-
elif tickType in {14, 76}:
870-
ticker.open = price
871-
elif tickType == 15:
872-
ticker.low13week = price
873-
elif tickType == 16:
874-
ticker.high13week = price
875-
elif tickType == 17:
876-
ticker.low26week = price
877-
elif tickType == 18:
878-
ticker.high26week = price
879-
elif tickType == 19:
880-
ticker.low52week = price
881-
elif tickType == 20:
882-
ticker.high52week = price
883-
elif tickType == 35:
884-
ticker.auctionPrice = price
885-
elif tickType == 37:
886-
ticker.markPrice = price
887-
elif tickType in {50, 103}:
888-
ticker.bidYield = price
889-
elif tickType in {51, 104}:
890-
ticker.askYield = price
891-
elif tickType == 52:
892-
ticker.lastYield = price
907+
else:
908+
assert (
909+
tickType in PRICE_TICK_MAP
910+
), f"Received tick {tickType=} {price=} but we don't have an attribute mapping for it? Triggered from {ticker.contract=}"
911+
912+
setattr(ticker, PRICE_TICK_MAP[tickType], price)
893913

894914
if price or size:
895915
tick = TickData(self.lastTime, tickType, price, size)
@@ -924,30 +944,12 @@ def tickSize(self, reqId: int, tickType: int, size: float):
924944
if size != ticker.lastSize:
925945
ticker.prevLastSize = ticker.lastSize
926946
ticker.lastSize = size
927-
elif tickType in {8, 74}:
928-
ticker.volume = size
929-
elif tickType == 21:
930-
ticker.avVolume = size
931-
elif tickType == 27:
932-
ticker.callOpenInterest = size
933-
elif tickType == 28:
934-
ticker.putOpenInterest = size
935-
elif tickType == 29:
936-
ticker.callVolume = size
937-
elif tickType == 30:
938-
ticker.putVolume = size
939-
elif tickType == 34:
940-
ticker.auctionVolume = size
941-
elif tickType == 36:
942-
ticker.auctionImbalance = size
943-
elif tickType == 61:
944-
ticker.regulatoryImbalance = size
945-
elif tickType == 86:
946-
ticker.futuresOpenInterest = size
947-
elif tickType == 87:
948-
ticker.avOptionVolume = size
949-
elif tickType == 89:
950-
ticker.shortableShares = size
947+
else:
948+
assert (
949+
tickType in SIZE_TICK_MAP
950+
), f"Received tick {tickType=} {price=} but we don't have an attribute mapping for it? Triggered from {ticker.contract=}"
951+
952+
setattr(ticker, SIZE_TICK_MAP[tickType], price)
951953

952954
if price or size:
953955
tick = TickData(self.lastTime, tickType, price, size)
@@ -1054,6 +1056,10 @@ def tickString(self, reqId: int, tickType: int, value: str):
10541056
ticker.askExchange = value
10551057
elif tickType == 84:
10561058
ticker.lastExchange = value
1059+
elif tickType == 45:
1060+
ticker.lastTimestamp = datetime.fromtimestamp(
1061+
int(value), self.defaultTimezone
1062+
)
10571063
elif tickType == 47:
10581064
# https://web.archive.org/web/20200725010343/https://interactivebrokers.github.io/tws-api/fundamental_ratios_tags.html
10591065
d = dict(

0 commit comments

Comments
 (0)