itchfeed 1.0.0__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- itch/__init__.py +11 -14
- itch/indicators.py +206 -206
- itch/messages.py +1600 -1600
- itch/parser.py +179 -180
- {itchfeed-1.0.0.dist-info → itchfeed-1.0.1.dist-info}/METADATA +224 -217
- itchfeed-1.0.1.dist-info/RECORD +9 -0
- {itchfeed-1.0.0.dist-info → itchfeed-1.0.1.dist-info}/licenses/LICENSE +21 -21
- itchfeed-1.0.0.dist-info/RECORD +0 -9
- {itchfeed-1.0.0.dist-info → itchfeed-1.0.1.dist-info}/WHEEL +0 -0
- {itchfeed-1.0.0.dist-info → itchfeed-1.0.1.dist-info}/top_level.txt +0 -0
itch/parser.py
CHANGED
@@ -1,180 +1,179 @@
|
|
1
|
-
from queue import Queue
|
2
|
-
from typing import BinaryIO, List
|
3
|
-
|
4
|
-
|
5
|
-
from itch.messages import MarketMessage
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
f"
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
message_type
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
)
|
1
|
+
from queue import Queue
|
2
|
+
from typing import BinaryIO, List
|
3
|
+
|
4
|
+
import itch.messages as msg
|
5
|
+
from itch.messages import MarketMessage, MESSAGES
|
6
|
+
class MessageParser(object):
|
7
|
+
"""
|
8
|
+
A market message parser for ITCH 5.0 data.
|
9
|
+
|
10
|
+
"""
|
11
|
+
|
12
|
+
def __init__(self, message_type: bytes = MESSAGES):
|
13
|
+
self.message_type = message_type
|
14
|
+
|
15
|
+
def read_message_from_file(
|
16
|
+
self,
|
17
|
+
file: BinaryIO,
|
18
|
+
cachesize: int = 4096,
|
19
|
+
) -> List[MarketMessage]:
|
20
|
+
max_message_size = 52
|
21
|
+
file_end_reached = False
|
22
|
+
|
23
|
+
data_buffer = file.read(cachesize)
|
24
|
+
buffer_len = len(data_buffer)
|
25
|
+
messages: List[MarketMessage] = []
|
26
|
+
|
27
|
+
while not file_end_reached:
|
28
|
+
if buffer_len < 2:
|
29
|
+
new_data = file.read(cachesize)
|
30
|
+
if not new_data:
|
31
|
+
break
|
32
|
+
data_buffer += new_data
|
33
|
+
buffer_len = len(data_buffer)
|
34
|
+
continue
|
35
|
+
|
36
|
+
if data_buffer[0:1] != b"\x00":
|
37
|
+
raise ValueError(
|
38
|
+
"Unexpected byte: " + str(data_buffer[0:1], encoding="ascii")
|
39
|
+
)
|
40
|
+
|
41
|
+
message_len = data_buffer[1]
|
42
|
+
total_len = 2 + message_len
|
43
|
+
|
44
|
+
if buffer_len < total_len:
|
45
|
+
# Wait for more data if message is incomplete
|
46
|
+
new_data = file.read(cachesize)
|
47
|
+
if not new_data:
|
48
|
+
break
|
49
|
+
data_buffer += new_data
|
50
|
+
buffer_len = len(data_buffer)
|
51
|
+
continue
|
52
|
+
message_data = data_buffer[2:total_len]
|
53
|
+
message = self.get_message_type(message_data)
|
54
|
+
|
55
|
+
if message.message_type in self.message_type:
|
56
|
+
messages.append(message)
|
57
|
+
|
58
|
+
if message.message_type == b"S": # System message
|
59
|
+
if message.event_code == b"C": # End of messages
|
60
|
+
break
|
61
|
+
|
62
|
+
# Update buffer
|
63
|
+
data_buffer = data_buffer[total_len:]
|
64
|
+
buffer_len = len(data_buffer)
|
65
|
+
|
66
|
+
if buffer_len < max_message_size and not file_end_reached:
|
67
|
+
new_data = file.read(cachesize)
|
68
|
+
if not new_data:
|
69
|
+
file_end_reached = True
|
70
|
+
else:
|
71
|
+
data_buffer += new_data
|
72
|
+
buffer_len = len(data_buffer)
|
73
|
+
|
74
|
+
return messages
|
75
|
+
|
76
|
+
def read_message_from_bytes(self, data: bytes):
|
77
|
+
"""
|
78
|
+
Process one or multiple ITCH binary messages from a raw bytes input.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
data (bytes): Binary blob containing one or more ITCH messages.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Queue: A queue containing parsed ITCH message objects.
|
85
|
+
|
86
|
+
Notes:
|
87
|
+
- Each message must be prefixed with a 0x00 header and a length byte.
|
88
|
+
- No buffering is done here — this is meant for real-time decoding.
|
89
|
+
"""
|
90
|
+
|
91
|
+
offset = 0
|
92
|
+
messages = Queue()
|
93
|
+
while offset + 2 <= len(data):
|
94
|
+
# Each message starts with: 1-byte header (0x00) 1-byte length
|
95
|
+
if data[offset : offset + 1] != b"\x00":
|
96
|
+
raise ValueError(
|
97
|
+
f"Unexpected start byte at offset {offset}: "
|
98
|
+
f"{str(data[offset : offset + 1], encoding='ascii')}"
|
99
|
+
)
|
100
|
+
|
101
|
+
msg_len = data[offset + 1]
|
102
|
+
total_len = 2 + msg_len
|
103
|
+
|
104
|
+
if offset + total_len > len(data):
|
105
|
+
break
|
106
|
+
|
107
|
+
raw_msg = data[offset + 2 : offset + total_len]
|
108
|
+
message = self.get_message_type(raw_msg)
|
109
|
+
|
110
|
+
if message.message_type in self.message_type:
|
111
|
+
messages.put(message)
|
112
|
+
|
113
|
+
if message.message_type == b"S": # System message
|
114
|
+
if message.event_code == b"C": # End of messages
|
115
|
+
break
|
116
|
+
|
117
|
+
offset += total_len
|
118
|
+
|
119
|
+
return messages
|
120
|
+
|
121
|
+
def get_message_type(self, message: bytes):
|
122
|
+
"""
|
123
|
+
Take an entire bytearray and return the appropriate ITCH message
|
124
|
+
instance based on the message type indicator (first byte of the message).
|
125
|
+
|
126
|
+
All message type indicators are single ASCII characters.
|
127
|
+
"""
|
128
|
+
message_type = message[0:1]
|
129
|
+
match message_type:
|
130
|
+
case b"S":
|
131
|
+
return msg.SystemEventMessage(message)
|
132
|
+
case b"R":
|
133
|
+
return msg.StockDirectoryMessage(message)
|
134
|
+
case b"H":
|
135
|
+
return msg.StockTradingActionMessage(message)
|
136
|
+
case b"Y":
|
137
|
+
return msg.RegSHOMessage(message)
|
138
|
+
case b"L":
|
139
|
+
return msg.MarketParticipantPositionMessage(message)
|
140
|
+
case b"V":
|
141
|
+
return msg.MWCBDeclineLeveMessage(message)
|
142
|
+
case b"W":
|
143
|
+
return msg.MWCBStatusMessage(message)
|
144
|
+
case b"K":
|
145
|
+
return msg.IPOQuotingPeriodUpdateMessage(message)
|
146
|
+
case b"J":
|
147
|
+
return msg.LULDAuctionCollarMessage(message)
|
148
|
+
case b"h":
|
149
|
+
return msg.OperationalHaltMessage(message)
|
150
|
+
case b"A":
|
151
|
+
return msg.AddOrderNoMPIAttributionMessage(message)
|
152
|
+
case b"F":
|
153
|
+
return msg.AddOrderMPIDAttribution(message)
|
154
|
+
case b"E":
|
155
|
+
return msg.OrderExecutedMessage(message)
|
156
|
+
case b"C":
|
157
|
+
return msg.OrderExecutedWithPriceMessage(message)
|
158
|
+
case b"X":
|
159
|
+
return msg.OrderCancelMessage(message)
|
160
|
+
case b"D":
|
161
|
+
return msg.OrderDeleteMessage(message)
|
162
|
+
case b"U":
|
163
|
+
return msg.OrderReplaceMessage(message)
|
164
|
+
case b"P":
|
165
|
+
return msg.NonCrossTradeMessage(message)
|
166
|
+
case b"Q":
|
167
|
+
return msg.CrossTradeMessage(message)
|
168
|
+
case b"B":
|
169
|
+
return msg.BrokenTradeMessage(message)
|
170
|
+
case b"I":
|
171
|
+
return msg.NOIIMessage(message)
|
172
|
+
case b"N":
|
173
|
+
return msg.RetailPriceImprovementIndicator(message)
|
174
|
+
case b"O":
|
175
|
+
return msg.DLCRMessage(message)
|
176
|
+
case _:
|
177
|
+
raise ValueError(
|
178
|
+
f"Unknown message type: {message_type.decode(encoding='ascii')}"
|
179
|
+
)
|