# D01-F01-A05 — Tick-Imbalance Bars

> **Domain:** Market Data Engineering  
> **Family:** Bar Construction  
> **Package status:** Article-ready; code, tests, and teaching visuals validated

## Executive summary

A Tick-Imbalance Bar (TIB) groups consecutive eligible trades until the absolute sum of causal tick signs reaches a threshold known before the bar begins. The method comes from Section 2.3.2.1 of Marcos López de Prado's *Advances in Financial Machine Learning*. That source defines the tick rule, cumulative imbalance, and expectation-based stopping idea. It does **not** make this package's seeds, EMA coefficients, floor, multiplier, session reset, or partial-tail policy universal defaults.

This package implements one reproducible, session-local teaching variant. It answers:

> What is the first trade for which cumulative signed ticks reach the threshold frozen at this bar's start?

The result is an auditable OHLCV stream, not a prediction or trading recommendation. Adaptive state changes sampling membership; it does not forecast returns.

## Learning contract

After this topic, a reader can:

- calculate tick signs, cumulative imbalance, and the closing boundary by hand;
- distinguish the source methodology from this package's engineering choices;
- implement identical causal state in Python and TypeScript;
- test flat trades, equality, fractional-threshold overshoot, session resets, tails, and timestamp ties;
- explain why an end-of-day price series cannot validate a transaction-level TIB episode.

Prerequisites are **Tick Bars**, the tick rule, exponential moving averages (EMAs), and basic OHLCV aggregation.

## Source method versus package choices

| Item | Source method | This package |
|---|---|---|
| Tick sign | Uptick +1, downtick −1, flat carries the prior sign | Same |
| Sign at a boundary | The book carries the terminal sign from the preceding bar | Carries across bars within a session; resets to `initialTickSign` at a new session |
| Closing idea | First contiguous subset whose absolute tick imbalance reaches expected magnitude | Same causal idea |
| Expected length | Estimated from prior bar lengths, for example with an EWMA | One-step EMA updated after each threshold-closed bar |
| Expected tick imbalance | Estimated from prior tick signs, for example with an EWMA | One-step EMA of each completed bar's mean tick sign |
| Initialization | Must be supplied by an implementation or warm-up procedure | Explicit `initialExpectedTicks` and `initialExpectedTickImbalance` |
| Floor and multiplier | Not part of the cited defining equation | Explicit stability and teaching controls |
| Session and tail handling | Data-pipeline choices | Full state reset by explicit session; configurable partial emission |

The phrase **Tick-Imbalance Bars** therefore names a family of source-grounded stopping rules, not one vendor-neutral parameter set.

## Input data contract

Each call contains one symbol and one currency.

| Field | Type | Nullable | Meaning |
|---|---|:---:|---|
| `tradeId` | non-empty string | No | Unique identifier after correction/cancel resolution |
| `timestamp` | ISO-8601 UTC ending `Z` | No | Chosen event-time field |
| `sequence` | integer | Conditional | Required on both records in a timestamp tie; strictly increasing |
| `session` | non-empty string | No | Explicit reset key; a closed session may not reappear |
| `symbol` | non-empty string | No | One instrument per call |
| `price` | finite number > 0 | No | Eligible execution price |
| `volume` | finite number > 0 | No | Eligible quantity in shares |
| `currency` | non-empty string | No | One currency per call |

