TBBO Data
TickData columns (bid/ask price/size, spread, volume, delta), data.col() free-form access, DayPrep, OHLC bars.
TickData
The per-day tick struct passed to prepare() and calculate().
All slices have the same length (data.len()); index i corresponds to the same tick across all arrays. Optional columns (volume, delta, vwap) may be empty if the source parquet lacks them. Always check has_volume(), has_delta(), has_vwap() or use col(name) for safe lookup.
Parameters
timestamp(Vec<i64>): Tick timestamp in millisecondsbid(Vec<f64>): Best-bid price per tickask(Vec<f64>): Best-ask price per tickmid(Vec<f64>): (bid + ask) / 2 per tickspread(Vec<f64>): ask - bid per tickbid_size(Vec<f64>): Best-bid size per tickask_size(Vec<f64>): Best-ask size per tickvolume(Vec<f64>): Trade volume per tick (may be empty)delta(Vec<f64>): Signed buyer-seller delta per tick (may be empty)vwap(Vec<f64>): Session VWAP at this tick (may be empty)extras(HashMap<String, Vec<f64>>): Any extra columns the parquet emitted
Example
// Direct field access (always safe for core fields):
for i in 0..data.len() {
let micro = (data.bid[i] * data.ask_size[i] + data.ask[i] * data.bid_size[i])
/ (data.bid_size[i] + data.ask_size[i] + 1e-9);
// ...
}
data.col(name)
data.col(name)fn col(&self, name: &str) -> Option<&[f64]>
Free-form by-name column access. Covers core fields, optional fields, and arbitrary extras.
Returns None when a column is absent or empty (the optional ones may not exist on every parquet). Pair with unwrap_or(&[]) or use col_or(name, fallback) for a default.
Name aliases: "bid_size" / "bidSize" / "bid_vol" all return the bid-size slice; "ask_size" / "askSize" / "ask_vol" all return the ask-size slice; "mid" and "close" both return the mid slice.
Parameters
name(&str, defaultrequired): Column name
Returns
Option<&[f64]>
Example
// Safe access to optional columns
let volume = data.col("volume").unwrap_or(&[]);
let delta = data.col("delta").unwrap_or(&[]);
// Custom column from the parquet (e.g. a feature engineered upstream)
if let Some(feat) = data.col("my_feature") {
// use feat[i]
}
// With a fallback in one call
let vols = data.col_or("volume", &[]);
has_volume / has_delta / has_vwap
Boolean checks for the optional columns.
Example
if data.has_volume() {
let cum_vol: f64 = data.volume.iter().sum();
qc_log!("total volume = {:.0}", cum_vol);
}
data.ohlc_bars(bucket_ms)
data.ohlc_bars(bucket_ms)fn ohlc_bars(&self, bucket_ms: i64) -> Vec<OhlcBar>
Aggregate ticks into fixed-size OHLC time buckets on the mid-price axis.
Useful when an indicator needs bar-level context (e.g. session-anchored buckets, day-of-week stats, intraday regime detection). bucket_ms must be > 0 (60_000 = 1m, 300_000 = 5m, 3_600_000 = 1h).
Returns Vec<OhlcBar> with start_ms, end_ms, open, high, low, close, volume, tick_count. When volume is absent on the source data, falls back to bid_size + ask_size per tick.
Parameters
bucket_ms(i64, defaultrequired): Bucket size in milliseconds
Returns
Vec
Example
let bars_5m = data.ohlc_bars(5 * 60_000);
qc_log!("got {} 5-minute bars", bars_5m.len());
// Use in prepare() for hoisted bar-level context
fn prepare(data: &TickData) -> DayPrep {
let mut prep = DayPrep::empty();
let bars = data.ohlc_bars(60_000);
let closes: Vec<f64> = bars.iter().map(|b| b.close).collect();
prep.insert_f64("bar_closes_1m", closes);
prep
}
DayPrep
Storage for hoisted per-day arrays. Returned by prepare(), read in calculate().
Four typed maps: f64_arrays, bool_arrays, i64_arrays, scalars. Insert with insert_f64/insert_bool/insert_i64/insert_scalar; read with f64/bool_arr/i64_arr/scalar (each returns Option).
Use this for any computation that does not depend on swept parameters: full-series TA arrays, rolling baselines, day-level summary stats, regime masks.
Example
fn prepare(data: &TickData) -> DayPrep {
let mut prep = DayPrep::empty();
let atr = ta::atr_bid_ask(&data.bid, &data.ask, 30);
let baseline = rolling_mean(&data.spread, 200);
let day_open = data.mid.first().copied().unwrap_or(f64::NAN);
prep.insert_f64("atr", atr);
prep.insert_f64("spread_baseline", baseline);
prep.insert_scalar("day_open", day_open);
prep
}
fn calculate(&self, data: &TickData, prep: &DayPrep) -> SignalOutput {
let atr = prep.f64("atr").unwrap_or(&[]);
let baseline = prep.f64("spread_baseline").unwrap_or(&[]);
let day_open = prep.scalar("day_open").unwrap_or(0.0);
// ...
}
qc_log!
qc_log!qc_log!("fmt {}", arg)
Macro: write a debug line to the in-app terminal.
Forwards eprintln! through the runner's stderr to the renderer, surfaced in the Terminal pane.
Example
qc_log!("loaded {} ticks, has_volume={}", data.len(), data.has_volume());
qc_log!("avg spread = {:.4}", data.spread.iter().sum::<f64>() / data.len() as f64);