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