diff --git a/src/decode.jl b/src/decode.jl index 7badc15..080b812 100644 --- a/src/decode.jl +++ b/src/decode.jl @@ -955,7 +955,7 @@ end currency = intern_string(decoder, read(decoder.io, 4)) # offset 170 settl_currency = intern_string(decoder, read(decoder.io, 4)) # offset 174 secsubtype = intern_string(decoder, read(decoder.io, 6)) # offset 178 - raw_symbol = intern_string(decoder, read(decoder.io, 71)) # offset 184 (71 bytes in v2!) + raw_symbol = intern_string(decoder, read(decoder.io, SYMBOL_CSTR_LEN)) # offset 184 group = intern_string(decoder, read(decoder.io, 21)) # offset 255 exchange = intern_string(decoder, read(decoder.io, 5)) # offset 276 asset = intern_string(decoder, read(decoder.io, 7)) # offset 281 (7 bytes in v2!) @@ -996,10 +996,10 @@ end leg_raw_symbol = "" leg_instrument_class = InstrumentClass.OTHER # Default is OTHER, not UNKNOWN_0 leg_side = Side.NONE - leg_ratio_price_numerator = UInt32(0) - leg_ratio_price_denominator = UInt32(0) - leg_ratio_qty_numerator = UInt32(0) - leg_ratio_qty_denominator = UInt32(0) + leg_ratio_price_numerator = Int32(0) + leg_ratio_price_denominator = Int32(0) + leg_ratio_qty_numerator = Int32(0) + leg_ratio_qty_denominator = Int32(0) leg_underlying_id = UInt32(0) return InstrumentDefMsg( @@ -1026,44 +1026,9 @@ end @inline function read_instrument_def_v3(decoder::DBNDecoder, hd::RecordHeader) # ===== DBN V3 InstrumentDefMsg ===== + # DBN binary records are encoded in the fixed binary field layout. + # `encode_order` controls text/field ordering, not the binary layout. ts_recv = read(decoder.io, Int64) - # encode_order 2: raw_symbol (22 bytes in v3) - raw_symbol = read_null_terminated_string(decoder.io, 22) - # encode_order 3: security_update_action - security_update_action_byte = read(decoder.io, UInt8) - security_update_action = security_update_action_byte == 0 ? '\0' : Char(security_update_action_byte) - # encode_order 4: instrument_class - instrument_class_byte = read(decoder.io, UInt8) - instrument_class = safe_instrument_class(instrument_class_byte) - # encode_order 20: raw_instrument_id (u64 in v3) - raw_instrument_id = read(decoder.io, UInt64) - # encode_order 54: strike_price - strike_price = read(decoder.io, Int64) - # encode_order 158: leg_count - leg_count = read(decoder.io, UInt16) - # encode_order 159: leg_index - leg_index = read(decoder.io, UInt16) - # encode_order 160: leg_instrument_id - leg_instrument_id = read(decoder.io, UInt32) - # encode_order 161: leg_raw_symbol (20 bytes) - leg_raw_symbol = intern_string(decoder, read(decoder.io, 20)) - # encode_order 163: leg_instrument_class - leg_instrument_class_byte = read(decoder.io, UInt8) - leg_instrument_class = safe_instrument_class(leg_instrument_class_byte) - # encode_order 164: leg_side - leg_side_byte = read(decoder.io, UInt8) - leg_side = unsafe_side(leg_side_byte) - # encode_order 165: leg_price - leg_price = read(decoder.io, Int64) - # encode_order 166: leg_delta - leg_delta = read(decoder.io, Int64) - # encode_order 167-171: leg ratio fields - leg_ratio_price_numerator = read(decoder.io, UInt32) - leg_ratio_price_denominator = read(decoder.io, UInt32) - leg_ratio_qty_numerator = read(decoder.io, UInt32) - leg_ratio_qty_denominator = read(decoder.io, UInt32) - leg_underlying_id = read(decoder.io, UInt32) - # Now all fields WITHOUT encode_order, in struct declaration order min_price_increment = read(decoder.io, Int64) display_factor = read(decoder.io, Int64) expiration = read(decoder.io, Int64) @@ -1074,6 +1039,10 @@ end unit_of_measure_qty = read(decoder.io, Int64) min_price_increment_amount = read(decoder.io, Int64) price_ratio = read(decoder.io, Int64) + strike_price = read(decoder.io, Int64) + raw_instrument_id = read(decoder.io, UInt64) + leg_price = read(decoder.io, Int64) + leg_delta = read(decoder.io, Int64) inst_attrib_value = read(decoder.io, Int32) underlying_id = read(decoder.io, UInt32) market_depth_implied = read(decoder.io, Int32) @@ -1087,15 +1056,24 @@ end contract_multiplier = read(decoder.io, Int32) decay_quantity = read(decoder.io, Int32) original_contract_size = read(decoder.io, Int32) + leg_instrument_id = read(decoder.io, UInt32) + leg_ratio_price_numerator = read(decoder.io, Int32) + leg_ratio_price_denominator = read(decoder.io, Int32) + leg_ratio_qty_numerator = read(decoder.io, Int32) + leg_ratio_qty_denominator = read(decoder.io, Int32) + leg_underlying_id = read(decoder.io, UInt32) appl_id = read(decoder.io, Int16) maturity_year = read(decoder.io, UInt16) decay_start_date = read(decoder.io, UInt16) channel_id = read(decoder.io, UInt16) - # String fields without encode_order - # Use string interning for repeated values (currencies, exchanges, etc.) + leg_count = read(decoder.io, UInt16) + leg_index = read(decoder.io, UInt16) + + # String fields in struct declaration order. currency = intern_string(decoder, read(decoder.io, 4)) settl_currency = intern_string(decoder, read(decoder.io, 4)) secsubtype = intern_string(decoder, read(decoder.io, 6)) + raw_symbol = intern_string(decoder, read(decoder.io, SYMBOL_CSTR_LEN)) group = intern_string(decoder, read(decoder.io, 21)) exchange = intern_string(decoder, read(decoder.io, 5)) asset = intern_string(decoder, read(decoder.io, 11)) # 11 bytes in v3! @@ -1104,13 +1082,19 @@ end unit_of_measure = intern_string(decoder, read(decoder.io, 31)) underlying = intern_string(decoder, read(decoder.io, 21)) strike_price_currency = intern_string(decoder, read(decoder.io, 4)) + leg_raw_symbol = intern_string(decoder, read(decoder.io, SYMBOL_CSTR_LEN)) + # Single-byte fields without encode_order + instrument_class_byte = read(decoder.io, UInt8) + instrument_class = safe_instrument_class(instrument_class_byte) match_algorithm_byte = read(decoder.io, UInt8) match_algorithm = match_algorithm_byte == 0 ? '\0' : Char(match_algorithm_byte) main_fraction = read(decoder.io, UInt8) price_display_format = read(decoder.io, UInt8) sub_fraction = read(decoder.io, UInt8) underlying_product = read(decoder.io, UInt8) + security_update_action_byte = read(decoder.io, UInt8) + security_update_action = security_update_action_byte == 0 ? '\0' : Char(security_update_action_byte) maturity_month = read(decoder.io, UInt8) maturity_day = read(decoder.io, UInt8) maturity_week = read(decoder.io, UInt8) @@ -1119,6 +1103,11 @@ end contract_multiplier_unit = read(decoder.io, Int8) flow_schedule_type = read(decoder.io, Int8) tick_rule = read(decoder.io, UInt8) + leg_instrument_class_byte = read(decoder.io, UInt8) + leg_instrument_class = safe_instrument_class(leg_instrument_class_byte) + leg_side_byte = read(decoder.io, UInt8) + leg_side = unsafe_side(leg_side_byte) + # v3: 17 bytes _reserved skip(decoder.io, 17) # V3 has NO v2-only fields - set to defaults @@ -1664,4 +1653,4 @@ read_bbo1s(filename::String) = read_dbn_typed(filename, BBO1sMsg) Fast reader for BBO 1-minute data files. 5-6x faster than `read_dbn()`. """ -read_bbo1m(filename::String) = read_dbn_typed(filename, BBO1mMsg) \ No newline at end of file +read_bbo1m(filename::String) = read_dbn_typed(filename, BBO1mMsg) diff --git a/src/encode.jl b/src/encode.jl index 07d22c7..179ce30 100644 --- a/src/encode.jl +++ b/src/encode.jl @@ -103,8 +103,8 @@ function write_header(encoder::DBNEncoder) write(metadata_buf, encoder.metadata.ts_out ? UInt8(1) : UInt8(0)) # Symbol string length (2 bytes) - only for version > 1 - # DBN v3 uses 71-byte symbol length (same as v2) - symbol_cstr_len = UInt16(71) + # DBN v2/v3 use 71-byte symbol strings. + symbol_cstr_len = UInt16(SYMBOL_CSTR_LEN) write(metadata_buf, htol(symbol_cstr_len)) # Reserved padding (53 bytes for v3) @@ -498,71 +498,28 @@ function write_record_complex(encoder::DBNEncoder, record) write(io, UInt8(0)) end - else # v3 - # ===== DBN V3 InstrumentDefMsg ===== - # encode_order 0: ts_recv - write(io, record.ts_recv) - - # encode_order 2: raw_symbol (22 bytes in v3) - write_fixed_string(io, record.raw_symbol, 22) - - # encode_order 3: security_update_action - write(io, UInt8(record.security_update_action)) - - # encode_order 4: instrument_class - write(io, UInt8(record.instrument_class)) - - # encode_order 20: raw_instrument_id (u64 in v3) - write(io, record.raw_instrument_id) - - # encode_order 54: strike_price - write(io, record.strike_price) - - # encode_order 158: leg_count - write(io, record.leg_count) - - # encode_order 159: leg_index - write(io, record.leg_index) - - # encode_order 160: leg_instrument_id - write(io, record.leg_instrument_id) - - # encode_order 161: leg_raw_symbol (20 bytes) - write_fixed_string(io, record.leg_raw_symbol, 20) - - # encode_order 163: leg_instrument_class - write(io, UInt8(record.leg_instrument_class)) - - # encode_order 164: leg_side - write(io, UInt8(record.leg_side)) - - # encode_order 165: leg_price - write(io, record.leg_price) - - # encode_order 166: leg_delta - write(io, record.leg_delta) - - # encode_order 167-171: leg ratio fields - write(io, record.leg_ratio_price_numerator) - write(io, record.leg_ratio_price_denominator) - write(io, record.leg_ratio_qty_numerator) - write(io, record.leg_ratio_qty_denominator) - write(io, record.leg_underlying_id) - - # Now all fields WITHOUT encode_order, in struct declaration order - write(io, record.min_price_increment) - write(io, record.display_factor) - write(io, record.expiration) + else # v3 + # ===== DBN V3 InstrumentDefMsg ===== + # DBN binary records are encoded in the fixed binary field layout. + # `encode_order` controls text/field ordering, not the binary layout. + write(io, record.ts_recv) + write(io, record.min_price_increment) + write(io, record.display_factor) + write(io, record.expiration) write(io, record.activation) write(io, record.high_limit_price) write(io, record.low_limit_price) write(io, record.max_price_variation) - write(io, record.unit_of_measure_qty) - write(io, record.min_price_increment_amount) - write(io, record.price_ratio) - - write(io, record.inst_attrib_value) - write(io, record.underlying_id) + write(io, record.unit_of_measure_qty) + write(io, record.min_price_increment_amount) + write(io, record.price_ratio) + write(io, record.strike_price) + write(io, record.raw_instrument_id) + write(io, record.leg_price) + write(io, record.leg_delta) + + write(io, record.inst_attrib_value) + write(io, record.underlying_id) write(io, record.market_depth_implied) write(io, record.market_depth) write(io, record.market_segment_id) @@ -571,44 +528,58 @@ function write_record_complex(encoder::DBNEncoder, record) write(io, record.min_lot_size_block) write(io, record.min_lot_size_round_lot) write(io, record.min_trade_vol) - write(io, record.contract_multiplier) - write(io, record.decay_quantity) - write(io, record.original_contract_size) - - write(io, record.appl_id) - write(io, record.maturity_year) - write(io, record.decay_start_date) - write(io, record.channel_id) - - # String fields without encode_order - write_fixed_string(io, record.currency, 4) - write_fixed_string(io, record.settl_currency, 4) - write_fixed_string(io, record.secsubtype, 6) - write_fixed_string(io, record.group, 21) - write_fixed_string(io, record.exchange, 5) - write_fixed_string(io, record.asset, 11) # 11 bytes in v3! + write(io, record.contract_multiplier) + write(io, record.decay_quantity) + write(io, record.original_contract_size) + write(io, record.leg_instrument_id) + write(io, record.leg_ratio_price_numerator) + write(io, record.leg_ratio_price_denominator) + write(io, record.leg_ratio_qty_numerator) + write(io, record.leg_ratio_qty_denominator) + write(io, record.leg_underlying_id) + + write(io, record.appl_id) + write(io, record.maturity_year) + write(io, record.decay_start_date) + write(io, record.channel_id) + write(io, record.leg_count) + write(io, record.leg_index) + + # String fields in struct declaration order. + write_fixed_string(io, record.currency, 4) + write_fixed_string(io, record.settl_currency, 4) + write_fixed_string(io, record.secsubtype, 6) + write_fixed_string(io, record.raw_symbol, SYMBOL_CSTR_LEN) + write_fixed_string(io, record.group, 21) + write_fixed_string(io, record.exchange, 5) + write_fixed_string(io, record.asset, 11) # 11 bytes in v3! write_fixed_string(io, record.cfi, 7) write_fixed_string(io, record.security_type, 7) - write_fixed_string(io, record.unit_of_measure, 31) - write_fixed_string(io, record.underlying, 21) - write_fixed_string(io, record.strike_price_currency, 4) - - # Single-byte fields without encode_order - write(io, UInt8(record.match_algorithm)) - write(io, record.main_fraction) - write(io, record.price_display_format) - write(io, record.sub_fraction) - write(io, record.underlying_product) - write(io, record.maturity_month) - write(io, record.maturity_day) - write(io, record.maturity_week) + write_fixed_string(io, record.unit_of_measure, 31) + write_fixed_string(io, record.underlying, 21) + write_fixed_string(io, record.strike_price_currency, 4) + write_fixed_string(io, record.leg_raw_symbol, SYMBOL_CSTR_LEN) + + # Single-byte fields without encode_order + write(io, UInt8(record.instrument_class)) + write(io, UInt8(record.match_algorithm)) + write(io, record.main_fraction) + write(io, record.price_display_format) + write(io, record.sub_fraction) + write(io, record.underlying_product) + write(io, UInt8(record.security_update_action)) + write(io, record.maturity_month) + write(io, record.maturity_day) + write(io, record.maturity_week) write(io, record.user_defined_instrument ? UInt8('Y') : UInt8('N')) - write(io, record.contract_multiplier_unit) - write(io, record.flow_schedule_type) - write(io, record.tick_rule) - - # v3: 17 bytes _reserved - for _ in 1:17 + write(io, record.contract_multiplier_unit) + write(io, record.flow_schedule_type) + write(io, record.tick_rule) + write(io, UInt8(record.leg_instrument_class)) + write(io, UInt8(record.leg_side)) + + # v3: 17 bytes _reserved + for _ in 1:17 write(io, UInt8(0)) end end @@ -933,4 +904,4 @@ function write_dbn(filename::String, metadata::Metadata, records) close(base_io) end end -end \ No newline at end of file +end diff --git a/src/messages.jl b/src/messages.jl index 2698f6b..19cec66 100644 --- a/src/messages.jl +++ b/src/messages.jl @@ -324,7 +324,7 @@ Note: DBN v2 and v3 have different field sets. This struct supports both version - v2-only fields (trading_reference_price, trading_reference_date, md_security_trading_status, settl_price_type) are set to 0 in v3 - v3-only fields (all leg_* fields) are set to 0/empty in v2 - raw_instrument_id is UInt32 in v2, UInt64 in v3 -- raw_symbol is 19 bytes in v2, 22 bytes in v3 +- raw_symbol is 71 bytes in DBN v2/v3 - asset is 7 bytes in v2, 11 bytes in v3 # Fields @@ -363,7 +363,7 @@ Note: DBN v2 and v3 have different field sets. This struct supports both version - `currency::String`: Currency code - `settl_currency::String`: Settlement currency - `secsubtype::String`: Security subtype -- `raw_symbol::String`: Raw symbol (19 bytes in v2, 22 bytes in v3) +- `raw_symbol::String`: Raw symbol (71 bytes in DBN v2/v3) - `group::String`: Group identifier - `exchange::String`: Exchange identifier - `asset::String`: Asset identifier (7 bytes in v2, 11 bytes in v3) @@ -392,14 +392,14 @@ Note: DBN v2 and v3 have different field sets. This struct supports both version - `leg_count::UInt16`: Number of legs (DBN v3 only, 0 in v2) - `leg_index::UInt16`: Leg index (DBN v3 only, 0 in v2) - `leg_instrument_id::UInt32`: Leg instrument ID (DBN v3 only, 0 in v2) -- `leg_raw_symbol::String`: Leg raw symbol (DBN v3 only, empty in v2) +- `leg_raw_symbol::String`: Leg raw symbol (DBN v3 only, 71 bytes, empty in v2) - `leg_side::Side.T`: Leg side (DBN v3 only, NONE in v2) - `leg_underlying_id::UInt32`: Leg underlying ID (DBN v3 only, 0 in v2) - `leg_instrument_class::InstrumentClass.T`: Leg instrument class (DBN v3 only, UNKNOWN_0 in v2) -- `leg_ratio_qty_numerator::UInt32`: Leg ratio quantity numerator (DBN v3 only, 0 in v2) -- `leg_ratio_qty_denominator::UInt32`: Leg ratio quantity denominator (DBN v3 only, 0 in v2) -- `leg_ratio_price_numerator::UInt32`: Leg ratio price numerator (DBN v3 only, 0 in v2) -- `leg_ratio_price_denominator::UInt32`: Leg ratio price denominator (DBN v3 only, 0 in v2) +- `leg_ratio_qty_numerator::Int32`: Leg ratio quantity numerator (DBN v3 only, 0 in v2) +- `leg_ratio_qty_denominator::Int32`: Leg ratio quantity denominator (DBN v3 only, 0 in v2) +- `leg_ratio_price_numerator::Int32`: Leg ratio price numerator (DBN v3 only, 0 in v2) +- `leg_ratio_price_denominator::Int32`: Leg ratio price denominator (DBN v3 only, 0 in v2) - `leg_price::Int64`: Leg price (DBN v3 only, 0 in v2) - `leg_delta::Int64`: Leg delta (DBN v3 only, 0 in v2) """ @@ -473,10 +473,10 @@ struct InstrumentDefMsg leg_side::Side.T leg_underlying_id::UInt32 leg_instrument_class::InstrumentClass.T - leg_ratio_qty_numerator::UInt32 - leg_ratio_qty_denominator::UInt32 - leg_ratio_price_numerator::UInt32 - leg_ratio_price_denominator::UInt32 + leg_ratio_qty_numerator::Int32 + leg_ratio_qty_denominator::Int32 + leg_ratio_price_numerator::Int32 + leg_ratio_price_denominator::Int32 leg_price::Int64 leg_delta::Int64 end @@ -711,4 +711,4 @@ const DBNRecord = Union{ StatusMsg, ImbalanceMsg, StatMsg, ErrorMsg, SymbolMappingMsg, SystemMsg, InstrumentDefMsg, CMBP1Msg, CBBO1sMsg, CBBO1mMsg, TCBBOMsg, BBO1sMsg, BBO1mMsg } -StructTypes.StructType(::Type{BidAskPair}) = StructTypes.Struct() \ No newline at end of file +StructTypes.StructType(::Type{BidAskPair}) = StructTypes.Struct() diff --git a/src/types.jl b/src/types.jl index 34616d6..69078c6 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,11 +1,14 @@ # DBN types, enums, and data structures # Constants -"""The current DBN format version supported by this implementation.""" -const DBN_VERSION = 3 - -"""Fixed-point price scaling factor for converting between integer and float prices.""" -const FIXED_PRICE_SCALE = Int32(1_000_000_000) +"""The current DBN format version supported by this implementation.""" +const DBN_VERSION = 3 + +"""Fixed-length symbol string size for DBN v2/v3 metadata and records.""" +const SYMBOL_CSTR_LEN = 71 + +"""Fixed-point price scaling factor for converting between integer and float prices.""" +const FIXED_PRICE_SCALE = Int32(1_000_000_000) """Sentinel value indicating an undefined or missing price.""" const UNDEF_PRICE = typemax(Int64) @@ -235,38 +238,38 @@ end """ InstrumentClass -Classification of financial instruments. - -# Values -- `STOCK`: Equity instruments -- `OPTION`: Option contracts -- `FUTURE`: Futures contracts -- `FX`: Foreign exchange -- `BOND`: Fixed income securities -- `MIXED_SPREAD`: Mixed spread instruments -- `COMMODITY`: Commodity instruments -- `INDEX`: Index instruments -- `CURRENCY`: Currency instruments -- `SWAP`: Swap instruments -- `OTHER`: Other/unknown instrument types -- `UNKNOWN_0`, `UNKNOWN_45`: Numeric fallback values for unknown classes -""" -@enumx InstrumentClass::UInt8 begin - STOCK = UInt8('K') - OPTION = UInt8('O') - FUTURE = UInt8('F') - FX = UInt8('X') - BOND = UInt8('B') - MIXED_SPREAD = UInt8('M') - COMMODITY = UInt8('C') - INDEX = UInt8('I') - CURRENCY = UInt8('U') - SWAP = UInt8('S') - OTHER = UInt8('?') - # Also support numeric values - UNKNOWN_0 = 0 - UNKNOWN_45 = 45 -end +Classification of financial instruments. Values match Databento's DBN +`InstrumentClass` enum. + +# Values +- `STOCK`: Equity instruments +- `CALL`: Call option contracts +- `PUT`: Put option contracts +- `FUTURE`: Futures contracts +- `BOND`: Fixed income securities +- `MIXED_SPREAD`: Mixed spread instruments +- `FUTURE_SPREAD`: Futures spread instruments +- `OPTION_SPREAD`: Option spread instruments +- `FX_SPOT`: Foreign exchange spot +- `COMMODITY_SPOT`: Commodity spot +- `UNKNOWN_0`, `UNKNOWN_45`: Numeric fallback values for unknown classes +""" +@enumx InstrumentClass::UInt8 begin + BOND = UInt8('B') + CALL = UInt8('C') + FUTURE = UInt8('F') + STOCK = UInt8('K') + MIXED_SPREAD = UInt8('M') + PUT = UInt8('P') + FUTURE_SPREAD = UInt8('S') + OPTION_SPREAD = UInt8('T') + FX_SPOT = UInt8('X') + COMMODITY_SPOT = UInt8('Y') + # Also support numeric values + UNKNOWN_0 = 0 + UNKNOWN_45 = 45 + OTHER = UInt8('?') +end # Basic structures @@ -659,4 +662,4 @@ function safe_instrument_class(raw_val::UInt8) @warn "Unknown InstrumentClass value: $raw_val, using OTHER as default" return InstrumentClass.OTHER end -end \ No newline at end of file +end diff --git a/test/test_phase1.jl b/test/test_phase1.jl index 3697fc6..4c92887 100644 --- a/test/test_phase1.jl +++ b/test/test_phase1.jl @@ -111,13 +111,14 @@ @test Side.BID == Side.T(UInt8('B')) @test Side.NONE == Side.T(UInt8('N')) - # Test InstrumentClass enum - @test InstrumentClass.STOCK == InstrumentClass.T(UInt8('K')) - @test InstrumentClass.OPTION == InstrumentClass.T(UInt8('O')) - @test InstrumentClass.FUTURE == InstrumentClass.T(UInt8('F')) - @test InstrumentClass.FX == InstrumentClass.T(UInt8('X')) - @test InstrumentClass.BOND == InstrumentClass.T(UInt8('B')) - end + # Test InstrumentClass enum + @test InstrumentClass.STOCK == InstrumentClass.T(UInt8('K')) + @test InstrumentClass.CALL == InstrumentClass.T(UInt8('C')) + @test InstrumentClass.PUT == InstrumentClass.T(UInt8('P')) + @test InstrumentClass.FUTURE == InstrumentClass.T(UInt8('F')) + @test InstrumentClass.FX_SPOT == InstrumentClass.T(UInt8('X')) + @test InstrumentClass.BOND == InstrumentClass.T(UInt8('B')) + end @testset "Constants are defined" begin @test DBN_VERSION == 3 @@ -127,4 +128,4 @@ @test UNDEF_TIMESTAMP == typemax(Int64) end -end \ No newline at end of file +end diff --git a/test/test_phase5.jl b/test/test_phase5.jl index c1b4708..ce4b365 100644 --- a/test/test_phase5.jl +++ b/test/test_phase5.jl @@ -4,8 +4,8 @@ using Dates @testset "Phase 5: Record Type Read/Write Testing" begin # Helper function to create test metadata - function create_test_metadata(schema::Schema.T) - return Metadata( + function create_test_metadata(schema::Schema.T) + return Metadata( UInt8(DBN_VERSION), # version "TEST.DATA", # dataset schema, # schema @@ -19,8 +19,21 @@ using Dates String[], # partial String[], # not_found Tuple{String,String,Int64,Int64}[] # mappings - ) - end + ) + end + + function record_bytes(metadata::Metadata, record) + io = IOBuffer() + encoder = DBNEncoder(io, metadata) + write_record(encoder, record) + return take!(io) + end + + function scalar_bytes(value) + io = IOBuffer() + write(io, value) + return take!(io) + end @testset "MBOMsg serialization/deserialization" begin metadata = create_test_metadata(Schema.MBO) @@ -452,7 +465,7 @@ using Dates "USD", # currency "USD", # settl_currency "CS", # secsubtype - "TSLA", # raw_symbol + "TSLA240119C00100000.EXTRA", # raw_symbol longer than DBN v1 length "AUTO", # group "XNAS", # exchange "TSLA.NASDAQ", # asset (11 bytes in v3) @@ -482,10 +495,10 @@ using Dates 2, # leg_count 1, # leg_index 88888, # leg_instrument_id - "LEG1", # leg_raw_symbol + "LEG-SYMBOL-LONGER-THAN-20", # leg_raw_symbol uses DBN v2/v3 length Side.BID, # leg_side 99999, # leg_underlying_id - InstrumentClass.OPTION, # leg_instrument_class + InstrumentClass.CALL, # leg_instrument_class 1, # leg_ratio_qty_numerator 2, # leg_ratio_qty_denominator 3, # leg_ratio_price_numerator @@ -520,14 +533,25 @@ using Dates @test read_msg.leg_instrument_class == original_msg.leg_instrument_class @test read_msg.leg_ratio_qty_numerator == original_msg.leg_ratio_qty_numerator @test read_msg.leg_ratio_qty_denominator == original_msg.leg_ratio_qty_denominator - @test read_msg.leg_ratio_price_numerator == original_msg.leg_ratio_price_numerator - @test read_msg.leg_ratio_price_denominator == original_msg.leg_ratio_price_denominator - @test read_msg.leg_price == original_msg.leg_price - @test read_msg.leg_delta == original_msg.leg_delta - - finally - safe_rm(temp_file) - end + @test read_msg.leg_ratio_price_numerator == original_msg.leg_ratio_price_numerator + @test read_msg.leg_ratio_price_denominator == original_msg.leg_ratio_price_denominator + @test read_msg.leg_price == original_msg.leg_price + @test read_msg.leg_delta == original_msg.leg_delta + + bytes = record_bytes(metadata, original_msg) + @test length(bytes) == 520 + @test bytes[17:24] == scalar_bytes(original_msg.ts_recv) + @test bytes[25:32] == scalar_bytes(original_msg.min_price_increment) + @test bytes[129:136] == scalar_bytes(original_msg.leg_delta) + @test bytes[239:238 + ncodeunits(original_msg.raw_symbol)] == + collect(codeunits(original_msg.raw_symbol)) + @test bytes[488] == UInt8(original_msg.instrument_class) + @test bytes[494] == UInt8(original_msg.security_update_action) + @test bytes[495] == original_msg.maturity_month + + finally + safe_rm(temp_file) + end end @testset "Mixed record types in single file" begin @@ -1578,4 +1602,4 @@ using Dates end end end -end \ No newline at end of file +end