# TBBO Data

TickData columns (bid/ask price/size, spread, volume, delta), data.col() free-form access, DayPrep, OHLC bars.

<a id="tickdata"></a>
## 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 milliseconds
- `bid` (`Vec<f64>`): Best-bid price per tick
- `ask` (`Vec<f64>`): Best-ask price per tick
- `mid` (`Vec<f64>`): (bid + ask) / 2 per tick
- `spread` (`Vec<f64>`): ask - bid per tick
- `bid_size` (`Vec<f64>`): Best-bid size per tick
- `ask_size` (`Vec<f64>`): Best-ask size per tick
- [`volume`](https://quantchartsllc.com/docs/python/py-data.md#volume) (`Vec<f64>`): Trade volume per tick (may be empty)
- [`delta`](https://quantchartsllc.com/docs/python/py-data.md#delta) (`Vec<f64>`): Signed buyer-seller delta per tick (may be empty)
- [`vwap`](https://quantchartsllc.com/docs/python/py-data.md#vwap) (`Vec<f64>`): Session VWAP at this tick (may be empty)
- `extras` (`HashMap<String, Vec<f64>>`): Any extra columns the parquet emitted

### Example

```rust
// 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);
    // ...
}
```

<a id="data-colname"></a>
## data.col(name)

```rust
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`, default `required`): Column name

### Returns

Option<&[f64]>

### Example

```rust
// 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", &[]);
```

<a id="has_volume-has_delta-has_vwap"></a>
## has_volume / has_delta / has_vwap

Boolean checks for the optional columns.

### Example

```rust
if data.has_volume() {
    let cum_vol: f64 = data.volume.iter().sum();
    qc_log!("total volume = {:.0}", cum_vol);
}
```

<a id="data-ohlc_barsbucket_ms"></a>
## data.ohlc_bars(bucket_ms)

```rust
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`](https://quantchartsllc.com/docs/python/py-data.md#open), [`high`](https://quantchartsllc.com/docs/python/py-data.md#high), [`low`](https://quantchartsllc.com/docs/python/py-data.md#low), [`close`](https://quantchartsllc.com/docs/python/py-data.md#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`, default `required`): Bucket size in milliseconds

### Returns

Vec<OhlcBar>

### Example

```rust
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
}
```

<a id="dayprep"></a>
## 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

```rust
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);
    // ...
}
```

<a id="qc_log"></a>
## qc_log!

```rust
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

```rust
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);
```
