itchfeed 1.0.4__py3-none-any.whl → 1.0.5__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/messages.py +50 -31
- itch/parser.py +92 -73
- {itchfeed-1.0.4.dist-info → itchfeed-1.0.5.dist-info}/METADATA +13 -13
- itchfeed-1.0.5.dist-info/RECORD +9 -0
- itchfeed-1.0.4.dist-info/RECORD +0 -9
- {itchfeed-1.0.4.dist-info → itchfeed-1.0.5.dist-info}/WHEEL +0 -0
- {itchfeed-1.0.4.dist-info → itchfeed-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {itchfeed-1.0.4.dist-info → itchfeed-1.0.5.dist-info}/top_level.txt +0 -0
itch/messages.py
CHANGED
@@ -13,13 +13,21 @@ class MarketMessage(object):
|
|
13
13
|
|
14
14
|
All Message have the following attributes:
|
15
15
|
- message_type: A single letter that identify the message
|
16
|
+
- timestamp: Time at which the message was generated (Nanoseconds past midnight)
|
17
|
+
- stock_locate: Locate code identifying the security
|
18
|
+
- tracking_number: Nasdaq internal tracking number
|
19
|
+
|
20
|
+
The following attributes are not part of the message, but are used to describe the message:
|
16
21
|
- description: Describe the message
|
17
22
|
- message_format: string format using to unpack the message
|
18
23
|
- message_pack_format: string format using to pack the message
|
19
24
|
- message_size: The size in bytes of the message
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
|
26
|
+
# NOTE:
|
27
|
+
Prices are integers fields supplied with an associated precision. When converted to a decimal format, prices are in
|
28
|
+
fixed point format, where the precision defines the number of decimal places. For example, a field flagged as Price
|
29
|
+
(4) has an implied 4 decimal places. The maximum value of price (4) in TotalView ITCH is 200,000.0000 (decimal,
|
30
|
+
77359400 hex). ``price_precision`` is 4 for all messages except MWCBDeclineLeveMessage where ``price_precision`` is 8.
|
23
31
|
"""
|
24
32
|
|
25
33
|
message_type: bytes
|
@@ -32,13 +40,24 @@ class MarketMessage(object):
|
|
32
40
|
tracking_number: int
|
33
41
|
price_precision: int = 4
|
34
42
|
|
35
|
-
def __repr__(self):
|
43
|
+
def __repr__(self) -> str:
|
36
44
|
return repr(self.decode())
|
37
45
|
|
38
|
-
def
|
46
|
+
def __bytes__(self) -> bytes:
|
47
|
+
return self.to_bytes()
|
48
|
+
|
49
|
+
def to_bytes(self) -> bytes:
|
39
50
|
"""
|
40
51
|
Packs the message into bytes using the defined message_pack_format.
|
41
52
|
This method should be overridden by subclasses to include specific fields.
|
53
|
+
|
54
|
+
Note:
|
55
|
+
All packed messages do not include
|
56
|
+
- ``description``,
|
57
|
+
- ``message_format``,
|
58
|
+
- ``message_pack_format``,
|
59
|
+
- ``message_size``
|
60
|
+
- ``price_precision``
|
42
61
|
"""
|
43
62
|
pass
|
44
63
|
|
@@ -166,7 +185,7 @@ class SystemEventMessage(MarketMessage):
|
|
166
185
|
) = struct.unpack(self.message_format, message[1:])
|
167
186
|
self.set_timestamp(timestamp1, timestamp2)
|
168
187
|
|
169
|
-
def
|
188
|
+
def to_bytes(self):
|
170
189
|
(timestamp1, timestamp2) = self.split_timestamp()
|
171
190
|
message = struct.pack(
|
172
191
|
self.message_pack_format,
|
@@ -288,7 +307,7 @@ class StockDirectoryMessage(MarketMessage):
|
|
288
307
|
) = struct.unpack(self.message_format, message[1:])
|
289
308
|
self.set_timestamp(timestamp1, timestamp2)
|
290
309
|
|
291
|
-
def
|
310
|
+
def to_bytes(self):
|
292
311
|
(timestamp1, timestamp2) = self.split_timestamp()
|
293
312
|
message = struct.pack(
|
294
313
|
self.message_pack_format,
|
@@ -368,7 +387,7 @@ class StockTradingActionMessage(MarketMessage):
|
|
368
387
|
) = struct.unpack(self.message_format, message[1:])
|
369
388
|
self.set_timestamp(timestamp1, timestamp2)
|
370
389
|
|
371
|
-
def
|
390
|
+
def to_bytes(self):
|
372
391
|
(timestamp1, timestamp2) = self.split_timestamp()
|
373
392
|
message = struct.pack(
|
374
393
|
self.message_pack_format,
|
@@ -429,7 +448,7 @@ class RegSHOMessage(MarketMessage):
|
|
429
448
|
) = struct.unpack(self.message_format, message[1:])
|
430
449
|
self.set_timestamp(timestamp1, timestamp2)
|
431
450
|
|
432
|
-
def
|
451
|
+
def to_bytes(self):
|
433
452
|
(timestamp1, timestamp2) = self.split_timestamp()
|
434
453
|
message = struct.pack(
|
435
454
|
self.message_pack_format,
|
@@ -491,7 +510,7 @@ class MarketParticipantPositionMessage(MarketMessage):
|
|
491
510
|
) = struct.unpack(self.message_format, message[1:])
|
492
511
|
self.set_timestamp(timestamp1, timestamp2)
|
493
512
|
|
494
|
-
def
|
513
|
+
def to_bytes(self):
|
495
514
|
(timestamp1, timestamp2) = self.split_timestamp()
|
496
515
|
message = struct.pack(
|
497
516
|
self.message_pack_format,
|
@@ -542,7 +561,7 @@ class MWCBDeclineLeveMessage(MarketMessage):
|
|
542
561
|
) = struct.unpack(self.message_format, message[1:])
|
543
562
|
self.set_timestamp(timestamp1, timestamp2)
|
544
563
|
|
545
|
-
def
|
564
|
+
def to_bytes(self):
|
546
565
|
(timestamp1, timestamp2) = self.split_timestamp()
|
547
566
|
message = struct.pack(
|
548
567
|
self.message_pack_format,
|
@@ -587,7 +606,7 @@ class MWCBStatusMessage(MarketMessage):
|
|
587
606
|
) = struct.unpack(self.message_format, message[1:])
|
588
607
|
self.set_timestamp(timestamp1, timestamp2)
|
589
608
|
|
590
|
-
def
|
609
|
+
def to_bytes(self):
|
591
610
|
(timestamp1, timestamp2) = self.split_timestamp()
|
592
611
|
message = struct.pack(
|
593
612
|
self.message_pack_format,
|
@@ -648,7 +667,7 @@ class IPOQuotingPeriodUpdateMessage(MarketMessage):
|
|
648
667
|
) = struct.unpack(self.message_format, message[1:])
|
649
668
|
self.set_timestamp(timestamp1, timestamp2)
|
650
669
|
|
651
|
-
def
|
670
|
+
def to_bytes(self):
|
652
671
|
(timestamp1, timestamp2) = self.split_timestamp()
|
653
672
|
message = struct.pack(
|
654
673
|
self.message_pack_format,
|
@@ -704,7 +723,7 @@ class LULDAuctionCollarMessage(MarketMessage):
|
|
704
723
|
) = struct.unpack(self.message_format, message[1:])
|
705
724
|
self.set_timestamp(timestamp1, timestamp2)
|
706
725
|
|
707
|
-
def
|
726
|
+
def to_bytes(self):
|
708
727
|
(timestamp1, timestamp2) = self.split_timestamp()
|
709
728
|
message = struct.pack(
|
710
729
|
self.message_pack_format,
|
@@ -769,7 +788,7 @@ class OperationalHaltMessage(MarketMessage):
|
|
769
788
|
) = struct.unpack(self.message_format, message[1:])
|
770
789
|
self.set_timestamp(timestamp1, timestamp2)
|
771
790
|
|
772
|
-
def
|
791
|
+
def to_bytes(self):
|
773
792
|
(timestamp1, timestamp2) = self.split_timestamp()
|
774
793
|
message = struct.pack(
|
775
794
|
self.message_pack_format,
|
@@ -832,7 +851,7 @@ class AddOrderNoMPIAttributionMessage(AddOrderMessage):
|
|
832
851
|
) = struct.unpack(self.message_format, message[1:])
|
833
852
|
self.set_timestamp(timestamp1, timestamp2)
|
834
853
|
|
835
|
-
def
|
854
|
+
def to_bytes(self):
|
836
855
|
(timestamp1, timestamp2) = self.split_timestamp()
|
837
856
|
message = struct.pack(
|
838
857
|
self.message_pack_format,
|
@@ -886,7 +905,7 @@ class AddOrderMPIDAttribution(AddOrderMessage):
|
|
886
905
|
) = struct.unpack(self.message_format, message[1:])
|
887
906
|
self.set_timestamp(timestamp1, timestamp2)
|
888
907
|
|
889
|
-
def
|
908
|
+
def to_bytes(self):
|
890
909
|
(timestamp1, timestamp2) = self.split_timestamp()
|
891
910
|
message = struct.pack(
|
892
911
|
self.message_pack_format,
|
@@ -957,7 +976,7 @@ class OrderExecutedMessage(ModifyOrderMessage):
|
|
957
976
|
) = struct.unpack(self.message_format, message[1:])
|
958
977
|
self.set_timestamp(timestamp1, timestamp2)
|
959
978
|
|
960
|
-
def
|
979
|
+
def to_bytes(self):
|
961
980
|
(timestamp1, timestamp2) = self.split_timestamp()
|
962
981
|
message = struct.pack(
|
963
982
|
self.message_pack_format,
|
@@ -1025,7 +1044,7 @@ class OrderExecutedWithPriceMessage(ModifyOrderMessage):
|
|
1025
1044
|
) = struct.unpack(self.message_format, message[1:])
|
1026
1045
|
self.set_timestamp(timestamp1, timestamp2)
|
1027
1046
|
|
1028
|
-
def
|
1047
|
+
def to_bytes(self):
|
1029
1048
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1030
1049
|
message = struct.pack(
|
1031
1050
|
self.message_pack_format,
|
@@ -1071,7 +1090,7 @@ class OrderCancelMessage(ModifyOrderMessage):
|
|
1071
1090
|
) = struct.unpack(self.message_format, message[1:])
|
1072
1091
|
self.set_timestamp(timestamp1, timestamp2)
|
1073
1092
|
|
1074
|
-
def
|
1093
|
+
def to_bytes(self):
|
1075
1094
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1076
1095
|
message = struct.pack(
|
1077
1096
|
self.message_pack_format,
|
@@ -1111,7 +1130,7 @@ class OrderDeleteMessage(ModifyOrderMessage):
|
|
1111
1130
|
) = struct.unpack(self.message_format, message[1:])
|
1112
1131
|
self.set_timestamp(timestamp1, timestamp2)
|
1113
1132
|
|
1114
|
-
def
|
1133
|
+
def to_bytes(self):
|
1115
1134
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1116
1135
|
message = struct.pack(
|
1117
1136
|
self.message_pack_format,
|
@@ -1164,7 +1183,7 @@ class OrderReplaceMessage(ModifyOrderMessage):
|
|
1164
1183
|
) = struct.unpack(self.message_format, message[1:])
|
1165
1184
|
self.set_timestamp(timestamp1, timestamp2)
|
1166
1185
|
|
1167
|
-
def
|
1186
|
+
def to_bytes(self):
|
1168
1187
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1169
1188
|
message = struct.pack(
|
1170
1189
|
self.message_pack_format,
|
@@ -1238,7 +1257,7 @@ class NonCrossTradeMessage(TradeMessage):
|
|
1238
1257
|
) = struct.unpack(self.message_format, message[1:])
|
1239
1258
|
self.set_timestamp(timestamp1, timestamp2)
|
1240
1259
|
|
1241
|
-
def
|
1260
|
+
def to_bytes(self):
|
1242
1261
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1243
1262
|
message = struct.pack(
|
1244
1263
|
self.message_pack_format,
|
@@ -1309,7 +1328,7 @@ class CrossTradeMessage(TradeMessage):
|
|
1309
1328
|
) = struct.unpack(self.message_format, message[1:])
|
1310
1329
|
self.set_timestamp(timestamp1, timestamp2)
|
1311
1330
|
|
1312
|
-
def
|
1331
|
+
def to_bytes(self):
|
1313
1332
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1314
1333
|
message = struct.pack(
|
1315
1334
|
self.message_pack_format,
|
@@ -1359,7 +1378,7 @@ class BrokenTradeMessage(TradeMessage):
|
|
1359
1378
|
) = struct.unpack(self.message_format, message[1:])
|
1360
1379
|
self.set_timestamp(timestamp1, timestamp2)
|
1361
1380
|
|
1362
|
-
def
|
1381
|
+
def to_bytes(self):
|
1363
1382
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1364
1383
|
message = struct.pack(
|
1365
1384
|
self.message_pack_format,
|
@@ -1441,7 +1460,7 @@ class NOIIMessage(MarketMessage):
|
|
1441
1460
|
) = struct.unpack(self.message_format, message[1:])
|
1442
1461
|
self.set_timestamp(timestamp1, timestamp2)
|
1443
1462
|
|
1444
|
-
def
|
1463
|
+
def to_bytes(self):
|
1445
1464
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1446
1465
|
message = struct.pack(
|
1447
1466
|
self.message_pack_format,
|
@@ -1496,7 +1515,7 @@ class RetailPriceImprovementIndicator(MarketMessage):
|
|
1496
1515
|
) = struct.unpack(self.message_format, message[1:])
|
1497
1516
|
self.set_timestamp(timestamp1, timestamp2)
|
1498
1517
|
|
1499
|
-
def
|
1518
|
+
def to_bytes(self):
|
1500
1519
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1501
1520
|
message = struct.pack(
|
1502
1521
|
self.message_pack_format,
|
@@ -1559,7 +1578,7 @@ class DLCRMessage(MarketMessage):
|
|
1559
1578
|
) = struct.unpack(self.message_format, message[1:])
|
1560
1579
|
self.set_timestamp(timestamp1, timestamp2)
|
1561
1580
|
|
1562
|
-
def
|
1581
|
+
def to_bytes(self):
|
1563
1582
|
(timestamp1, timestamp2) = self.split_timestamp()
|
1564
1583
|
message = struct.pack(
|
1565
1584
|
self.message_pack_format,
|
@@ -1579,8 +1598,8 @@ class DLCRMessage(MarketMessage):
|
|
1579
1598
|
)
|
1580
1599
|
return message
|
1581
1600
|
|
1582
|
-
|
1583
|
-
messages
|
1601
|
+
messages: Dict[bytes, Type[MarketMessage]]
|
1602
|
+
messages = {
|
1584
1603
|
b"S": SystemEventMessage,
|
1585
1604
|
b"R": StockDirectoryMessage,
|
1586
1605
|
b"H": StockTradingActionMessage,
|
@@ -1607,7 +1626,7 @@ messages: Dict[bytes, Type[MarketMessage]] = {
|
|
1607
1626
|
}
|
1608
1627
|
|
1609
1628
|
|
1610
|
-
def create_message(message_type: bytes, **kwargs) ->
|
1629
|
+
def create_message(message_type: bytes, **kwargs) -> MarketMessage:
|
1611
1630
|
"""
|
1612
1631
|
Creates a new message of a given type with specified attributes.
|
1613
1632
|
|
itch/parser.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
from
|
2
|
-
from typing import BinaryIO, List, Type
|
1
|
+
from typing import IO, BinaryIO, Iterator
|
3
2
|
|
4
3
|
from itch.messages import MESSAGES, MarketMessage
|
5
4
|
from itch.messages import messages as msgs
|
@@ -15,139 +14,161 @@ class MessageParser(object):
|
|
15
14
|
self.message_type = message_type
|
16
15
|
|
17
16
|
def read_message_from_file(
|
18
|
-
self,
|
19
|
-
|
20
|
-
cachesize: int = 4096,
|
21
|
-
) -> List[MarketMessage]:
|
17
|
+
self, file: BinaryIO, cachesize: int = 65_536, save_file: IO = None
|
18
|
+
) -> Iterator[MarketMessage]:
|
22
19
|
"""
|
23
20
|
Reads and parses market messages from a binary file-like object.
|
24
21
|
|
25
22
|
This method processes binary data in chunks, extracts individual messages
|
26
|
-
according to a specific format, and returns a list of successfully decoded
|
27
|
-
MarketMessage objects. Parsing stops either when the end of the file is
|
23
|
+
according to a specific format, and returns a list of successfully decoded
|
24
|
+
MarketMessage objects. Parsing stops either when the end of the file is
|
28
25
|
reached or when a system message with an end-of-messages event code is encountered.
|
29
26
|
|
30
27
|
Args:
|
31
|
-
file (BinaryIO):
|
28
|
+
file (BinaryIO):
|
32
29
|
A binary file-like object (opened in binary mode) from which market messages are read.
|
33
|
-
cachesize (int, optional):
|
34
|
-
The size
|
30
|
+
cachesize (int, optional):
|
31
|
+
The size of each data chunk to read. Defaults to 65536 bytes (64KB).
|
32
|
+
save_file (IO, optional):
|
33
|
+
A binary file-like object (opened in binary write mode) where filtered messages are saved.
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
defined in self.message_type.
|
35
|
+
Yields:
|
36
|
+
MarketMessage:
|
37
|
+
The next parsed MarketMessage object from the file.
|
40
38
|
|
41
39
|
Raises:
|
42
|
-
ValueError:
|
43
|
-
If a message does not start with the expected 0x00 byte, indicating
|
40
|
+
ValueError:
|
41
|
+
If a message does not start with the expected 0x00 byte, indicating
|
44
42
|
an unexpected file format or possible corruption.
|
45
43
|
|
46
|
-
|
44
|
+
Notes:
|
47
45
|
- Each message starts with a 0x00 byte.
|
48
46
|
- The following byte specifies the message length.
|
49
47
|
- The complete message consists of the first 2 bytes and 'message length' bytes of body.
|
50
|
-
- If a system message (message_type == b'S') with event_code == b'C' is encountered,
|
51
|
-
|
48
|
+
- If a system message (message_type == b'S') with event_code == b'C' is encountered,
|
49
|
+
parsing stops immediately.
|
52
50
|
|
53
51
|
Example:
|
54
|
-
>>>
|
55
|
-
>>>
|
56
|
-
>>>
|
52
|
+
>>> data_file = "01302020.NASDAQ_ITCH50.gz"
|
53
|
+
>>> message_type = b"AFE" # Example message type to filter
|
54
|
+
>>> parser = MessageParser(message_type=message_type)
|
55
|
+
>>> with gzip.open(data_file, "rb") as itch_file:
|
56
|
+
>>> message_count = 0
|
57
|
+
>>> start_time = time.time()
|
58
|
+
>>> for message in parser.read_message_from_file(itch_file):
|
59
|
+
>>> message_count += 1
|
60
|
+
>>> if message_count <= 5:
|
57
61
|
>>> print(message)
|
58
|
-
|
59
|
-
|
60
|
-
|
62
|
+
>>> end_time = time.time()
|
63
|
+
>>> print(f"Processed {message_count} messages in {end_time - start_time:.2f} seconds")
|
64
|
+
>>> print(f"Average time per message: {(end_time - start_time) / message_count:.6f} seconds")
|
65
|
+
"""
|
66
|
+
if not file.readable():
|
67
|
+
raise ValueError("file must be opened in binary read mode")
|
61
68
|
|
62
|
-
|
63
|
-
|
64
|
-
|
69
|
+
if save_file is not None:
|
70
|
+
if not save_file.writable():
|
71
|
+
raise ValueError("save_file must be opened in binary write mode")
|
65
72
|
|
66
|
-
|
67
|
-
|
73
|
+
data_buffer = b""
|
74
|
+
offset = 0
|
75
|
+
|
76
|
+
while True:
|
77
|
+
if len(data_buffer) - offset < 2:
|
78
|
+
data_buffer = data_buffer[offset:]
|
79
|
+
offset = 0
|
68
80
|
new_data = file.read(cachesize)
|
69
81
|
if not new_data:
|
70
82
|
break
|
71
83
|
data_buffer += new_data
|
72
|
-
buffer_len = len(data_buffer)
|
73
|
-
continue
|
74
84
|
|
75
|
-
|
85
|
+
if len(data_buffer) < 2:
|
86
|
+
break
|
87
|
+
|
88
|
+
if data_buffer[offset : offset + 1] != b"\x00":
|
76
89
|
raise ValueError(
|
77
|
-
"Unexpected byte: "
|
90
|
+
"Unexpected byte: "
|
91
|
+
+ str(data_buffer[offset : offset + 1], encoding="ascii")
|
78
92
|
)
|
79
93
|
|
80
|
-
message_len = data_buffer[1]
|
94
|
+
message_len = data_buffer[offset + 1]
|
81
95
|
total_len = 2 + message_len
|
82
96
|
|
83
|
-
if
|
84
|
-
|
97
|
+
if len(data_buffer) - offset < total_len:
|
98
|
+
data_buffer = data_buffer[offset:]
|
99
|
+
offset = 0
|
100
|
+
|
85
101
|
new_data = file.read(cachesize)
|
86
102
|
if not new_data:
|
87
103
|
break
|
88
104
|
data_buffer += new_data
|
89
|
-
buffer_len = len(data_buffer)
|
90
105
|
continue
|
91
|
-
|
106
|
+
|
107
|
+
message_data = data_buffer[offset + 2 : offset + total_len]
|
92
108
|
message = self.get_message_type(message_data)
|
93
109
|
|
94
110
|
if message.message_type in self.message_type:
|
95
|
-
|
111
|
+
if save_file is not None:
|
112
|
+
msg_len_to_bytes = message.message_size.to_bytes()
|
113
|
+
save_file.write(b"\x00" + msg_len_to_bytes + message.to_bytes())
|
114
|
+
yield message
|
96
115
|
|
97
116
|
if message.message_type == b"S": # System message
|
98
117
|
if message.event_code == b"C": # End of messages
|
99
118
|
break
|
119
|
+
offset += total_len
|
100
120
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if buffer_len < max_message_size and not file_end_reached:
|
106
|
-
new_data = file.read(cachesize)
|
107
|
-
if not new_data:
|
108
|
-
file_end_reached = True
|
109
|
-
else:
|
110
|
-
data_buffer += new_data
|
111
|
-
buffer_len = len(data_buffer)
|
112
|
-
|
113
|
-
return messages
|
114
|
-
|
115
|
-
def read_message_from_bytes(self, data: bytes):
|
121
|
+
def read_message_from_bytes(
|
122
|
+
self, data: bytes, save_file: IO = None
|
123
|
+
) -> Iterator[MarketMessage]:
|
116
124
|
"""
|
117
125
|
Process one or multiple ITCH binary messages from a raw bytes input.
|
118
126
|
|
119
127
|
Args:
|
120
128
|
data (bytes): Binary blob containing one or more ITCH messages.
|
121
129
|
|
122
|
-
|
123
|
-
|
130
|
+
save_file (IO, optional):
|
131
|
+
A binary file-like object (opened in binary write mode) where filtered messages are saved.
|
132
|
+
|
133
|
+
Yields:
|
134
|
+
MarketMessage:
|
135
|
+
The next parsed MarketMessage object from the bytes input.
|
124
136
|
|
125
137
|
Notes:
|
126
138
|
- Each message must be prefixed with a 0x00 header and a length byte.
|
127
139
|
- No buffering is done here — this is meant for real-time decoding.
|
128
140
|
"""
|
141
|
+
if not isinstance(data, (bytes, bytearray)):
|
142
|
+
raise TypeError("data must be bytes or bytearray not " + str(type(data)))
|
143
|
+
|
144
|
+
if save_file is not None:
|
145
|
+
if not save_file.writable():
|
146
|
+
raise ValueError("save_file must be opened in binary write mode")
|
129
147
|
|
130
148
|
offset = 0
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
149
|
+
data_view = memoryview(data)
|
150
|
+
data_len = len(data_view)
|
151
|
+
|
152
|
+
while offset + 2 <= data_len:
|
153
|
+
if data_view[offset : offset + 1] != b"\x00":
|
135
154
|
raise ValueError(
|
136
|
-
f"Unexpected start byte at offset {offset}: "
|
137
|
-
f"{
|
155
|
+
f"Unexpected start byte at offset {offset:offset+1}: "
|
156
|
+
f"{data_view[offset : offset + 1].tobytes()}"
|
138
157
|
)
|
139
|
-
|
140
|
-
msg_len = data[offset + 1]
|
158
|
+
msg_len = data_view[offset + 1]
|
141
159
|
total_len = 2 + msg_len
|
142
160
|
|
143
|
-
if offset + total_len >
|
161
|
+
if offset + total_len > data_len:
|
144
162
|
break
|
145
163
|
|
146
|
-
raw_msg =
|
147
|
-
message = self.get_message_type(raw_msg)
|
164
|
+
raw_msg = data_view[offset + 2 : offset + total_len]
|
165
|
+
message = self.get_message_type(raw_msg.tobytes())
|
148
166
|
|
149
167
|
if message.message_type in self.message_type:
|
150
|
-
|
168
|
+
if save_file is not None:
|
169
|
+
msg_len_to_bytes = message.message_size.to_bytes()
|
170
|
+
save_file.write(b"\x00" + msg_len_to_bytes + message.to_bytes())
|
171
|
+
yield message
|
151
172
|
|
152
173
|
if message.message_type == b"S": # System message
|
153
174
|
if message.event_code == b"C": # End of messages
|
@@ -155,9 +176,7 @@ class MessageParser(object):
|
|
155
176
|
|
156
177
|
offset += total_len
|
157
178
|
|
158
|
-
|
159
|
-
|
160
|
-
def get_message_type(self, message: bytes) -> Type[MarketMessage]:
|
179
|
+
def get_message_type(self, message: bytes) -> MarketMessage:
|
161
180
|
"""
|
162
181
|
Take an entire bytearray and return the appropriate ITCH message
|
163
182
|
instance based on the message type indicator (first byte of the message).
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: itchfeed
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.5
|
4
4
|
Summary: Simple parser for ITCH messages
|
5
5
|
Home-page: https://github.com/bbalouki/itch
|
6
6
|
Download-URL: https://pypi.org/project/itchfeed/
|
@@ -59,7 +59,7 @@ Dynamic: summary
|
|
59
59
|
* [Data Representation](#data-representation)
|
60
60
|
* [Common Attributes of `MarketMessage`](#common-attributes-of-marketmessage)
|
61
61
|
* [Common Methods of `MarketMessage`](#common-methods-of-marketmessage)
|
62
|
-
* [Serializing Messages with `
|
62
|
+
* [Serializing Messages with `to_bytes()`](#serializing-messages-with-to_bytes)
|
63
63
|
* [Data Types in Parsed Messages](#data-types-in-parsed-messages)
|
64
64
|
* [Error Handling](#error-handling)
|
65
65
|
* [Handling Strategies](#handling-strategies)
|
@@ -170,8 +170,8 @@ try:
|
|
170
170
|
|
171
171
|
except FileNotFoundError:
|
172
172
|
print(f"Error: File not found at {itch_file_path}")
|
173
|
-
except
|
174
|
-
print(f"An error occurred: {e}")
|
173
|
+
except ValueError as e:
|
174
|
+
print(f"An error occurred during parsing: {e}")
|
175
175
|
|
176
176
|
```
|
177
177
|
|
@@ -276,10 +276,10 @@ for message_type, sample_data in TEST_DATA.items():
|
|
276
276
|
print(f"Creating message of type {message_type}")
|
277
277
|
message = create_message(message_type, **sample_data)
|
278
278
|
print(f"Created message: {message}")
|
279
|
-
print(f"Packed message: {message.
|
279
|
+
print(f"Packed message: {message.to_bytes()}")
|
280
280
|
print(f"Message size: {message.message_size}")
|
281
281
|
print(f"Message Attributes: {message.get_attributes()}")
|
282
|
-
assert len(message.
|
282
|
+
assert len(message.to_bytes()) == message.message_size
|
283
283
|
print()
|
284
284
|
```
|
285
285
|
|
@@ -388,14 +388,14 @@ The `MarketMessage` base class, and therefore all specific message classes, prov
|
|
388
388
|
* Returns a dictionary of all attributes (fields) of the message instance, along with their current values.
|
389
389
|
* This can be useful for generic inspection or logging of message contents without needing to know the specific type of the message beforehand.
|
390
390
|
|
391
|
-
### Serializing Messages with `
|
391
|
+
### Serializing Messages with `to_bytes()`
|
392
392
|
|
393
|
-
Each specific message class (e.g., `SystemEventMessage`, `AddOrderNoMPIAttributionMessage`) also provides a `
|
393
|
+
Each specific message class (e.g., `SystemEventMessage`, `AddOrderNoMPIAttributionMessage`) also provides a `to_bytes()` method. This method is the inverse of the parsing process.
|
394
394
|
|
395
395
|
* **Purpose:** It serializes the message object, with its current attribute values, back into its raw ITCH 5.0 binary format. The output is a `bytes` object representing the exact byte sequence that would appear in an ITCH data feed for that message.
|
396
396
|
* **Usefulness:**
|
397
397
|
* **Generating Test Data:** Create custom ITCH messages for testing your own ITCH processing applications.
|
398
|
-
* **Modifying Messages:** Parse an existing message, modify some of its attributes, and then `
|
398
|
+
* **Modifying Messages:** Parse an existing message, modify some of its attributes, and then `to_bytes()` it back into binary form.
|
399
399
|
* **Creating Custom ITCH Feeds:** While more involved, you could use this to construct sequences of ITCH messages for specialized scenarios.
|
400
400
|
|
401
401
|
**Example:**
|
@@ -415,21 +415,21 @@ import time
|
|
415
415
|
# for packing requires setting attributes manually if not using raw bytes for construction)
|
416
416
|
|
417
417
|
event_msg = SystemEventMessage.__new__(SystemEventMessage) # Create instance without calling __init__
|
418
|
-
event_msg.message_type = b'S' # Must be set for
|
418
|
+
event_msg.message_type = b'S' # Must be set for to_bytes() to know its type
|
419
419
|
event_msg.stock_locate = 0 # Placeholder or actual value
|
420
420
|
event_msg.tracking_number = 0 # Placeholder or actual value
|
421
421
|
event_msg.event_code = b'O' # Example: Start of Messages
|
422
422
|
|
423
423
|
# 2. Set the timestamp.
|
424
424
|
# The `timestamp` attribute (nanoseconds since midnight) must be set.
|
425
|
-
# The `
|
425
|
+
# The `to_bytes()` method will internally use `split_timestamp()` to get the parts.
|
426
426
|
current_nanoseconds = int(time.time() * 1e9) % (24 * 60 * 60 * int(1e9))
|
427
427
|
event_msg.timestamp = current_nanoseconds # Directly set the nanosecond timestamp
|
428
428
|
|
429
429
|
# 3. Pack the message into binary format.
|
430
|
-
# The
|
430
|
+
# The to_bytes() method prepends the message type and then packs stock_locate,
|
431
431
|
# tracking_number, the split timestamp, and then the message-specific fields.
|
432
|
-
packed_bytes = event_msg.
|
432
|
+
packed_bytes = event_msg.to_bytes()
|
433
433
|
|
434
434
|
# 4. The result is a bytes object
|
435
435
|
print(f"Packed {len(packed_bytes)} bytes: {packed_bytes.hex().upper()}")
|
@@ -0,0 +1,9 @@
|
|
1
|
+
itch/__init__.py,sha256=M9Jirj4-XXdaCoTcU2_g89z7JHK8mDtJflTFf9HnU-k,205
|
2
|
+
itch/indicators.py,sha256=-Ed2M8I60xGQ1bIPZCGCKGb8ayT87JAnIaosfiBimXI,6542
|
3
|
+
itch/messages.py,sha256=UWhpFzgfaDwAah-R0p24LtpP7-G5pP1jnOMakBc33t4,64935
|
4
|
+
itch/parser.py,sha256=glYjyeqY9841UqcOD8Wck-Az70AUwQxTt9SckYoxOg0,7422
|
5
|
+
itchfeed-1.0.5.dist-info/licenses/LICENSE,sha256=f2u79rUzh-UcYH0RN0Ph0VvVYHBkYlVxtguhKmrHqsw,1089
|
6
|
+
itchfeed-1.0.5.dist-info/METADATA,sha256=gUXQ25Q5Cvw6EHGVLUDuTXFOTdUSp8VQEIyKUOd8LU8,33853
|
7
|
+
itchfeed-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
+
itchfeed-1.0.5.dist-info/top_level.txt,sha256=xwsOYShvy3gc1rfyitCTgSxBZDGG1y6bfQxkdhIGmEM,5
|
9
|
+
itchfeed-1.0.5.dist-info/RECORD,,
|
itchfeed-1.0.4.dist-info/RECORD
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
itch/__init__.py,sha256=M9Jirj4-XXdaCoTcU2_g89z7JHK8mDtJflTFf9HnU-k,205
|
2
|
-
itch/indicators.py,sha256=-Ed2M8I60xGQ1bIPZCGCKGb8ayT87JAnIaosfiBimXI,6542
|
3
|
-
itch/messages.py,sha256=lm5pKQ00ETMbTij9HqaYVrN08JJdQe-mZKB_C7IAetU,63934
|
4
|
-
itch/parser.py,sha256=BOrkGsmRkcYnXSf5S9yqrwzP0jqtkH5mGCpWCeiWNTg,6155
|
5
|
-
itchfeed-1.0.4.dist-info/licenses/LICENSE,sha256=f2u79rUzh-UcYH0RN0Ph0VvVYHBkYlVxtguhKmrHqsw,1089
|
6
|
-
itchfeed-1.0.4.dist-info/METADATA,sha256=HYjteeevesrGUcrjvuNlW2P-0B9k8jtL0qdovoaLZT0,33793
|
7
|
-
itchfeed-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
-
itchfeed-1.0.4.dist-info/top_level.txt,sha256=xwsOYShvy3gc1rfyitCTgSxBZDGG1y6bfQxkdhIGmEM,5
|
9
|
-
itchfeed-1.0.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|