# D01-F01-A03 - Volume Bars

> **Domain:** Market Data Engineering  
> **Family:** Bar Construction  
> **Package convention:** whole-trade assignment, session reset, optional partial tails

## Executive summary

A volume bar closes when cumulative **eligible share quantity** first reaches or exceeds a configured target. Clock time does not decide membership. This package makes the choices that charts often hide explicit: which prints are eligible, how ties are ordered, what happens to a threshold-crossing trade, when state resets, and whether incomplete session tails are emitted.

The reference convention keeps each trade whole. If 750 shares are already in the open bar and a 500-share trade arrives against a 1,000-share target, the bar closes at 1,250 shares. The 250-share overshoot is neither split nor carried as a numerical credit into the next bar.

This is market-data engineering education, not investment advice. Passing the tests establishes construction fidelity under this convention; it does not establish that volume bars predict returns or improve a strategy.

## Learning contract

After this topic, a reader can:

- distinguish share-volume sampling from trade-count and notional sampling;
- specify ordering, eligibility, session, boundary, overshoot, and partial-tail policies;
- calculate exact volume-bar membership and OHLCV values by hand;
- implement the same causal rule in Python and TypeScript;
- test equality, overshoot, reset, invalid input, and cross-language parity.

Prerequisites are eligible trade records, OHLC aggregation, and exchange-session concepts. **Tick Bars** is the recommended preceding topic. Split-trade allocation, quote bars, adaptive volume thresholds, and vendor-specific sale-condition matrices are outside this package.

## Why the feed contract comes first