Trades must be corrected, deduplicated, sale-condition filtered, and globally chronological before this algorithm runs. Equal timestamps are not interchangeable: source sequence decides which price change is observed first. The current [NYSE Daily TAQ specification](https://www.nyse.com/publicdocs/nyse/data/Daily_TAQ_Client_Spec_v4.3.pdf) documents time, sale condition, correction indicator, sequence number, trade ID, volume, and price fields. [FINRA's trade-reporting FAQ](https://www.finra.org/filing-reporting/market-transparency-reporting/trade-reporting-faq) shows why execution time and later corrections require an explicit upstream policy.

This implementation rejects missing fields, NaN, infinity, zero or negative price/volume, duplicate IDs, decreasing time, unresolved ties, mixed instruments/currencies, and reappearing sessions. It does not silently sort or repair input.

### Configuration

| Key | Domain | Package meaning |
|---|---|---|
| `initialTickSign` | −1 or +1 | Sign assigned to the first trade of each session |
| `initialExpectedTicks` | finite > 0 | Seed for expected complete-bar length |
| `initialExpectedTickImbalance` | finite in [−1, 1] | Seed for expected mean tick sign |
| `alphaTicks` | (0, 1] | EMA coefficient for completed bar length |
| `alphaTickImbalance` | (0, 1] | EMA coefficient for completed bar mean sign |
| `thresholdFloor` | finite > 0 | Minimum absolute boundary |
| `thresholdMultiplier` | finite > 0 | Sensitivity applied before the floor |
| `closePartial` | boolean | Emit an incomplete session/stream tail when true |

## Mathematical formulation

For trade $i$ with price $p_i$, the session's first sign is the configured seed $s_0$. Later signs follow the tick rule:

$$
b_i =
\begin{cases}
+1, & p_i > p_{i-1},\\
-1, & p_i < p_{i-1},\\
b_{i-1}, & p_i = p_{i-1}.
\end{cases}
$$

The current bar's cumulative tick imbalance after $T$ trades is

$$
\theta_T=\sum_{i=1}^{T} b_i.
$$

At bar opening, freeze

$$
h_k=\max\left(h_{\min},\lambda\widehat E_k[T]\left|\widehat E_k[b]\right|\right),
$$

where $h_{\min}$ is the package floor and $\lambda$ is the package multiplier. The complete bar closes at

$$
T_k^*=\min\{T\ge1:|\theta_T|\ge h_k\}.
$$

Equality closes. A trade is indivisible, so a fractional threshold such as 2.5 closes when the integer imbalance first reaches 3; the crossing trade remains wholly in that bar.

Only after a threshold closure do the expectations update:

$$
\widehat E_{k+1}[T]=(1-\alpha_T)\widehat E_k[T]+\alpha_T T_k^*,
$$

$$
\widehat E_{k+1}[b]=(1-\alpha_b)\widehat E_k[b]+\alpha_b\frac{\theta_{T_k^*}}{T_k^*}.
$$

Partial bars do not update either expectation. At a new session, the previous price, tick sign, expectations, and open-bar state reset to the disclosed seeds.

## Worked example

Use `initialTickSign=+1`, $\widehat E_0[T]=8$, $\widehat E_0[b]=0.5$, $h_{\min}=3$, and $\lambda=1$. The opening threshold is $\max(3,8\times0.5)=4$.

| Trade | Price | Rule | $b_i$ | $\theta_i$ | Decision |
|---:|---:|---|---:|---:|---|
| 1 | 100.00 | session seed | +1 | 1 | keep open |
| 2 | 100.00 | flat carries +1 | +1 | 2 | keep open |
| 3 | 100.10 | uptick | +1 | 3 | keep open |
| 4 | 100.05 | downtick | −1 | 2 | keep open |
| 5 | 100.10 | uptick | +1 | 3 | keep open |
| 6 | 100.15 | uptick | +1 | 4 | equality closes |

With $\alpha_T=0.25$ and $\alpha_b=0.5$, the next estimates are 7.5 ticks and $0.583333\ldots$, so the next frozen threshold is 4.375. The machine-readable derivation is in [`examples/worked-example.json`](./examples/worked-example.json).

## Algorithm

```text
validate the complete input and configuration
for each explicit session:
    reset sign, expected state, previous price, and open bar to disclosed seeds
    freeze the opening threshold
    for each eligible trade in stable event order:
        assign the causal tick sign; flat price carries the prior sign
        append the whole trade and update OHLCV plus theta
        if abs(theta) >= frozen threshold:
            emit a complete bar
            update expectations from that completed bar only
            freeze the next threshold
    emit or discard the tail according to closePartial
```

The previous price and sign carry across a complete-bar boundary inside the same session. The threshold and expectations do not change while a bar is open.

## Output contract

Each bar contains OHLC, share volume, exact unrounded price-times-volume sum rounded only at output, tick count, session, first/last trade IDs, first/last timestamps, `imbalance`, the frozen `threshold`, and `closeReason`.

`closeReason` is:

- `threshold` for a complete bar that updates expectations;
- `session_end` for an emitted incomplete tail before a session reset;
- `stream_end` for an emitted incomplete final tail.

With `closePartial=true`, every valid input trade belongs to exactly one output bar. With `false`, tail observations are intentionally omitted and the conservation invariant applies only to emitted complete bars.

## Implementation and verification

- [`implementations/python/bars.py`](./implementations/python/bars.py)
- [`implementations/typescript/bars.ts`](./implementations/typescript/bars.ts)
- [`tests/shared_fixture.json`](./tests/shared_fixture.json)
- [`datasets/synthetic-trades.csv`](./datasets/synthetic-trades.csv)

The shared fixture contains 240 deterministic trades in two sessions and produces 12 bars under its stored configuration. Focused tests cover the worked conventions, cross-language equality, validation, ties, session isolation, and tail policy. Complexity is $O(n)$ time. This readable implementation retains the open bar's trades, so working memory is $O(q)$ for the largest bar.

## Visual explanations

The causal flow is in [`visuals/mermaid/construction-flow.md`](./visuals/mermaid/construction-flow.md). The static assets separate exact canonical state from interpretation:

- [`construction-anatomy.svg`](./visuals/static/construction-anatomy.svg) follows the six-trade worked example;
- [`boundary-and-state.svg`](./visuals/static/boundary-and-state.svg) distinguishes frozen threshold, integer overshoot, and session reset;
- [`article-hero.svg`](./visuals/static/article-hero.svg) labels its price path as context and its signed-tick path as the decision state.

The [interactive playground](./visuals/animated/playground.html) provides canonical, flat-tick, and session-reset scenarios with Back, Step, Play/Pause, Reset, multiplier control, audit trace, responsive layout, and reduced-motion behavior.

## Failure modes and limitations

- A tick sign is a price-change classification, not proof of buyer or seller intent.
- Sale-condition selection, corrections, cancels, late reports, and source-time choice can change every later sign and bar boundary.
- Initialization and EMA choices materially affect early bars and may create unstable or very long bars.
- The floor prevents a zero expected imbalance from collapsing the boundary; it is a package extension, not part of the cited defining equation.
- Session reset prevents state leakage but differs from continuous-market or cross-session conventions.
- Adaptive sampling is not predictive evidence. It changes when observations are recorded; economic usefulness needs separate, bias-controlled, out-of-sample study.
- OHLCV is lossy and cannot reconstruct the original tape.

## Historical-example and data-licensing boundary

No real historical order-flow episode is presented as fact here. A defensible episode needs the exact transaction tape, timestamp/sequence semantics, correction state, sale-condition filter, session calendar, algorithm configuration, and redistribution rights. End-of-day OHLCV from FMP or another data provider cannot reproduce trade membership and is therefore insufficient evidence.

A future episode is **medium priority** only if licensed, reproducible tick data can be archived with a source note. Public teaching should then distinguish the observed tape, the package's derived bars, and any interpretation. Until that gate is met, deterministic synthetic data is the more honest evidence for construction correctness.

## Related topics

- **Tick Bars** — fixed trade-count sampling.
- **Volume-Imbalance Bars** — signs weighted by share quantity.
- **Tick-Run Bars** — dominant-side run state rather than net signed ticks.
- **OHLC Consistency Validator** — output invariant checks.

## References

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