itchfeed 1.0.4__tar.gz → 1.0.5__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itchfeed
3
- Version: 1.0.4
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 `pack()`](#serializing-messages-with-pack)
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 Exception as e:
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.pack()}")
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.pack()) == message.message_size
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 `pack()`
391
+ ### Serializing Messages with `to_bytes()`
392
392
 
393
- Each specific message class (e.g., `SystemEventMessage`, `AddOrderNoMPIAttributionMessage`) also provides a `pack()` method. This method is the inverse of the parsing process.
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 `pack()` it back into binary form.
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 pack() to know its type
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 `pack()` method will internally use `split_timestamp()` to get the parts.
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 pack() method prepends the message type and then packs stock_locate,
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.pack()
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()}")
@@ -23,7 +23,7 @@
23
23
  * [Data Representation](#data-representation)
24
24
  * [Common Attributes of `MarketMessage`](#common-attributes-of-marketmessage)
25
25
  * [Common Methods of `MarketMessage`](#common-methods-of-marketmessage)
26
- * [Serializing Messages with `pack()`](#serializing-messages-with-pack)
26
+ * [Serializing Messages with `to_bytes()`](#serializing-messages-with-to_bytes)
27
27
  * [Data Types in Parsed Messages](#data-types-in-parsed-messages)
28
28
  * [Error Handling](#error-handling)
29
29
  * [Handling Strategies](#handling-strategies)
@@ -134,8 +134,8 @@ try:
134
134
 
135
135
  except FileNotFoundError:
136
136
  print(f"Error: File not found at {itch_file_path}")
137
- except Exception as e:
138
- print(f"An error occurred: {e}")
137
+ except ValueError as e:
138
+ print(f"An error occurred during parsing: {e}")
139
139
 
140
140
  ```
141
141
 
@@ -240,10 +240,10 @@ for message_type, sample_data in TEST_DATA.items():
240
240
  print(f"Creating message of type {message_type}")
241
241
  message = create_message(message_type, **sample_data)
242
242
  print(f"Created message: {message}")
243
- print(f"Packed message: {message.pack()}")
243
+ print(f"Packed message: {message.to_bytes()}")
244
244
  print(f"Message size: {message.message_size}")
245
245
  print(f"Message Attributes: {message.get_attributes()}")
246
- assert len(message.pack()) == message.message_size
246
+ assert len(message.to_bytes()) == message.message_size
247
247
  print()
248
248
  ```
249
249
 
@@ -352,14 +352,14 @@ The `MarketMessage` base class, and therefore all specific message classes, prov
352
352
  * Returns a dictionary of all attributes (fields) of the message instance, along with their current values.
353
353
  * This can be useful for generic inspection or logging of message contents without needing to know the specific type of the message beforehand.
354
354
 
355
- ### Serializing Messages with `pack()`
355
+ ### Serializing Messages with `to_bytes()`
356
356
 
357
- Each specific message class (e.g., `SystemEventMessage`, `AddOrderNoMPIAttributionMessage`) also provides a `pack()` method. This method is the inverse of the parsing process.
357
+ Each specific message class (e.g., `SystemEventMessage`, `AddOrderNoMPIAttributionMessage`) also provides a `to_bytes()` method. This method is the inverse of the parsing process.
358
358
 
359
359
  * **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.
360
360
  * **Usefulness:**
361
361
  * **Generating Test Data:** Create custom ITCH messages for testing your own ITCH processing applications.
362
- * **Modifying Messages:** Parse an existing message, modify some of its attributes, and then `pack()` it back into binary form.
362
+ * **Modifying Messages:** Parse an existing message, modify some of its attributes, and then `to_bytes()` it back into binary form.
363
363
  * **Creating Custom ITCH Feeds:** While more involved, you could use this to construct sequences of ITCH messages for specialized scenarios.
364
364
 
365
365
  **Example:**
@@ -379,21 +379,21 @@ import time
379
379
  # for packing requires setting attributes manually if not using raw bytes for construction)
380
380
 
381
381
  event_msg = SystemEventMessage.__new__(SystemEventMessage) # Create instance without calling __init__
382
- event_msg.message_type = b'S' # Must be set for pack() to know its type
382
+ event_msg.message_type = b'S' # Must be set for to_bytes() to know its type
383
383
  event_msg.stock_locate = 0 # Placeholder or actual value
384
384
  event_msg.tracking_number = 0 # Placeholder or actual value
385
385
  event_msg.event_code = b'O' # Example: Start of Messages
386
386
 
387
387
  # 2. Set the timestamp.
388
388
  # The `timestamp` attribute (nanoseconds since midnight) must be set.
389
- # The `pack()` method will internally use `split_timestamp()` to get the parts.
389
+ # The `to_bytes()` method will internally use `split_timestamp()` to get the parts.
390
390
  current_nanoseconds = int(time.time() * 1e9) % (24 * 60 * 60 * int(1e9))
391
391
  event_msg.timestamp = current_nanoseconds # Directly set the nanosecond timestamp
392
392
 
393
393
  # 3. Pack the message into binary format.
394
- # The pack() method prepends the message type and then packs stock_locate,
394
+ # The to_bytes() method prepends the message type and then packs stock_locate,
395
395
  # tracking_number, the split timestamp, and then the message-specific fields.
396
- packed_bytes = event_msg.pack()
396
+ packed_bytes = event_msg.to_bytes()
397
397
 
398
398
  # 4. The result is a bytes object
399
399
  print(f"Packed {len(packed_bytes)} bytes: {packed_bytes.hex().upper()}")
@@ -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
- - timestamp: Time at which the message was generated (Nanoseconds past midnight)
21
- - stock_locate: Locate code identifying the security
22
- - tracking_number: Nasdaq internal tracking number
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 pack(self) -> bytes:
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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 pack(self):
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: Dict[bytes, Type[MarketMessage]] = {
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) -> Type[MarketMessage]:
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
 
@@ -0,0 +1,192 @@
1
+ from typing import IO, BinaryIO, Iterator
2
+
3
+ from itch.messages import MESSAGES, MarketMessage
4
+ from itch.messages import messages as msgs
5
+
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 = MESSAGES):
14
+ self.message_type = message_type
15
+
16
+ def read_message_from_file(
17
+ self, file: BinaryIO, cachesize: int = 65_536, save_file: IO = None
18
+ ) -> Iterator[MarketMessage]:
19
+ """
20
+ Reads and parses market messages from a binary file-like object.
21
+
22
+ This method processes binary data in chunks, extracts individual messages
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
25
+ reached or when a system message with an end-of-messages event code is encountered.
26
+
27
+ Args:
28
+ file (BinaryIO):
29
+ A binary file-like object (opened in binary mode) from which market messages are read.
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.
34
+
35
+ Yields:
36
+ MarketMessage:
37
+ The next parsed MarketMessage object from the file.
38
+
39
+ Raises:
40
+ ValueError:
41
+ If a message does not start with the expected 0x00 byte, indicating
42
+ an unexpected file format or possible corruption.
43
+
44
+ Notes:
45
+ - Each message starts with a 0x00 byte.
46
+ - The following byte specifies the message length.
47
+ - The complete message consists of the first 2 bytes and 'message length' bytes of body.
48
+ - If a system message (message_type == b'S') with event_code == b'C' is encountered,
49
+ parsing stops immediately.
50
+
51
+ Example:
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:
61
+ >>> print(message)
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")
68
+
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")
72
+
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
80
+ new_data = file.read(cachesize)
81
+ if not new_data:
82
+ break
83
+ data_buffer += new_data
84
+
85
+ if len(data_buffer) < 2:
86
+ break
87
+
88
+ if data_buffer[offset : offset + 1] != b"\x00":
89
+ raise ValueError(
90
+ "Unexpected byte: "
91
+ + str(data_buffer[offset : offset + 1], encoding="ascii")
92
+ )
93
+
94
+ message_len = data_buffer[offset + 1]
95
+ total_len = 2 + message_len
96
+
97
+ if len(data_buffer) - offset < total_len:
98
+ data_buffer = data_buffer[offset:]
99
+ offset = 0
100
+
101
+ new_data = file.read(cachesize)
102
+ if not new_data:
103
+ break
104
+ data_buffer += new_data
105
+ continue
106
+
107
+ message_data = data_buffer[offset + 2 : offset + total_len]
108
+ message = self.get_message_type(message_data)
109
+
110
+ if message.message_type in self.message_type:
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
115
+
116
+ if message.message_type == b"S": # System message
117
+ if message.event_code == b"C": # End of messages
118
+ break
119
+ offset += total_len
120
+
121
+ def read_message_from_bytes(
122
+ self, data: bytes, save_file: IO = None
123
+ ) -> Iterator[MarketMessage]:
124
+ """
125
+ Process one or multiple ITCH binary messages from a raw bytes input.
126
+
127
+ Args:
128
+ data (bytes): Binary blob containing one or more ITCH messages.
129
+
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.
136
+
137
+ Notes:
138
+ - Each message must be prefixed with a 0x00 header and a length byte.
139
+ - No buffering is done here — this is meant for real-time decoding.
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")
147
+
148
+ offset = 0
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":
154
+ raise ValueError(
155
+ f"Unexpected start byte at offset {offset:offset+1}: "
156
+ f"{data_view[offset : offset + 1].tobytes()}"
157
+ )
158
+ msg_len = data_view[offset + 1]
159
+ total_len = 2 + msg_len
160
+
161
+ if offset + total_len > data_len:
162
+ break
163
+
164
+ raw_msg = data_view[offset + 2 : offset + total_len]
165
+ message = self.get_message_type(raw_msg.tobytes())
166
+
167
+ if message.message_type in self.message_type:
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
172
+
173
+ if message.message_type == b"S": # System message
174
+ if message.event_code == b"C": # End of messages
175
+ break
176
+
177
+ offset += total_len
178
+
179
+ def get_message_type(self, message: bytes) -> MarketMessage:
180
+ """
181
+ Take an entire bytearray and return the appropriate ITCH message
182
+ instance based on the message type indicator (first byte of the message).
183
+
184
+ All message type indicators are single ASCII characters.
185
+ """
186
+ message_type = message[0:1]
187
+ try:
188
+ return msgs[message_type](message)
189
+ except Exception:
190
+ raise ValueError(
191
+ f"Unknown message type: {message_type.decode(encoding='ascii')}"
192
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: itchfeed
3
- Version: 1.0.4
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 `pack()`](#serializing-messages-with-pack)
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 Exception as e:
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.pack()}")
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.pack()) == message.message_size
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 `pack()`
391
+ ### Serializing Messages with `to_bytes()`
392
392
 
393
- Each specific message class (e.g., `SystemEventMessage`, `AddOrderNoMPIAttributionMessage`) also provides a `pack()` method. This method is the inverse of the parsing process.
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 `pack()` it back into binary form.
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 pack() to know its type
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 `pack()` method will internally use `split_timestamp()` to get the parts.
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 pack() method prepends the message type and then packs stock_locate,
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.pack()
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()}")
@@ -12,7 +12,7 @@ with io.open(path.join(here, "README.md"), encoding="utf-8") as f:
12
12
  with io.open(path.join(here, "requirements.txt"), encoding="utf-8") as f:
13
13
  REQUIREMENTS = [line.rstrip() for line in f]
14
14
 
15
- VERSION = "1.0.4"
15
+ VERSION = "1.0.5"
16
16
  DESCRIPTION = "Simple parser for ITCH messages"
17
17
 
18
18
  KEYWORDS = [
@@ -12,7 +12,7 @@ def test_create_message_and_pack(message_type, sample_data):
12
12
  created_message = create_message(message_type, **sample_data)
13
13
 
14
14
  # Pack the created message
15
- packed_message = created_message.pack()
15
+ packed_message = created_message.to_bytes()
16
16
 
17
17
  # Unpack the message using the original class constructor
18
18
  message_class = messages[message_type]
@@ -76,7 +76,7 @@ def pack_message_data(
76
76
 
77
77
  # Now call pack - instance should have all attributes defined in 'data'
78
78
  try:
79
- packed_message = instance.pack()
79
+ packed_message = instance.to_bytes()
80
80
  except struct.error as e:
81
81
  print(f"Struct error during pack for {msg_class.__name__}: {e}")
82
82
  raise
@@ -183,8 +183,8 @@ def test_market_message_decode_price():
183
183
  msg.decode_price("stock")
184
184
 
185
185
  # Test trying to decode a method
186
- with pytest.raises(ValueError, match="Please check the price attribute for pack"):
187
- msg.decode_price("pack")
186
+ with pytest.raises(ValueError, match="Please check the price attribute for to_bytes"):
187
+ msg.decode_price("to_bytes")
188
188
 
189
189
 
190
190
  # Helper Function for Verifying Unpacked Attributes
@@ -385,7 +385,7 @@ def test_pack_unpack_decode_consistency(message_params):
385
385
  _verify_unpacked_attributes(instance, message_class, sample_data)
386
386
 
387
387
  # Step 3: Pack the initialized object and compare
388
- repacked_message = instance.pack()
388
+ repacked_message = instance.to_bytes()
389
389
  assert repacked_message == expected_packed_message, (
390
390
  f"Repacked message does not match original for {message_class.__name__}"
391
391
  )
@@ -427,7 +427,7 @@ def test_get_attributes(message_params):
427
427
  assert "level1_price" in non_callable_attrs
428
428
 
429
429
  # Check some expected callable attributes
430
- assert "pack" in callable_attrs
430
+ assert "to_bytes" in callable_attrs
431
431
  assert "decode" in callable_attrs
432
432
  assert "set_timestamp" in callable_attrs
433
433
  assert "split_timestamp" in callable_attrs
@@ -1,173 +0,0 @@
1
- from queue import Queue
2
- from typing import BinaryIO, List, Type
3
-
4
- from itch.messages import MESSAGES, MarketMessage
5
- from itch.messages import messages as msgs
6
-
7
-
8
- class MessageParser(object):
9
- """
10
- A market message parser for ITCH 5.0 data.
11
-
12
- """
13
-
14
- def __init__(self, message_type: bytes = MESSAGES):
15
- self.message_type = message_type
16
-
17
- def read_message_from_file(
18
- self,
19
- file: BinaryIO,
20
- cachesize: int = 4096,
21
- ) -> List[MarketMessage]:
22
- """
23
- Reads and parses market messages from a binary file-like object.
24
-
25
- 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
28
- reached or when a system message with an end-of-messages event code is encountered.
29
-
30
- Args:
31
- file (BinaryIO):
32
- A binary file-like object (opened in binary mode) from which market messages are read.
33
- cachesize (int, optional):
34
- The size (in bytes) of each data chunk read from the file. Defaults to 4096 bytes.
35
-
36
- Returns:
37
- List[MarketMessage]:
38
- A list of parsed MarketMessage objects that match the allowed message types
39
- defined in self.message_type.
40
-
41
- Raises:
42
- ValueError:
43
- If a message does not start with the expected 0x00 byte, indicating
44
- an unexpected file format or possible corruption.
45
-
46
- Message Format:
47
- - Each message starts with a 0x00 byte.
48
- - The following byte specifies the message length.
49
- - 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
- parsing stops immediately.
52
-
53
- Example:
54
- >>> with open('market_data.bin', 'rb') as binary_file:
55
- >>> messages = reader.read_message_from_file(binary_file, cachesize=4096)
56
- >>> for message in messages:
57
- >>> print(message)
58
- """
59
- max_message_size = 52
60
- file_end_reached = False
61
-
62
- data_buffer = file.read(cachesize)
63
- buffer_len = len(data_buffer)
64
- messages: List[MarketMessage] = []
65
-
66
- while not file_end_reached:
67
- if buffer_len < 2:
68
- new_data = file.read(cachesize)
69
- if not new_data:
70
- break
71
- data_buffer += new_data
72
- buffer_len = len(data_buffer)
73
- continue
74
-
75
- if data_buffer[0:1] != b"\x00":
76
- raise ValueError(
77
- "Unexpected byte: " + str(data_buffer[0:1], encoding="ascii")
78
- )
79
-
80
- message_len = data_buffer[1]
81
- total_len = 2 + message_len
82
-
83
- if buffer_len < total_len:
84
- # Wait for more data if message is incomplete
85
- new_data = file.read(cachesize)
86
- if not new_data:
87
- break
88
- data_buffer += new_data
89
- buffer_len = len(data_buffer)
90
- continue
91
- message_data = data_buffer[2:total_len]
92
- message = self.get_message_type(message_data)
93
-
94
- if message.message_type in self.message_type:
95
- messages.append(message)
96
-
97
- if message.message_type == b"S": # System message
98
- if message.event_code == b"C": # End of messages
99
- break
100
-
101
- # Update buffer
102
- data_buffer = data_buffer[total_len:]
103
- buffer_len = len(data_buffer)
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):
116
- """
117
- Process one or multiple ITCH binary messages from a raw bytes input.
118
-
119
- Args:
120
- data (bytes): Binary blob containing one or more ITCH messages.
121
-
122
- Returns:
123
- Queue: A queue containing parsed ITCH message objects.
124
-
125
- Notes:
126
- - Each message must be prefixed with a 0x00 header and a length byte.
127
- - No buffering is done here — this is meant for real-time decoding.
128
- """
129
-
130
- offset = 0
131
- messages = Queue()
132
- while offset + 2 <= len(data):
133
- # Each message starts with: 1-byte header (0x00) 1-byte length
134
- if data[offset : offset + 1] != b"\x00":
135
- raise ValueError(
136
- f"Unexpected start byte at offset {offset}: "
137
- f"{str(data[offset : offset + 1], encoding='ascii')}"
138
- )
139
-
140
- msg_len = data[offset + 1]
141
- total_len = 2 + msg_len
142
-
143
- if offset + total_len > len(data):
144
- break
145
-
146
- raw_msg = data[offset + 2 : offset + total_len]
147
- message = self.get_message_type(raw_msg)
148
-
149
- if message.message_type in self.message_type:
150
- messages.put(message)
151
-
152
- if message.message_type == b"S": # System message
153
- if message.event_code == b"C": # End of messages
154
- break
155
-
156
- offset += total_len
157
-
158
- return messages
159
-
160
- def get_message_type(self, message: bytes) -> Type[MarketMessage]:
161
- """
162
- Take an entire bytearray and return the appropriate ITCH message
163
- instance based on the message type indicator (first byte of the message).
164
-
165
- All message type indicators are single ASCII characters.
166
- """
167
- message_type = message[0:1]
168
- try:
169
- return msgs[message_type](message)
170
- except Exception:
171
- raise ValueError(
172
- f"Unknown message type: {message_type.decode(encoding='ascii')}"
173
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes