use arrow_array::builder::{StringBuilder, UInt32Builder};
use arrow_array::RecordBatch;
use arrow_schema::{DataType, Field, Schema};
use std::sync::Arc;
use opendeviationbar_core::arrow_export::opendeviationbar_vec_to_record_batch;
use crate::live_engine::CompletedBar;
pub fn completed_bars_to_record_batch(bars: &[CompletedBar]) -> RecordBatch {
if bars.is_empty() {
let core_batch = opendeviationbar_vec_to_record_batch(&[]);
return append_metadata_columns(core_batch, &[], &[]);
}
let odb_bars: Vec<&opendeviationbar_core::types::OpenDeviationBar> =
bars.iter().map(|cb| &cb.bar).collect();
let core_batch = opendeviationbar_vec_to_record_batch_borrowed(&odb_bars);
let symbols: Vec<&str> = bars.iter().map(|cb| &*cb.symbol).collect();
let thresholds: Vec<u32> = bars.iter().map(|cb| cb.threshold_decimal_bps).collect();
append_metadata_columns(core_batch, &symbols, &thresholds)
}
fn append_metadata_columns(
core_batch: RecordBatch,
symbols: &[&str],
thresholds: &[u32],
) -> RecordBatch {
let n = core_batch.num_rows();
let mut symbol_builder = StringBuilder::with_capacity(n, n * 10);
let mut threshold_builder = UInt32Builder::with_capacity(n);
for &sym in symbols {
symbol_builder.append_value(sym);
}
for &thr in thresholds {
threshold_builder.append_value(thr);
}
let mut fields: Vec<Arc<Field>> = core_batch.schema().fields().iter().cloned().collect();
fields.push(Arc::new(Field::new("_symbol", DataType::Utf8, false)));
fields.push(Arc::new(Field::new(
"_threshold_decimal_bps",
DataType::UInt32,
false,
)));
let extended_schema = Arc::new(Schema::new(fields));
let mut columns: Vec<Arc<dyn arrow_array::Array>> = core_batch.columns().to_vec();
columns.push(Arc::new(symbol_builder.finish()));
columns.push(Arc::new(threshold_builder.finish()));
RecordBatch::try_new(extended_schema, columns)
.expect("Failed to create extended RecordBatch with metadata columns")
}
fn opendeviationbar_vec_to_record_batch_borrowed(
bars: &[&opendeviationbar_core::types::OpenDeviationBar],
) -> RecordBatch {
let owned: Vec<opendeviationbar_core::types::OpenDeviationBar> =
bars.iter().map(|b| (*b).clone()).collect();
opendeviationbar_vec_to_record_batch(&owned)
}
#[cfg(test)]
mod tests {
use super::*;
use opendeviationbar_core::types::OpenDeviationBar;
fn make_test_bar() -> OpenDeviationBar {
OpenDeviationBar::default()
}
#[test]
fn test_completed_bars_to_record_batch_empty() {
let batch = completed_bars_to_record_batch(&[]);
assert_eq!(batch.num_rows(), 0);
assert!(batch.schema().field_with_name("_symbol").is_ok());
assert!(batch
.schema()
.field_with_name("_threshold_decimal_bps")
.is_ok());
}
#[test]
fn test_completed_bars_to_record_batch_single() {
let bars = vec![CompletedBar {
symbol: Arc::from("BTCUSDT"),
threshold_decimal_bps: 250,
bar: make_test_bar(),
}];
let batch = completed_bars_to_record_batch(&bars);
assert_eq!(batch.num_rows(), 1);
let sym_col = batch
.column_by_name("_symbol")
.unwrap()
.as_any()
.downcast_ref::<arrow_array::StringArray>()
.unwrap();
assert_eq!(sym_col.value(0), "BTCUSDT");
let thr_col = batch
.column_by_name("_threshold_decimal_bps")
.unwrap()
.as_any()
.downcast_ref::<arrow_array::UInt32Array>()
.unwrap();
assert_eq!(thr_col.value(0), 250);
}
#[test]
fn test_completed_bars_to_record_batch_mixed() {
let bars = vec![
CompletedBar {
symbol: Arc::from("BTCUSDT"),
threshold_decimal_bps: 250,
bar: make_test_bar(),
},
CompletedBar {
symbol: Arc::from("ETHUSDT"),
threshold_decimal_bps: 500,
bar: make_test_bar(),
},
CompletedBar {
symbol: Arc::from("BTCUSDT"),
threshold_decimal_bps: 500,
bar: make_test_bar(),
},
];
let batch = completed_bars_to_record_batch(&bars);
assert_eq!(batch.num_rows(), 3);
let sym_col = batch
.column_by_name("_symbol")
.unwrap()
.as_any()
.downcast_ref::<arrow_array::StringArray>()
.unwrap();
assert_eq!(sym_col.value(0), "BTCUSDT");
assert_eq!(sym_col.value(1), "ETHUSDT");
assert_eq!(sym_col.value(2), "BTCUSDT");
}
}