diff --git a/thetadatadx-cpp/include/thetadatadx.h b/thetadatadx-cpp/include/thetadatadx.h index 69c7bae5..fd0feca4 100644 --- a/thetadatadx-cpp/include/thetadatadx.h +++ b/thetadatadx-cpp/include/thetadatadx.h @@ -603,7 +603,7 @@ THETADATADX_ALIGN64_BEGIN typedef struct { } ThetaDataDxIndexPriceAtTimeTick THETADATADX_ALIGN64_END; /* NBBO quote tick (*_history_quote): the bid/ask quote with sizes, - * exchanges, conditions, and a derived midpoint. */ + * exchanges, and conditions. */ THETADATADX_ALIGN64_BEGIN typedef struct { int32_t ms_of_day; int32_t bid_size; @@ -624,9 +624,7 @@ THETADATADX_ALIGN64_BEGIN typedef struct { * 'P' (80) for a put, 0 when contract identity is absent * (single-contract queries). Cast to char for display. */ uint32_t right; - /* 4 bytes padding before the double field */ - double midpoint; - uint8_t _tail_padding[40]; + uint8_t _tail_padding[52]; } ThetaDataDxQuoteTick THETADATADX_ALIGN64_END; /* Trade-with-quote tick (*_history_trade_quote): each trade print fused diff --git a/thetadatadx-cpp/include/tick_layout_asserts.hpp.inc b/thetadatadx-cpp/include/tick_layout_asserts.hpp.inc index 71c2561b..614a3fdb 100644 --- a/thetadatadx-cpp/include/tick_layout_asserts.hpp.inc +++ b/thetadatadx-cpp/include/tick_layout_asserts.hpp.inc @@ -462,8 +462,6 @@ static_assert(offsetof(QuoteTick, strike) == 64, "ThetaDataDxQuoteTick.strike offset drifted from Rust"); static_assert(offsetof(QuoteTick, right) == 72, "ThetaDataDxQuoteTick.right offset drifted from Rust"); -static_assert(offsetof(QuoteTick, midpoint) == 80, - "ThetaDataDxQuoteTick.midpoint offset drifted from Rust"); static_assert(sizeof(TradeGreeksAllTick) == 320 && alignof(TradeGreeksAllTick) == 64, "ThetaDataDxTradeGreeksAllTick layout drifted from Rust"); static_assert(offsetof(TradeGreeksAllTick, ms_of_day) == 0, diff --git a/thetadatadx-py/src/_generated/tick_arrow.rs b/thetadatadx-py/src/_generated/tick_arrow.rs index 9683294e..2816a3b9 100644 --- a/thetadatadx-py/src/_generated/tick_arrow.rs +++ b/thetadatadx-py/src/_generated/tick_arrow.rs @@ -264,7 +264,6 @@ pub(crate) fn arrow_schema_for_qualname(qualname: &str) -> Option> { Field::new("ask", DataType::Float64, false), Field::new("ask_condition", DataType::Int32, false), Field::new("date", DataType::Int32, false), - Field::new("midpoint", DataType::Float64, false), Field::new("expiration", DataType::Int32, true), Field::new("strike", DataType::Float64, true), Field::new("right", DataType::Utf8, true), @@ -2310,7 +2309,6 @@ pub(crate) mod slice_arrow { let has_expiration = present.contains("expiration"); let has_strike = present.contains("strike"); let has_right = present.contains("right"); - let has_midpoint = present.contains("midpoint"); let mut col_ms_of_day: Vec = Vec::with_capacity(if has_ms_of_day { n } else { 0 }); let mut col_bid_size: Vec = Vec::with_capacity(if has_bid_size { n } else { 0 }); let mut col_bid_exchange: Vec = Vec::with_capacity(if has_bid_exchange { n } else { 0 }); @@ -2324,7 +2322,6 @@ pub(crate) mod slice_arrow { let mut col_expiration: Vec> = Vec::with_capacity(if has_expiration { n } else { 0 }); let mut col_strike: Vec> = Vec::with_capacity(if has_strike { n } else { 0 }); let mut col_right: Vec> = Vec::with_capacity(if has_right { n } else { 0 }); - let mut col_midpoint: Vec = Vec::with_capacity(if has_midpoint { n } else { 0 }); for t in ticks { if has_ms_of_day { col_ms_of_day.push(t.ms_of_day); } if has_bid_size { col_bid_size.push(t.bid_size); } @@ -2339,7 +2336,6 @@ pub(crate) mod slice_arrow { if has_expiration { col_expiration.push(t.has_contract_id().then_some(t.expiration)); } if has_strike { col_strike.push(t.has_contract_id().then_some(t.strike)); } if has_right { col_right.push(if t.right == '\0' { None } else { Some(t.right.to_string()) }); } - if has_midpoint { col_midpoint.push(t.midpoint); } } let mut fields: Vec = Vec::new(); let mut columns: Vec = Vec::new(); @@ -2402,10 +2398,6 @@ pub(crate) mod slice_arrow { fields.push(Field::new("right", DataType::Utf8, true)); columns.push(Arc::new(StringArray::from(col_right)) as ArrayRef); } - if has_midpoint { - fields.push(Field::new("midpoint", DataType::Float64, false)); - columns.push(Arc::new(Float64Array::from(col_midpoint)) as ArrayRef); - } RecordBatch::try_new_with_options(Arc::new(Schema::new(fields)), columns, &RecordBatchOptions::new().with_row_count(Some(n))).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string())) } diff --git a/thetadatadx-py/src/_generated/tick_classes.rs b/thetadatadx-py/src/_generated/tick_classes.rs index c8f431da..31476961 100644 --- a/thetadatadx-py/src/_generated/tick_classes.rs +++ b/thetadatadx-py/src/_generated/tick_classes.rs @@ -991,7 +991,6 @@ pub(crate) struct QuoteTick { #[pyo3(get)] pub ask: f64, #[pyo3(get)] pub ask_condition: i32, #[pyo3(get)] pub date: i32, - #[pyo3(get)] pub midpoint: f64, #[pyo3(get)] pub expiration: Option, #[pyo3(get)] pub strike: Option, #[pyo3(get)] pub right: Option, @@ -999,8 +998,8 @@ pub(crate) struct QuoteTick { #[pymethods] impl QuoteTick { #[new] - #[pyo3(signature = (*, ms_of_day = 0i32, bid_size = 0i32, bid_exchange = 0i32, bid = 0.0f64, bid_condition = 0i32, ask_size = 0i32, ask_exchange = 0i32, ask = 0.0f64, ask_condition = 0i32, date = 0i32, midpoint = 0.0f64, expiration = None, strike = None, right = None))] - fn new(ms_of_day: i32, bid_size: i32, bid_exchange: i32, bid: f64, bid_condition: i32, ask_size: i32, ask_exchange: i32, ask: f64, ask_condition: i32, date: i32, midpoint: f64, expiration: Option, strike: Option, right: Option) -> Self { + #[pyo3(signature = (*, ms_of_day = 0i32, bid_size = 0i32, bid_exchange = 0i32, bid = 0.0f64, bid_condition = 0i32, ask_size = 0i32, ask_exchange = 0i32, ask = 0.0f64, ask_condition = 0i32, date = 0i32, expiration = None, strike = None, right = None))] + fn new(ms_of_day: i32, bid_size: i32, bid_exchange: i32, bid: f64, bid_condition: i32, ask_size: i32, ask_exchange: i32, ask: f64, ask_condition: i32, date: i32, expiration: Option, strike: Option, right: Option) -> Self { Self { ms_of_day, bid_size, @@ -1012,7 +1011,6 @@ impl QuoteTick { ask, ask_condition, date, - midpoint, expiration, strike, right, @@ -4940,7 +4938,6 @@ impl QuoteTickList { ask: t.ask, ask_condition: t.ask_condition, date: t.date, - midpoint: t.midpoint, expiration: t.expiration.unwrap_or(0), strike: t.strike.unwrap_or(0.0), right: match t.right.as_deref() { Some("C") => 'C', Some("P") => 'P', None | Some("") => '\0', Some(other) => return Err(pyo3::exceptions::PyValueError::new_err(format!("right must be \"C\" or \"P\", got {other:?}"))) }, @@ -5005,7 +5002,6 @@ impl QuoteTickList { ask: t.ask, ask_condition: t.ask_condition, date: t.date, - midpoint: t.midpoint, expiration: t.has_contract_id().then_some(t.expiration), strike: t.has_contract_id().then_some(t.strike), right: if t.right == '\0' { None } else { Some(t.right.to_string()) }, @@ -5035,7 +5031,6 @@ impl QuoteTickList { ask: t.ask, ask_condition: t.ask_condition, date: t.date, - midpoint: t.midpoint, expiration: t.has_contract_id().then_some(t.expiration), strike: t.has_contract_id().then_some(t.strike), right: if t.right == '\0' { None } else { Some(t.right.to_string()) }, @@ -5111,7 +5106,6 @@ impl QuoteTickListIter { ask: t.ask, ask_condition: t.ask_condition, date: t.date, - midpoint: t.midpoint, expiration: t.has_contract_id().then_some(t.expiration), strike: t.has_contract_id().then_some(t.strike), right: if t.right == '\0' { None } else { Some(t.right.to_string()) }, @@ -7485,7 +7479,6 @@ pub(crate) fn quote_ticks_vec_to_pylist(py: Python<'_>, ticks: thetadatadx::Tick ask: t.ask, ask_condition: t.ask_condition, date: t.date, - midpoint: t.midpoint, expiration: t.has_contract_id().then_some(t.expiration), strike: t.has_contract_id().then_some(t.strike), right: if t.right == '\0' { None } else { Some(t.right.to_string()) }, diff --git a/thetadatadx-py/tests/test_dataframe_arrow.py b/thetadatadx-py/tests/test_dataframe_arrow.py index 704d2cae..d25f7cda 100644 --- a/thetadatadx-py/tests/test_dataframe_arrow.py +++ b/thetadatadx-py/tests/test_dataframe_arrow.py @@ -261,13 +261,6 @@ def test_ohlc_tick_to_arrow_schema(): _assert_arrow_types_match(table, expected) -def test_quote_tick_to_arrow_has_midpoint(): - tick = thetadatadx.QuoteTick(ms_of_day=34_200_000, bid=99.95, ask=100.05, midpoint=100.0) - lst = thetadatadx.QuoteTickList([tick]) - table = lst.to_arrow() - schema = {f.name: str(f.type) for f in table.schema} - assert "midpoint" in schema - assert schema["midpoint"] == "double" def test_trade_tick_to_arrow_schema(): diff --git a/thetadatadx-rs/benches/bench_tick.rs b/thetadatadx-rs/benches/bench_tick.rs index e78570f9..5b3812f9 100644 --- a/thetadatadx-rs/benches/bench_tick.rs +++ b/thetadatadx-rs/benches/bench_tick.rs @@ -1,7 +1,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use std::hint::black_box; -use thetadatadx::{OhlcTick, QuoteTick, TradeTick}; +use thetadatadx::{OhlcTick, TradeTick}; // ═══════════════════════════════════════════════════════════════════════════ // Tick operation benchmarks @@ -39,30 +39,6 @@ fn bench_trade_tick_price_access(c: &mut Criterion) { }); } -fn bench_quote_tick_midpoint(c: &mut Criterion) { - let tick = QuoteTick { - ms_of_day: 34_200_000, - bid_size: 50, - bid_exchange: 4, - bid: 150.20, - bid_condition: 1, - ask_size: 30, - ask_exchange: 4, - ask: 150.30, - ask_condition: 1, - midpoint: 150.25, - date: 20240315, - expiration: 0, - strike: 0.0, - right: '\0', - }; - c.bench_function("quote_tick_midpoint", |b| { - b.iter(|| { - black_box(black_box(&tick).midpoint); - }); - }); -} - fn bench_ohlc_tick_all_prices(c: &mut Criterion) { let tick = OhlcTick { ms_of_day: 34_200_000, @@ -93,7 +69,6 @@ fn bench_ohlc_tick_all_prices(c: &mut Criterion) { criterion_group!( tick_benches, bench_trade_tick_price_access, - bench_quote_tick_midpoint, bench_ohlc_tick_all_prices, ); diff --git a/thetadatadx-rs/build_support/ticks/parser.rs b/thetadatadx-rs/build_support/ticks/parser.rs index 1f40a15f..29041415 100644 --- a/thetadatadx-rs/build_support/ticks/parser.rs +++ b/thetadatadx-rs/build_support/ticks/parser.rs @@ -166,9 +166,6 @@ fn generate_parser(out: &mut String, type_name: &str, def: &TickTypeDef) { out.push_str(" let _cid_right_idx = h.iter().position(|c| *c == \"right\");\n"); } - // Check if this is QuoteTick (needs midpoint computation). - let is_quote_tick = type_name == "QuoteTick"; - out.push('\n'); // Phase 2: seed the output, then bulk-extract column by column in @@ -187,9 +184,6 @@ fn generate_parser(out: &mut String, type_name: &str, def: &TickTypeDef) { out.push_str(" strike: 0.0,\n"); out.push_str(" right: '\\0',\n"); } - if is_quote_tick { - out.push_str(" midpoint: 0.0,\n"); - } out.push_str(" };\n"); out.push_str(" rows.len()\n"); out.push_str(" ];\n"); @@ -236,13 +230,6 @@ fn generate_parser(out: &mut String, type_name: &str, def: &TickTypeDef) { out.push_str(" }\n"); } - if is_quote_tick { - // QuoteTick gets midpoint computed from bid + ask. - out.push_str(" for tick in ticks.iter_mut() {\n"); - out.push_str(" tick.midpoint = (tick.bid + tick.ask) / 2.0;\n"); - out.push_str(" }\n"); - } - out.push_str(" row_base += rows.len();\n"); out.push_str(" }\n"); out.push_str(" Ok(ticks)\n"); @@ -259,10 +246,9 @@ fn generate_parser(out: &mut String, type_name: &str, def: &TickTypeDef) { /// resolves several schema names to the same wire spelling), and the parser /// fills every such field from that one column. `present_columns_from` counts /// each physical header once (first-claim, exact matches before aliases) but -/// exempts the two derived fields that are not their own physical column: -/// `date` (the `YYYYMMDD` split of a `Timestamp` header, present whenever it -/// resolves) and `QuoteTick.midpoint` (computed from `bid` + `ask`, present -/// whenever both inputs are). The EOD wire sends `created`: the exact match +/// exempts the derived `date` field that is not its own physical column: +/// the `YYYYMMDD` split of a `Timestamp` header, present whenever it +/// resolves. The EOD wire sends `created`: the exact match /// (`created` -> `created_ms_of_day`) claims the header, and `date` rides the /// same column via the exemption instead of being dropped. /// @@ -273,7 +259,6 @@ fn generate_parser(out: &mut String, type_name: &str, def: &TickTypeDef) { /// `is_open` and `status`, so its generated impl delegates to the matching /// calendar-specific presence table instead of the generic first-claim helper. fn generate_present_columns(out: &mut String, type_name: &str, def: &TickTypeDef) { - let derive_midpoint = type_name == "QuoteTick"; writeln!(out, "impl crate::columns::WireColumns for {type_name} {{").unwrap(); out.push_str(" fn present_columns(headers: &[&str]) -> crate::columns::ColumnPresence {\n"); if type_name == "CalendarDay" { @@ -292,7 +277,7 @@ fn generate_present_columns(out: &mut String, type_name: &str, def: &TickTypeDef out.push_str(" ];\n"); writeln!( out, - " crate::columns::present_columns_from(headers, COLS, {}, {derive_midpoint})", + " crate::columns::present_columns_from(headers, COLS, {})", def.contract_id, ) .unwrap(); @@ -300,7 +285,7 @@ fn generate_present_columns(out: &mut String, type_name: &str, def: &TickTypeDef out.push_str(" }\n\n"); // all_columns(): the full-schema column set (schema columns + the - // contract-id trio + the QuoteTick `midpoint` derived tail), matching + // contract-id trio), matching // the column order the full `to_arrow` / `to_polars` builders emit. out.push_str(" fn all_columns() -> crate::columns::ColumnPresence {\n"); out.push_str(" crate::columns::ColumnPresence::from_names([\n"); @@ -312,9 +297,6 @@ fn generate_present_columns(out: &mut String, type_name: &str, def: &TickTypeDef out.push_str(" \"strike\",\n"); out.push_str(" \"right\",\n"); } - if type_name == "QuoteTick" { - out.push_str(" \"midpoint\",\n"); - } out.push_str(" ])\n"); out.push_str(" }\n"); out.push_str("}\n\n"); diff --git a/thetadatadx-rs/build_support_bin/ticks/cpp.rs b/thetadatadx-rs/build_support_bin/ticks/cpp.rs index b1793e07..f8bc201c 100644 --- a/thetadatadx-rs/build_support_bin/ticks/cpp.rs +++ b/thetadatadx-rs/build_support_bin/ticks/cpp.rs @@ -26,7 +26,7 @@ pub(super) fn render_cpp_tick_layout_asserts(schema: &Schema) -> String { continue; } let def = &schema.types[type_name]; - let (size, align) = tick_ffi_size_and_align(type_name, def); + let (size, align) = tick_ffi_size_and_align(def); // C++ wrapper exposes the schema type name verbatim under // `thetadatadx::cxx`; the C mirror is `ThetaDataDx`. The wrapper // alias is `using = ThetaDataDx;` so `sizeof(alias)` @@ -40,10 +40,9 @@ pub(super) fn render_cpp_tick_layout_asserts(schema: &Schema) -> String { .unwrap(); writeln!(out, " \"{c_name} layout drifted from Rust\");").unwrap(); // Per-field offset asserts. Same field set/order as the Rust - // offset table (column tail + contract_id triple + - // QuoteTick.midpoint). Field-offset drift can sneak past total- + // offset table (column tail + contract_id triple). Field-offset drift can sneak past total- // size asserts when two same-size fields swap order. - for (field, offset) in tick_ffi_offsets(type_name, def) { + for (field, offset) in tick_ffi_offsets(def) { let field = c_field_ident(&field); writeln!(out, "static_assert(offsetof({alias}, {field}) == {offset},").unwrap(); writeln!( diff --git a/thetadatadx-rs/build_support_bin/ticks/layout.rs b/thetadatadx-rs/build_support_bin/ticks/layout.rs index e7dc1b63..3a2cefa7 100644 --- a/thetadatadx-rs/build_support_bin/ticks/layout.rs +++ b/thetadatadx-rs/build_support_bin/ticks/layout.rs @@ -9,12 +9,11 @@ use super::schema::TickTypeDef; /// `(field, byte_offset)` pairs for every column the parser fills, in -/// declaration order, including the `contract_id` triple and -/// `QuoteTick.midpoint` tail. -pub(super) fn tick_ffi_offsets(type_name: &str, def: &TickTypeDef) -> Vec<(String, usize)> { +/// declaration order, including the `contract_id` triple. +pub(super) fn tick_ffi_offsets(def: &TickTypeDef) -> Vec<(String, usize)> { let mut offsets = Vec::new(); let mut size = 0usize; - for (field_name, field_type) in tick_ffi_fields(type_name, def) { + for (field_name, field_type) in tick_ffi_fields(def) { let (field_size, field_align) = tick_ffi_field_layout(field_type); size = size.next_multiple_of(field_align); offsets.push((field_name.to_string(), size)); @@ -28,10 +27,10 @@ pub(super) fn tick_ffi_offsets(type_name: &str, def: &TickTypeDef) -> Vec<(Strin /// the max of the schema's `align` directive and every field's natural /// alignment, and size is rounded up to a multiple of that alignment to /// reproduce Rust's struct tail padding. -pub(super) fn tick_ffi_size_and_align(type_name: &str, def: &TickTypeDef) -> (usize, usize) { +pub(super) fn tick_ffi_size_and_align(def: &TickTypeDef) -> (usize, usize) { let mut size = 0usize; let mut struct_align = def.align.unwrap_or(1) as usize; - for (_, field_type) in tick_ffi_fields(type_name, def) { + for (_, field_type) in tick_ffi_fields(def) { let (field_size, field_align) = tick_ffi_field_layout(field_type); struct_align = struct_align.max(field_align); size = size.next_multiple_of(field_align) + field_size; @@ -39,7 +38,7 @@ pub(super) fn tick_ffi_size_and_align(type_name: &str, def: &TickTypeDef) -> (us (size.next_multiple_of(struct_align), struct_align) } -fn tick_ffi_fields<'a>(type_name: &'a str, def: &'a TickTypeDef) -> Vec<(&'a str, &'a str)> { +fn tick_ffi_fields(def: &TickTypeDef) -> Vec<(&str, &str)> { let mut fields = def .columns .iter() @@ -50,9 +49,6 @@ fn tick_ffi_fields<'a>(type_name: &'a str, def: &'a TickTypeDef) -> Vec<(&'a str fields.push(("strike", "price")); fields.push(("right", "right")); } - if type_name == "QuoteTick" { - fields.push(("midpoint", "price")); - } fields } diff --git a/thetadatadx-rs/build_support_bin/ticks/python_arrow.rs b/thetadatadx-rs/build_support_bin/ticks/python_arrow.rs index af78717b..23256334 100644 --- a/thetadatadx-rs/build_support_bin/ticks/python_arrow.rs +++ b/thetadatadx-rs/build_support_bin/ticks/python_arrow.rs @@ -215,7 +215,6 @@ pub(super) fn column_push_expr(column_type: &str, field: &str) -> String { fn render_python_slice_reader(type_name: &str, def: &TickTypeDef) -> String { let mut out = String::new(); let fn_name = python_slice_reader_fn_name(type_name); - let is_quote_tick = type_name == "QuoteTick"; let is_contract = def.contract_id; writeln!( @@ -245,9 +244,6 @@ fn render_python_slice_reader(type_name: &str, def: &TickTypeDef) -> String { .unwrap(); column_decls.push((column.field.clone(), column.r#type.clone())); } - if is_quote_tick { - out.push_str(" let mut col_midpoint: Vec = Vec::with_capacity(n);\n"); - } if is_contract { // Absent contract identity (single-contract queries) buffers // as Arrow nulls — the columnar mirror of the pyclass `None` @@ -266,9 +262,6 @@ fn render_python_slice_reader(type_name: &str, def: &TickTypeDef) -> String { ) .unwrap(); } - if is_quote_tick { - out.push_str(" col_midpoint.push(t.midpoint);\n"); - } if is_contract { out.push_str(" col_expiration.push(t.has_contract_id().then_some(t.expiration));\n"); out.push_str(" col_strike.push(t.has_contract_id().then_some(t.strike));\n"); @@ -289,9 +282,6 @@ fn render_python_slice_reader(type_name: &str, def: &TickTypeDef) -> String { ) .unwrap(); } - if is_quote_tick { - out.push_str(" Arc::new(Float64Array::from(col_midpoint)) as ArrayRef,\n"); - } if is_contract { out.push_str(" Arc::new(Int32Array::from(col_expiration)) as ArrayRef,\n"); out.push_str(" Arc::new(Float64Array::from(col_strike)) as ArrayRef,\n"); @@ -379,7 +369,6 @@ fn render_python_slice_public_helper(type_name: &str) -> String { /// One projectable Arrow column: schema field name, buffer element type, /// Arrow `DataType` expr, array constructor, push expression, nullability. -/// The contract-id trio and the `QuoteTick.midpoint` tail are modelled as /// synthetic columns so the projected reader shares one loop — the same /// column set (and order) the Rust `to_arrow_projected` builder emits. struct ProjCol { @@ -391,7 +380,7 @@ struct ProjCol { nullable: bool, } -fn projected_columns(type_name: &str, def: &TickTypeDef) -> Vec { +fn projected_columns(def: &TickTypeDef) -> Vec { let mut cols: Vec = def .columns .iter() @@ -430,16 +419,6 @@ fn projected_columns(type_name: &str, def: &TickTypeDef) -> Vec { nullable: true, }); } - if type_name == "QuoteTick" { - cols.push(ProjCol { - name: "midpoint".into(), - buf_ty: "f64", - data_type: "DataType::Float64", - ctor: "Float64Array", - push: "t.midpoint".into(), - nullable: false, - }); - } cols } @@ -456,7 +435,7 @@ fn render_python_slice_reader_projected(type_name: &str, def: &TickTypeDef) -> S ) .unwrap(); out.push_str(" let n = ticks.len();\n"); - let cols = projected_columns(type_name, def); + let cols = projected_columns(def); // SINGLE pass: presence resolved once (`has_*`), absent columns keep a // zero-capacity buffer never pushed to, so one `for t in ticks` loop // fills only the present columns rather than re-scanning the slice per @@ -622,7 +601,6 @@ fn render_python_arrow_schema_map(schema: &Schema) -> String { for type_name in sorted_type_names(schema) { let def = &schema.types[type_name]; let class = pyclass_name(type_name); - let is_quote_tick = type_name == "QuoteTick"; writeln!( out, " \"{class}\" => Some(Arc::new(Schema::new(vec![" @@ -643,9 +621,6 @@ fn render_python_arrow_schema_map(schema: &Schema) -> String { ) .unwrap(); } - if is_quote_tick { - out.push_str(" Field::new(\"midpoint\", DataType::Float64, false),\n"); - } if def.contract_id { // Nullable: contract identity is absent (Arrow null) on // single-contract queries — mirrors the pyclass `None`. diff --git a/thetadatadx-rs/build_support_bin/ticks/python_classes.rs b/thetadatadx-rs/build_support_bin/ticks/python_classes.rs index 9d7c6038..e0868369 100644 --- a/thetadatadx-rs/build_support_bin/ticks/python_classes.rs +++ b/thetadatadx-rs/build_support_bin/ticks/python_classes.rs @@ -353,7 +353,6 @@ fn slice_arrow_converter<'a>(schema: &'a Schema, type_name: &str) -> &'a str { /// Strip " -- N fields[...]." from the first doc line so the rendered /// comment reflects the actual field set once `contract_id` and -/// `QuoteTick` midpoint get appended to `def.columns`. Words stay stable /// even when a schema edit adds/removes a column, so we drop the numeric /// count instead of recomputing it per surface. pub(super) fn strip_field_count_from_doc(doc: &str) -> String { @@ -411,7 +410,6 @@ struct ReprField { fn render_python_tick_class_struct(type_name: &str, def: &TickTypeDef) -> String { let mut out = String::new(); let class = pyclass_name(type_name); - let is_quote_tick = type_name == "QuoteTick"; let doc_text = if def.doc.is_empty() { format!("Typed {type_name} record.") } else { @@ -461,9 +459,6 @@ fn render_python_tick_class_struct(type_name: &str, def: &TickTypeDef) -> String ) .unwrap(); } - if is_quote_tick { - out.push_str(" #[pyo3(get)] pub midpoint: f64,\n"); - } if def.contract_id { // Contract identity is populated on wildcard queries only — // absent identity is `None`, matching the streaming @@ -617,13 +612,6 @@ fn render_python_tick_class_new(type_name: &str, def: &TickTypeDef) -> String { default, }); } - if type_name == "QuoteTick" { - fields.push(CtorField { - name: "midpoint".to_string(), - rust_type: "f64", - default: "0.0f64", - }); - } if def.contract_id { fields.push(CtorField { name: "expiration".to_string(), @@ -727,9 +715,6 @@ fn pyclass_to_tick_expr( } } } - if type_name == "QuoteTick" { - writeln!(out, "{indent} midpoint: {source_expr}.midpoint,").unwrap(); - } if def.contract_id { // `None` is the absence convention on the Python surface; the // struct's documented fills (0 / 0.0 / '\0') are the reverse @@ -769,7 +754,6 @@ fn pyclass_from_tick_expr( indent: &str, ) { let class = pyclass_name(type_name); - let is_quote_tick = type_name == "QuoteTick"; writeln!(out, "{indent}{class} {{").unwrap(); for column in &def.columns { let field = &column.field; @@ -801,9 +785,6 @@ fn pyclass_from_tick_expr( } } } - if is_quote_tick { - writeln!(out, "{indent} midpoint: {source_expr}.midpoint,").unwrap(); - } if def.contract_id { // Absent contract identity (single-contract queries) is `None` // on the Python surface; the struct's documented fills (0 / diff --git a/thetadatadx-rs/build_support_bin/ticks/rust_frames.rs b/thetadatadx-rs/build_support_bin/ticks/rust_frames.rs index 3c38d614..546e5ebf 100644 --- a/thetadatadx-rs/build_support_bin/ticks/rust_frames.rs +++ b/thetadatadx-rs/build_support_bin/ticks/rust_frames.rs @@ -110,13 +110,11 @@ pub(super) fn tick_has_symbol_column(def: &TickTypeDef) -> bool { } /// The ordered emittable columns for a tick type: its schema columns, -/// then the contract-identity trio when `contract_id = true`, then the -/// `QuoteTick.midpoint` derived tail. Mirrors the column order the Python -/// slice_arrow emitter and the tick struct use, so all surfaces agree. -/// -/// (`IvTick.midpoint` is a real wire column and appears via the schema -/// columns, not this synthetic tail.) -fn emit_columns(type_name: &str, def: &TickTypeDef) -> Vec { +/// then the contract-identity trio when `contract_id = true`. Mirrors the +/// column order the Python slice_arrow emitter and the tick struct use, so +/// all surfaces agree. (`IvTick.midpoint` is a real wire column and appears +/// via the schema columns.) +fn emit_columns(def: &TickTypeDef) -> Vec { let mut cols: Vec = Vec::new(); for column in &def.columns { cols.push(EmitColumn { @@ -154,16 +152,6 @@ fn emit_columns(type_name: &str, def: &TickTypeDef) -> Vec { nullable: true, }); } - if type_name == "QuoteTick" { - cols.push(EmitColumn { - name: "midpoint".to_string(), - buf_ty: "f64".to_string(), - data_type: "DataType::Float64", - ctor: "Float64Array", - push: "t.midpoint".to_string(), - nullable: false, - }); - } cols } @@ -175,7 +163,7 @@ fn emit_columns(type_name: &str, def: &TickTypeDef) -> Vec { // contract identity → Arrow null). fn render_arrow_impl(type_name: &str, def: &TickTypeDef) -> String { - let cols = emit_columns(type_name, def); + let cols = emit_columns(def); let has_symbol_field = tick_has_symbol_column(def); let mut out = String::new(); @@ -348,7 +336,7 @@ fn render_arrow_impl(type_name: &str, def: &TickTypeDef) -> String { // matches the Arrow path 1:1. fn render_polars_impl(type_name: &str, def: &TickTypeDef) -> String { - let cols = emit_columns(type_name, def); + let cols = emit_columns(def); let has_symbol_field = tick_has_symbol_column(def); let mut out = String::new(); diff --git a/thetadatadx-rs/build_support_bin/ticks/tdbe_structs.rs b/thetadatadx-rs/build_support_bin/ticks/tdbe_structs.rs index 82f23f49..6cda46ea 100644 --- a/thetadatadx-rs/build_support_bin/ticks/tdbe_structs.rs +++ b/thetadatadx-rs/build_support_bin/ticks/tdbe_structs.rs @@ -9,16 +9,13 @@ //! this generated file so every `Vec` consumer in the workspace picks //! up new tick types automatically. //! -//! Layout decisions (alignment, copy-derive, contract-id field tail, -//! `QuoteTick.midpoint`) are taken straight from the schema: +//! Layout decisions (alignment, copy-derive, contract-id field tail) +//! are taken straight from the schema: //! //! * `align = N` -> `#[repr(C, align(N))]` (omitted for no align). //! * `copy = true/false` -> `#[derive(Clone, Copy)]` vs `#[derive(Clone)]`. //! * `contract_id = true` -> append `expiration: i32`, `strike: f64`, //! `right: i32` after the schema columns. -//! * `QuoteTick` -> append `midpoint: f64`. The parser computes it from -//! `(bid + ask) / 2.0`; the field exists on the struct so consumers -//! don't recompute. use std::fmt::Write as _; @@ -117,7 +114,7 @@ pub(super) fn timestamp_accessor_fields(def: &TickTypeDef) -> Vec<(String, Strin /// * total `size_of::()` (catches tail-padding drift), /// * `align_of::()` (catches `align(N)` drift), /// * `offset_of!(T, field)` for every column the parser fills, plus -/// the `contract_id` triple and `QuoteTick.midpoint` tail. This is the +/// the `contract_id` triple. This is the /// ABI that the C-mirror in `thetadatadx-cpp/include/thetadatadx.h` and the /// layout-assert `*.hpp.inc` all index into via `offsetof()`. /// @@ -152,7 +149,7 @@ pub(super) fn render_tdbe_layout_asserts(schema: &Schema) -> String { continue; } let def = &schema.types[type_name]; - let (size, align) = tick_ffi_size_and_align(type_name, def); + let (size, align) = tick_ffi_size_and_align(def); let snake = type_name.to_snake_case(); writeln!(out, " #[test]").unwrap(); @@ -163,7 +160,7 @@ pub(super) fn render_tdbe_layout_asserts(schema: &Schema) -> String { " assert_eq!(align_of::<{type_name}>(), {align});" ) .unwrap(); - for (field, offset) in tick_ffi_offsets(type_name, def) { + for (field, offset) in tick_ffi_offsets(def) { let field = rust_field_ident(&field); writeln!( out, @@ -184,9 +181,8 @@ fn render_one_struct(type_name: &str, def: &TickTypeDef) -> String { // Doc comment derived from the schema. Multi-line docs (e.g. // `GreeksTick`) preserve their per-line `///` shape. The field-count // phrase is recomputed from the actual field set the struct emits - // (schema columns plus the `contract_id` tail and `QuoteTick` - // midpoint), so the count is correct by construction rather than - // hand-maintained in the schema prose. + // (schema columns plus the `contract_id` tail), so the count is correct + // by construction rather than hand-maintained in the schema prose. let doc = if def.doc.is_empty() { format!("`{type_name}` tick.") } else { @@ -232,9 +228,8 @@ fn render_one_struct(type_name: &str, def: &TickTypeDef) -> String { } // Field order MUST match the legacy `tick.rs` layout for FFI ABI - // compatibility: `contract_id` triple comes BEFORE `QuoteTick.midpoint`. - // Reordering would silently shift offsets for already-compiled C / C++ - // consumers even when total `size_of` stays the same. + // compatibility. Reordering would silently shift offsets for + // already-compiled C / C++ consumers even when total `size_of` stays the same. if def.contract_id { out.push_str(" /// Contract expiration (`YYYYMMDD`). Populated on wildcard queries, 0 otherwise.\n"); out.push_str(" pub expiration: i32,\n"); @@ -244,28 +239,19 @@ fn render_one_struct(type_name: &str, def: &TickTypeDef) -> String { out.push_str(" pub right: char,\n"); } - if type_name == "QuoteTick" { - out.push_str(" /// Pre-computed midpoint: `(bid + ask) / 2.0`.\n"); - out.push_str(" pub midpoint: f64,\n"); - } - out.push_str("}\n"); out } /// Field count rendered in the `-- N fields` doc phrase: every schema /// column plus the `contract_id` tail (`expiration` / `strike` / `right`). -/// `QuoteTick`'s computed `midpoint` is excluded here because its doc -/// phrase already names it separately (`-- N fields + midpoint`), so -/// counting it in `N` would double-count. The total still tracks the field -/// list by construction — only the named suffix is kept out of `N`. fn struct_field_count(_type_name: &str, def: &TickTypeDef) -> usize { def.columns.len() + usize::from(def.contract_id) * 3 } -/// Rewrite the `-- N fields[ + midpoint]` phrase on the first doc line to -/// the supplied `count`, preserving any suffix wording (e.g. ` + midpoint`, -/// trailing summary sentence). When the first line carries no `-- N` +/// Rewrite the `-- N fields` phrase on the first doc line to the supplied +/// `count`, preserving any trailing summary sentence. When the first line +/// carries no `-- N` /// phrase the doc is returned unchanged — the count is informational, not /// mandatory, so types that never declared it stay as authored. fn with_field_count(doc: &str, count: usize) -> String { @@ -287,10 +273,8 @@ fn with_field_count(doc: &str, count: usize) -> String { /// Replace the numeric run immediately after ` -- ` on a single line with /// `count`, keeping the surrounding text. `"OHLC tick -- 9 fields. ..."` -/// with `count = 12` becomes `"OHLC tick -- 12 fields. ..."`; -/// `"Quote tick -- 10 fields + midpoint. ..."` with `count = 12` becomes -/// `"Quote tick -- 12 fields + midpoint. ..."`. Lines without a leading -/// digit after ` -- ` are returned unchanged. +/// with `count = 12` becomes `"OHLC tick -- 12 fields. ..."`. Lines +/// without a leading digit after ` -- ` are returned unchanged. fn rewrite_count_phrase(line: &str, count: usize) -> String { let Some(dash) = line.find(" -- ") else { return line.to_string(); diff --git a/thetadatadx-rs/build_support_bin/ticks/typescript.rs b/thetadatadx-rs/build_support_bin/ticks/typescript.rs index b0938701..17d25683 100644 --- a/thetadatadx-rs/build_support_bin/ticks/typescript.rs +++ b/thetadatadx-rs/build_support_bin/ticks/typescript.rs @@ -314,9 +314,6 @@ fn render_ts_arrow_reconstruct_rows(type_name: &str, def: &TickTypeDef) -> Strin let expr = ts_arrow_reconstruct_expr(&column.r#type, &field); writeln!(out, " {field}: {expr},").unwrap(); } - if type_name == "QuoteTick" { - out.push_str(" midpoint: r.midpoint,\n"); - } if def.contract_id { // Inverse of the factory's `has_contract_id().then_some(..)`: the // absent JS `undefined`/`null` round-trips back to the struct's @@ -405,7 +402,6 @@ fn ts_arrow_reconstruct_is_fallible(def: &TickTypeDef) -> bool { fn render_ts_tick_class_struct(type_name: &str, def: &TickTypeDef) -> String { let mut out = String::new(); - let is_quote_tick = type_name == "QuoteTick"; // Strip " -- N fields[...]." from the first doc line — see // `strip_field_count_from_doc` for rationale. Shared with the Python // emitter so both surfaces drift together. @@ -430,9 +426,6 @@ fn render_ts_tick_class_struct(type_name: &str, def: &TickTypeDef) -> String { ) .unwrap(); } - if is_quote_tick { - out.push_str(" pub midpoint: f64,\n"); - } if def.contract_id { // Contract identity is populated on wildcard queries only — // absent identity is undefined/null on the JS surface, @@ -515,9 +508,6 @@ fn render_ts_tick_class_factory(schema: &Schema, type_name: &str, def: &TickType ) .unwrap(); } - if type_name == "QuoteTick" { - out.push_str(" midpoint: t.midpoint,\n"); - } if def.contract_id { // Absent contract identity (single-contract queries) crosses // to JS as undefined/null — the documented absence convention. diff --git a/thetadatadx-rs/src/columns.rs b/thetadatadx-rs/src/columns.rs index 680d95dc..79fc5912 100644 --- a/thetadatadx-rs/src/columns.rs +++ b/thetadatadx-rs/src/columns.rs @@ -167,14 +167,11 @@ impl ColumnPresence { /// claimed the shared `Timestamp` header, `date` is that column's derived /// `YYYYMMDD` sibling and rides it without a second claim. Either way it /// is present whenever it resolves. -/// * `midpoint` (`QuoteTick`) is computed at decode from `bid` + `ask` and -/// is never a wire header, so it is present exactly when both inputs are. #[must_use] pub fn present_columns_from( headers: &[&str], schema_columns: &[(&'static str, &'static str)], contract_id: bool, - derive_midpoint: bool, ) -> ColumnPresence { use crate::mdds::decode::headers::find_header; @@ -236,11 +233,6 @@ pub fn present_columns_from( } } } - // `midpoint` rides whenever both its inputs do. - if derive_midpoint && present.contains(&"bid") && present.contains(&"ask") { - present.push("midpoint"); - } - ColumnPresence::from_names(present) } @@ -469,7 +461,7 @@ mod tests { ("open", "open"), ("date", "date"), ]; - let p = present_columns_from(&["created", "open"], COLS, false, false); + let p = present_columns_from(&["created", "open"], COLS, false); assert!(p.contains("created_ms_of_day")); assert!(p.contains("open")); assert!( @@ -483,27 +475,11 @@ mod tests { #[test] fn present_date_standalone_header() { const COLS: &[(&str, &str)] = &[("ms_of_day", "ms_of_day"), ("date", "date")]; - let p = present_columns_from(&["timestamp", "date"], COLS, false, false); + let p = present_columns_from(&["timestamp", "date"], COLS, false); assert!(p.contains("ms_of_day")); assert!(p.contains("date")); } - /// `midpoint` is present exactly when both `bid` and `ask` are. - #[test] - fn present_midpoint_keys_on_bid_and_ask() { - const COLS: &[(&str, &str)] = &[ - ("ms_of_day", "ms_of_day"), - ("bid", "bid"), - ("ask", "ask"), - ("date", "date"), - ]; - let with = present_columns_from(&["ms_of_day", "bid", "ask", "date"], COLS, false, true); - assert!(with.contains("midpoint")); - - let without = present_columns_from(&["ms_of_day", "date"], COLS, false, true); - assert!(!without.contains("midpoint")); - } - /// Two-pass claiming: a column that names a header exactly claims it even /// when an alias-matching column is listed first, so ordering is not a /// latent invariant (`root` aliases to `symbol`; the exact `symbol` column @@ -511,7 +487,7 @@ mod tests { #[test] fn present_exact_match_beats_earlier_alias() { const COLS: &[(&str, &str)] = &[("root", "root"), ("symbol", "symbol")]; - let p = present_columns_from(&["symbol"], COLS, false, false); + let p = present_columns_from(&["symbol"], COLS, false); assert!( p.contains("symbol"), "exact `symbol` column claims the header" @@ -525,7 +501,7 @@ mod tests { #[test] fn present_date_is_primary_claimant_when_no_time_sibling() { const COLS: &[(&str, &str)] = &[("created", "date"), ("rate", "rate")]; - let p = present_columns_from(&["created", "rate"], COLS, false, false); + let p = present_columns_from(&["created", "rate"], COLS, false); assert!( p.contains("date"), "date claims the `created` header directly" @@ -544,7 +520,7 @@ mod tests { ]; // `ms_of_day` resolves via alias (pass 2), `rate`/`date` are exact — // yet the order must stay schema order, not claim order. - let p = present_columns_from(&["timestamp", "date", "rate"], COLS, false, false); + let p = present_columns_from(&["timestamp", "date", "rate"], COLS, false); let got: Vec<&str> = p.present_names().collect(); assert_eq!(got, ["ms_of_day", "date", "rate"]); } diff --git a/thetadatadx-rs/src/frames/generated.rs b/thetadatadx-rs/src/frames/generated.rs index 5672bad0..b62d754f 100644 --- a/thetadatadx-rs/src/frames/generated.rs +++ b/thetadatadx-rs/src/frames/generated.rs @@ -5064,7 +5064,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { let mut col_expiration: Vec> = Vec::with_capacity(n); let mut col_strike: Vec> = Vec::with_capacity(n); let mut col_right: Vec> = Vec::with_capacity(n); - let mut col_midpoint: Vec = Vec::with_capacity(n); for t in self { col_ms_of_day.push(t.ms_of_day); col_bid_size.push(t.bid_size); @@ -5079,7 +5078,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { col_expiration.push(t.has_contract_id().then_some(t.expiration)); col_strike.push(t.has_contract_id().then_some(t.strike)); col_right.push(if t.right == '\0' { None } else { Some(t.right.to_string()) }); - col_midpoint.push(t.midpoint); } let schema = Arc::new(ArrowSchema::new(vec![ Field::new("ms_of_day", DataType::Int32, false), @@ -5095,7 +5093,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { Field::new("expiration", DataType::Int32, true), Field::new("strike", DataType::Float64, true), Field::new("right", DataType::Utf8, true), - Field::new("midpoint", DataType::Float64, false), ])); let columns: Vec = vec![ Arc::new(Int32Array::from(col_ms_of_day)) as ArrayRef, @@ -5111,7 +5108,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { Arc::new(Int32Array::from(col_expiration)) as ArrayRef, Arc::new(Float64Array::from(col_strike)) as ArrayRef, Arc::new(StringArray::from(col_right)) as ArrayRef, - Arc::new(Float64Array::from(col_midpoint)) as ArrayRef, ]; RecordBatch::try_new(schema, columns) } @@ -5132,7 +5128,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { let has_expiration = present.contains("expiration"); let has_strike = present.contains("strike"); let has_right = present.contains("right"); - let has_midpoint = present.contains("midpoint"); let mut col_ms_of_day: Vec = Vec::with_capacity(if has_ms_of_day { n } else { 0 }); let mut col_bid_size: Vec = Vec::with_capacity(if has_bid_size { n } else { 0 }); let mut col_bid_exchange: Vec = Vec::with_capacity(if has_bid_exchange { n } else { 0 }); @@ -5146,7 +5141,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { let mut col_expiration: Vec> = Vec::with_capacity(if has_expiration { n } else { 0 }); let mut col_strike: Vec> = Vec::with_capacity(if has_strike { n } else { 0 }); let mut col_right: Vec> = Vec::with_capacity(if has_right { n } else { 0 }); - let mut col_midpoint: Vec = Vec::with_capacity(if has_midpoint { n } else { 0 }); for t in self { if has_ms_of_day { col_ms_of_day.push(t.ms_of_day); } if has_bid_size { col_bid_size.push(t.bid_size); } @@ -5161,7 +5155,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { if has_expiration { col_expiration.push(t.has_contract_id().then_some(t.expiration)); } if has_strike { col_strike.push(t.has_contract_id().then_some(t.strike)); } if has_right { col_right.push(if t.right == '\0' { None } else { Some(t.right.to_string()) }); } - if has_midpoint { col_midpoint.push(t.midpoint); } } let mut fields: Vec = Vec::new(); let mut columns: Vec = Vec::new(); @@ -5224,10 +5217,6 @@ impl crate::frames::TicksArrowExt for [crate::tdbe::types::tick::QuoteTick] { fields.push(Field::new("right", DataType::Utf8, true)); columns.push(Arc::new(StringArray::from(col_right)) as ArrayRef); } - if has_midpoint { - fields.push(Field::new("midpoint", DataType::Float64, false)); - columns.push(Arc::new(Float64Array::from(col_midpoint)) as ArrayRef); - } RecordBatch::try_new_with_options(Arc::new(ArrowSchema::new(fields)), columns, &RecordBatchOptions::new().with_row_count(Some(n))) } } @@ -5251,7 +5240,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { let mut col_expiration: Vec> = Vec::with_capacity(n); let mut col_strike: Vec> = Vec::with_capacity(n); let mut col_right: Vec> = Vec::with_capacity(n); - let mut col_midpoint: Vec = Vec::with_capacity(n); for t in self { col_ms_of_day.push(t.ms_of_day); col_bid_size.push(t.bid_size); @@ -5266,7 +5254,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { col_expiration.push(t.has_contract_id().then_some(t.expiration)); col_strike.push(t.has_contract_id().then_some(t.strike)); col_right.push(if t.right == '\0' { None } else { Some(t.right.to_string()) }); - col_midpoint.push(t.midpoint); } DataFrame::new(n, vec![ Series::new(PlSmallStr::from_static("ms_of_day"), col_ms_of_day).into(), @@ -5282,7 +5269,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { Series::new(PlSmallStr::from_static("expiration"), col_expiration).into(), Series::new(PlSmallStr::from_static("strike"), col_strike).into(), Series::new(PlSmallStr::from_static("right"), col_right).into(), - Series::new(PlSmallStr::from_static("midpoint"), col_midpoint).into(), ]) } @@ -5302,7 +5288,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { let has_expiration = present.contains("expiration"); let has_strike = present.contains("strike"); let has_right = present.contains("right"); - let has_midpoint = present.contains("midpoint"); let mut col_ms_of_day: Vec = Vec::with_capacity(if has_ms_of_day { n } else { 0 }); let mut col_bid_size: Vec = Vec::with_capacity(if has_bid_size { n } else { 0 }); let mut col_bid_exchange: Vec = Vec::with_capacity(if has_bid_exchange { n } else { 0 }); @@ -5316,7 +5301,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { let mut col_expiration: Vec> = Vec::with_capacity(if has_expiration { n } else { 0 }); let mut col_strike: Vec> = Vec::with_capacity(if has_strike { n } else { 0 }); let mut col_right: Vec> = Vec::with_capacity(if has_right { n } else { 0 }); - let mut col_midpoint: Vec = Vec::with_capacity(if has_midpoint { n } else { 0 }); for t in self { if has_ms_of_day { col_ms_of_day.push(t.ms_of_day); } if has_bid_size { col_bid_size.push(t.bid_size); } @@ -5331,7 +5315,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { if has_expiration { col_expiration.push(t.has_contract_id().then_some(t.expiration)); } if has_strike { col_strike.push(t.has_contract_id().then_some(t.strike)); } if has_right { col_right.push(if t.right == '\0' { None } else { Some(t.right.to_string()) }); } - if has_midpoint { col_midpoint.push(t.midpoint); } } let mut series: Vec = Vec::new(); if let Some(syms) = present.symbols() { @@ -5378,9 +5361,6 @@ impl crate::frames::TicksPolarsExt for [crate::tdbe::types::tick::QuoteTick] { if has_right { series.push(Series::new(PlSmallStr::from_static("right"), col_right).into()); } - if has_midpoint { - series.push(Series::new(PlSmallStr::from_static("midpoint"), col_midpoint).into()); - } DataFrame::new(n, series) } } diff --git a/thetadatadx-rs/src/mdds/decode/tests.rs b/thetadatadx-rs/src/mdds/decode/tests.rs index 4327e9f1..4c4aa05f 100644 --- a/thetadatadx-rs/src/mdds/decode/tests.rs +++ b/thetadatadx-rs/src/mdds/decode/tests.rs @@ -1343,11 +1343,6 @@ fn quote_tick_decodes_legacy_six_field_shape_with_zero_fill() { assert_eq!(t.bid_condition, 0); assert_eq!(t.ask_exchange, 0); assert_eq!(t.ask_condition, 0); - - // Midpoint is computed from bid + ask regardless of legacy / current - // layout — pin the value so a generator regression on the midpoint - // post-processing step would surface here. - assert!((t.midpoint - 1.50315).abs() < 1e-9); } /// The full 11-field shape must continue to decode all columns. A diff --git a/thetadatadx-rs/src/tdbe/types/generated/tick.rs b/thetadatadx-rs/src/tdbe/types/generated/tick.rs index 92b476d6..3891e48f 100644 --- a/thetadatadx-rs/src/tdbe/types/generated/tick.rs +++ b/thetadatadx-rs/src/tdbe/types/generated/tick.rs @@ -816,7 +816,7 @@ impl PriceTick { } } -/// Quote tick -- 13 fields + midpoint. NBBO quote data. +/// Quote tick -- 13 fields. NBBO quote data. /// /// Wire layout: the full shape is 11 columns (`ms_of_day`, /// `bid_size`, `bid_exchange`, `bid`, `bid_condition`, `ask_size`, @@ -858,8 +858,6 @@ pub struct QuoteTick { pub strike: f64, /// Contract right: `'C'` for a call, `'P'` for a put. `'\0'` on single-contract queries. pub right: char, - /// Pre-computed midpoint: `(bid + ask) / 2.0`. - pub midpoint: f64, } impl QuoteTick { diff --git a/thetadatadx-rs/src/tdbe/types/generated/tick_layout_asserts.rs b/thetadatadx-rs/src/tdbe/types/generated/tick_layout_asserts.rs index d8af9739..6adea26f 100644 --- a/thetadatadx-rs/src/tdbe/types/generated/tick_layout_asserts.rs +++ b/thetadatadx-rs/src/tdbe/types/generated/tick_layout_asserts.rs @@ -316,7 +316,6 @@ mod layout_asserts { assert_eq!(offset_of!(QuoteTick, expiration), 56); assert_eq!(offset_of!(QuoteTick, strike), 64); assert_eq!(offset_of!(QuoteTick, right), 72); - assert_eq!(offset_of!(QuoteTick, midpoint), 80); } #[test] diff --git a/thetadatadx-rs/tests/test_column_projection.rs b/thetadatadx-rs/tests/test_column_projection.rs index 6946eefb..2d17c7ec 100644 --- a/thetadatadx-rs/tests/test_column_projection.rs +++ b/thetadatadx-rs/tests/test_column_projection.rs @@ -277,9 +277,8 @@ fn option_contract_projects_exactly_one_symbol_column() { /// attributable to its underlying, rather than dropping the column silently. #[test] fn multi_symbol_snapshot_projects_per_row_symbol_column() { - // The quote columns a multi-symbol snapshot's wire carries (no `date`; - // `midpoint` rides on `bid`+`ask`), plus the per-row roots the seam reads - // off the varying `symbol` column. + // The quote columns a multi-symbol snapshot's wire carries (no `date`), + // plus the per-row roots the seam reads off the varying `symbol` column. let present = ColumnPresence::from_names([ "ms_of_day", "bid_size", @@ -290,7 +289,6 @@ fn multi_symbol_snapshot_projects_per_row_symbol_column() { "ask_exchange", "ask", "ask_condition", - "midpoint", ]) .with_symbols(["AAPL", "MSFT", "SPY"]); let quote = |bid: f64, ask: f64| QuoteTick { @@ -307,7 +305,6 @@ fn multi_symbol_snapshot_projects_per_row_symbol_column() { expiration: 0, strike: 0.0, right: '\0', - midpoint: (bid + ask) / 2.0, }; let ticks = vec![quote(1.0, 2.0), quote(3.0, 4.0), quote(5.0, 6.0)]; @@ -344,8 +341,7 @@ fn multi_symbol_snapshot_projects_per_row_symbol_column() { /// repeated on every row (no regression from the per-row addition). #[test] fn single_symbol_snapshot_still_broadcasts_constant() { - let present = - ColumnPresence::from_names(["ms_of_day", "bid", "ask", "midpoint"]).with_symbol("AAPL"); + let present = ColumnPresence::from_names(["ms_of_day", "bid", "ask"]).with_symbol("AAPL"); let quote = QuoteTick { ms_of_day: 1, bid_size: 0, @@ -360,7 +356,6 @@ fn single_symbol_snapshot_still_broadcasts_constant() { expiration: 0, strike: 0.0, right: '\0', - midpoint: 1.5, }; let ticks = vec![quote, quote]; let batch = ticks.as_slice().to_arrow_projected(&present).unwrap(); @@ -585,34 +580,6 @@ fn calendar_year_headers_project_all_v3_fields() { ); } -/// `QuoteTick.midpoint` is computed at decode from `bid` + `ask` and is never a -/// wire header, so the projected frame must key its presence on both inputs: -/// present when bid + ask are, absent otherwise. Regression guard — a decode -/// path that dropped midpoint left `ticks[0].midpoint` populated while the -/// frame omitted the column. -#[test] -fn quote_projects_midpoint_when_bid_and_ask_present() { - let with = projected_columns::(&[ - "ms_of_day", - "bid_size", - "bid", - "ask_size", - "ask", - "date", - ]); - assert!( - with.contains(&"midpoint".to_string()), - "midpoint must ride whenever bid + ask do; got {with:?}" - ); - - // A subset without bid/ask carries no midpoint. - let without = projected_columns::(&["ms_of_day", "date"]); - assert!( - !without.contains(&"midpoint".to_string()), - "midpoint must be absent when its inputs are; got {without:?}" - ); -} - // ── The full (hand-built) path is unchanged ─────────────────────────────── /// `to_arrow` (no presence) still emits the complete schema — the diff --git a/thetadatadx-rs/tests/test_frames_arrow.rs b/thetadatadx-rs/tests/test_frames_arrow.rs index d7bf9e01..26984bfe 100644 --- a/thetadatadx-rs/tests/test_frames_arrow.rs +++ b/thetadatadx-rs/tests/test_frames_arrow.rs @@ -72,31 +72,6 @@ fn ohlc_tick_to_arrow_schema_matches_python() { assert_eq!(dtype_of(&batch, "right"), DataType::Utf8); } -#[test] -fn quote_tick_to_arrow_emits_midpoint() { - let ticks = vec![tick::QuoteTick { - ms_of_day: 34200000, - bid_size: 10, - bid_exchange: 1, - bid: 99.99, - bid_condition: 0, - ask_size: 20, - ask_exchange: 2, - ask: 100.01, - ask_condition: 0, - date: 20240102, - midpoint: 100.0, - expiration: 0, - strike: 0.0, - right: '\0', - }]; - let batch = ticks.as_slice().to_arrow().unwrap(); - assert_eq!(batch.num_rows(), 1); - let cols = columns(&batch); - assert!(cols.contains(&"midpoint".to_string())); - assert_eq!(dtype_of(&batch, "midpoint"), DataType::Float64); -} - #[test] fn option_contract_right_stringifies() { let ticks = vec![ diff --git a/thetadatadx-rs/tests/test_frames_polars.rs b/thetadatadx-rs/tests/test_frames_polars.rs index 06e68426..c7825add 100644 --- a/thetadatadx-rs/tests/test_frames_polars.rs +++ b/thetadatadx-rs/tests/test_frames_polars.rs @@ -74,30 +74,6 @@ fn ohlc_tick_to_polars_emits_contract_tail() { assert_eq!(dtype_of(&df, "right"), DataType::String); } -#[test] -fn quote_tick_to_polars_emits_midpoint() { - let ticks = vec![tick::QuoteTick { - ms_of_day: 34200000, - bid_size: 10, - bid_exchange: 1, - bid: 99.99, - bid_condition: 0, - ask_size: 20, - ask_exchange: 2, - ask: 100.01, - ask_condition: 0, - date: 20240102, - midpoint: 100.0, - expiration: 0, - strike: 0.0, - right: '\0', - }]; - let df = ticks.as_slice().to_polars().unwrap(); - assert_eq!(df.height(), 1); - assert!(columns(&df).contains(&"midpoint".to_string())); - assert_eq!(dtype_of(&df, "midpoint"), DataType::Float64); -} - #[test] fn option_contract_right_stringifies() { let ticks = vec![ diff --git a/thetadatadx-rs/tick_schema.toml b/thetadatadx-rs/tick_schema.toml index 2a2b1bb1..d97f7692 100644 --- a/thetadatadx-rs/tick_schema.toml +++ b/thetadatadx-rs/tick_schema.toml @@ -113,7 +113,7 @@ ts_class_vec = "trade_ticks_to_class_vec" pyclass = "TradeTick" [types.QuoteTick] -doc = """Quote tick -- 10 fields + midpoint. NBBO quote data. +doc = """Quote tick -- 10 fields. NBBO quote data. Wire layout: the full shape is 11 columns (`ms_of_day`, `bid_size`, `bid_exchange`, `bid`, `bid_condition`, `ask_size`, diff --git a/thetadatadx-ts/index.d.ts b/thetadatadx-ts/index.d.ts index d494908e..47146af6 100644 --- a/thetadatadx-ts/index.d.ts +++ b/thetadatadx-ts/index.d.ts @@ -6176,7 +6176,6 @@ export interface QuoteTick { ask: number askCondition: number date: number - midpoint: number expiration?: number strike?: number right?: string diff --git a/thetadatadx-ts/src/_generated/tick_classes.rs b/thetadatadx-ts/src/_generated/tick_classes.rs index 4bf52756..8dc11259 100644 --- a/thetadatadx-ts/src/_generated/tick_classes.rs +++ b/thetadatadx-ts/src/_generated/tick_classes.rs @@ -407,7 +407,6 @@ pub struct QuoteTick { pub ask: f64, pub ask_condition: i32, pub date: i32, - pub midpoint: f64, pub expiration: Option, pub strike: Option, pub right: Option, @@ -1091,7 +1090,6 @@ fn quote_ticks_to_class_vec(ticks: &[tick::QuoteTick]) -> Vec { ask: t.ask, ask_condition: t.ask_condition, date: t.date, - midpoint: t.midpoint, expiration: t.has_contract_id().then_some(t.expiration), strike: t.has_contract_id().then_some(t.strike), right: if t.right == '\0' { None } else { Some(t.right.to_string()) }, @@ -2701,7 +2699,6 @@ fn quote_tick_reconstruct_rows(rows: Vec) -> napi::Result 'C', Some("P") => 'P', None | Some("") => '\0', Some(other) => return Err(napi::Error::from_reason(format!("[InvalidParameterError] right must be \"C\" or \"P\", got {other:?}"))) }, diff --git a/tools/mcp/src/main.rs b/tools/mcp/src/main.rs index 7236747f..4926743e 100644 --- a/tools/mcp/src/main.rs +++ b/tools/mcp/src/main.rs @@ -692,7 +692,6 @@ fn serialize_quote_ticks(ticks: &[thetadatadx::QuoteTick]) -> Value { "ask_size": t.ask_size, "ask_exchange": t.ask_exchange, "ask_condition": t.ask_condition, - "midpoint": t.midpoint, }); insert_contract_id_fields(&mut row, t.expiration, t.strike, t.right); row @@ -2160,7 +2159,7 @@ mod tests { // the serialized JSON output. #[test] - fn serialize_quote_ticks_includes_condition_and_midpoint_fields() { + fn serialize_quote_ticks_includes_condition_fields() { let tick = QuoteTick { ms_of_day: 0, bid_size: 100, @@ -2175,14 +2174,12 @@ mod tests { expiration: 0, strike: 0.0, right: '\0', - midpoint: 150.5, }; let payload = serialize_quote_ticks(&[tick]); let row = payload["ticks"].as_array().unwrap().first().unwrap(); for key in [ "bid_condition", "ask_condition", - "midpoint", "bid_exchange", "ask_exchange", ] { diff --git a/tools/server/src/format.rs b/tools/server/src/format.rs index b79fd9d0..4493fdad 100644 --- a/tools/server/src/format.rs +++ b/tools/server/src/format.rs @@ -1648,7 +1648,6 @@ fn representative_output(ep: &EndpointMeta) -> EndpointOutput { expiration: id_expiration, strike: id_strike, right: id_right, - midpoint: 0.0, }])) } ReturnType::TradeQuoteTicks => { @@ -2636,7 +2635,6 @@ mod tests { expiration: 20260417, strike: 150.0, right: 'C', - midpoint: 5.0, }; let r = quote_ticks_to_json(&[t]); let r = r.first().unwrap(); @@ -2876,7 +2874,6 @@ mod tests { expiration, strike, right, - midpoint: 5.0, } }