Official NYSE Daily TAQ documentation describes trade files containing trade time, symbol, quantity, price, sale condition, and message-sequence information, and says the files are sorted by symbol, time, and message sequence. It defines trade volume as the number of shares traded ([NYSE Daily TAQ Client Specification v4.3, pp. 16-18](https://www.nyse.com/publicdocs/nyse/data/Daily_TAQ_Client_Spec_v4.3.pdf)). FINRA separately documents execution-time, fractional-share, odd-lot, correction, cancellation, and reversal rules ([FINRA Trade Reporting FAQ](https://www.finra.org/filing-reporting/market-transparency-reporting/trade-reporting-faq)). These sources describe reporting fields and rules; they do not prescribe this package's bar threshold or eligibility filter.

The reference function therefore accepts only an **already cleaned eligible stream**. It does not guess whether a sale condition belongs in a research universe, repair a cancellation, or reorder a late report.

## Definitions and variants

| Term | Package meaning |
|---|---|
| Eligible trade | A corrected, deduplicated execution retained by an upstream documented sale-condition policy. |
| Share volume | The reported share quantity on an eligible trade. It is not trade count, contracts, lots, or price times quantity. |
| Complete bar | A bar closed because cumulative share volume is at least the target. |
| Partial bar | Valid remaining trades below the target, emitted at a session or stream boundary only when `closePartial=true`. |
| Overshoot | `bar volume - targetVolume` for a complete bar. It stays inside that bar under the whole-trade convention. |
| Session | An explicit state partition supplied by the caller; it is not inferred from UTC date. |

| Variant | Crossing trade | Next bar starts with | Consequence |
|---|---|---|---|
| Canonical whole-trade | Assigned wholly to the closing bar | The next eligible trade | Observed trades remain indivisible; completed volume can exceed the target. |
| Split-trade | Divided at the threshold | A synthetic residual fragment | Exact target volumes, but invented fragments require price and lineage rules. |
| Residual-credit carry | Assigned wholly to the closing bar | Next trade plus numerical credit | Changes later boundaries without a contributing trade; not used here. |
| Continuous session | Same as selected crossing rule | State can span session labels | Suitable only when a meaningful reset boundary truly does not exist. |

## Input contract

One call represents one symbol and one currency.

| Field | Type | Unit | Nullable | Contract |
|---|---|---|:---:|---|
| `tradeId` | non-empty string | source identifier | No | Unique after corrections and cancels. |
| `timestamp` | ISO-8601 string ending `Z` | UTC execution time | No | Nondecreasing. Equal timestamps preserve caller order, which must already reflect the source sequence policy. |
| `session` | non-empty string | caller-defined key | No | Contiguous; a session cannot reappear after another session begins. |
| `symbol` | non-empty string | instrument ID | No | Identical for every record in one call. |
| `price` | finite number | currency per share | No | Strictly positive; used for OHLC and the diagnostic dollar sum. |
| `volume` | finite number | shares | No | Strictly positive eligible quantity. |
| `currency` | non-empty string | currency code | No | Identical for every record in one call. |

Configuration:

| Field | Type | Default | Meaning |
|---|---|---:|---|
| `targetVolume` | finite positive number | required | Share threshold `V*`. |
| `closePartial` | boolean | `true` | Emit valid below-target tails at session and stream end. |

Missing fields, duplicate IDs, reversed timestamps, non-finite numbers, mixed symbols or currencies, and reappearing sessions are rejected. The input array is the authoritative order for equal timestamps. Production ingestion should retain source sequence and receive/availability time separately even though the compact teaching schema does not expose them.

## Output contract

| Field | Meaning | Invariant |
|---|---|---|
| `barIndex` | Zero-based output order | Consecutive. |
| `session` | Input session partition | One session per bar. |
| `startTime`, `endTime` | First and last contributing execution times | For event bars, `endTime = lastTradeTime`. |
| `open`, `high`, `low`, `close` | First, maximum, minimum, last trade price | `low <= open, close <= high`. |
| `volume` | Sum of contributing share quantity | Complete bars meet/exceed target; partial bars are below it. |
| `dollarValue` | Sum of unrounded `price * volume` | Diagnostic only; it does not decide closure. |
| `tickCount` | Contributing record count | Positive. |
| `firstTradeId`, `lastTradeId` | Membership lineage | Input-order endpoints. |
| `closeReason` | `threshold`, `session_end`, or `stream_end` | Explains why the bar was emitted. |

Outputs are rounded to eight decimal places for fixture portability. Decimal arithmetic may be preferable for ledger-grade notional calculations.

## Mathematical formulation

For target share volume `V* > 0`, the next bar is the shortest consecutive sequence of eligible trades whose cumulative quantity reaches the target:

$$
T_j = \min\left\{T : \sum_{i=s_j}^{T} v_i \ge V^*\right\},
\qquad
B_j = \{s_j,\ldots,T_j\}.
$$

Here `s_j` is the first trade index after the previous complete bar or session reset, `v_i` is eligible shares on trade `i`, and `T_j` is the crossing-trade index. After a complete bar, `s_(j+1) = T_j + 1`; there is no overshoot credit.

For the member prices `p_i`:

$$
O=p_{s_j},\quad H=\max_{i\in B_j}p_i,\quad L=\min_{i\in B_j}p_i,\quad C=p_{T_j},
$$

$$
V=\sum_{i\in B_j}v_i,\qquad D=\sum_{i\in B_j}p_iv_i.
$$

Membership is decided before OHLCV is interpreted. The dollar sum `D` is an audit field, not the volume-bar stopping statistic.

## Worked example

The canonical synthetic example uses `targetVolume=1,000` shares and `closePartial=true`:

| Trade | Price | Shares | Open-bar cumulative shares | Decision |
|---|---:|---:|---:|---|
| W001 | 100.00 | 400 | 400 | Continue |
| W002 | 101.00 | 350 | 750 | Continue |
| W003 | 99.00 | 500 | 1,250 | Close bar 0; retain 250-share overshoot |
| W004 | 99.50 | 600 | 600 | Start from zero; continue |
| W005 | 100.50 | 400 | 1,000 | Close bar 1 at exact equality |
| W006 | 101.00 | 250 | 250 | Emit partial bar 2 at stream end |

Bar 0 is `O=100`, `H=101`, `L=99`, `C=99`, `V=1,250`, `D=124,850`, trades W001-W003. Bar 1 starts with W004, proving the 250-share overshoot was not carried. The full input and expected output are machine-readable in [`examples/worked-example.json`](./examples/worked-example.json).

## Causal algorithm

```text
validate the entire ordered input and configuration
current_bar = empty
for each trade in caller order:
    if session changed:
        emit current_bar as session_end only if closePartial
        discard/reset current_bar
    add the whole trade to current_bar
    if current_bar.volume >= targetVolume:
        emit current_bar as threshold
        reset current_bar to empty (no residual credit)
after the final trade:
    emit current_bar as stream_end only if closePartial
```

The decision uses only the current and earlier records. A later correction can require rebuilding affected bars from the corrected tape; silently mutating an already-published bar breaks point-in-time reproducibility.

## Implementations and tests

- Python: [`implementations/python/bars.py`](./implementations/python/bars.py)
- TypeScript: [`implementations/typescript/bars.ts`](./implementations/typescript/bars.ts)
- Shared 240-trade oracle: [`tests/shared_fixture.json`](./tests/shared_fixture.json)
- Human-checkable oracle: [`examples/worked-example.json`](./examples/worked-example.json)

Both implementations are one causal pass: `O(n)` time, `O(m)` output for `m` bars, and `O(1)` mutable bar state apart from validation sets for identities and sessions. Tests cover exact shared-fixture output, worked-example arithmetic, exact equality, overshoot disposal, partial-tail keep/drop policy, session isolation, equal timestamps, non-finite numbers, and symbol/currency consistency.

## Visual explanations

Before using the lab, inspect [`construction-anatomy.svg`](./visuals/static/construction-anatomy.svg): it maps the six exact worked-example trades into two complete bars and one partial bar. [`boundary-and-state.svg`](./visuals/static/boundary-and-state.svg) isolates overshoot, equality, and reset behavior. The [interactive playground](./visuals/animated/playground.html) uses the 240-trade synthetic fixture and exposes causal back/step/play controls, target changes, session selection, and closure diagnostics. Partial-tail keep/drop behavior is executable in the implementations and tests and shown exactly in the static fallback.

```mermaid
flowchart TD
    A["Receive next cleaned eligible trade"] --> B{"New session?"}
    B -->|Yes| C["Apply partial-tail policy; reset to zero"]
    B -->|No| D["Keep open bar"]
    C --> E["Add whole trade"]
    D --> E
    E --> F{"Cumulative shares >= target?"}
    F -->|No| A
    F -->|Yes| G["Emit complete OHLCV bar and lineage"]
    G --> H["Reset to zero; do not carry overshoot"]
    H --> A
```

## Failure modes and limitations

- **Eligibility drift:** including or excluding odd lots, auctions, average-price trades, late reports, or other sale conditions changes membership. Pin the source-specific policy.
- **Correction drift:** cancels and corrections can change every later boundary. Preserve raw versions and rebuild deterministically.
- **Ordering ambiguity:** execution timestamps can tie; preserve a documented message sequence or stable ingest order.
- **Unit mismatch:** shares, lots, contracts, and notional are not interchangeable. This package accepts shares.
- **Session leakage:** carrying a partial across a declared reset changes the next session's bars.
- **Threshold dependence:** a different target changes membership, OHLCV, and bar count. The 1,300-share corpus target is pedagogical, not a recommended calibration.
- **Compression:** OHLCV cannot reconstruct the original trade tape or within-bar path.
- **No performance inference:** a more even quantity clock does not by itself imply predictive or trading value.

## Data and real-world-example decision

The package retains deterministic synthetic trades. A named historical session would be a useful comparison only if transaction-level redistribution rights, sale-condition semantics, correction state, sequence order, and session calendar could all be documented. Aggregate daily or minute bars cannot verify which trades crossed a volume threshold. No suitable redistributable tape was established during this review, so introducing a named security would add apparent realism without reproducible membership evidence.

The SEC's MIDAS overview demonstrates why high-resolution execution data require specialized processing, but MIDAS does not prescribe this algorithm or its parameters ([SEC MIDAS](https://www.sec.gov/securities-topics/market-structure-analytics/midas-market-information-data-analytics-system)).

## Related topics

- **Time Bars:** fixed exchange-session time intervals.
- **Tick Bars:** fixed eligible-trade counts.
- **Dollar Bars:** fixed reported notional rather than shares.
- **OHLC Consistency Validator:** invariants for constructed bars.

See [`REFERENCES.md`](./REFERENCES.md) for source roles, scope, and limitations.
