Bars¶
A bar summarises a run of consecutive trades: open, high, low, close, volume, VWAP, and the buy/sell split of its volume. What differs between bar types is only where the boundaries fall, so that decision is a rule and everything else is shared.
Five rules ship with the package:
| Rule | A new bar every… | Threshold |
|---|---|---|
time |
fixed span of the clock | a duration ("1min", pd.Timedelta) |
tick |
N trades | a trade count |
volume |
N units of traded size | a size, in the instrument's units |
dollar |
N units of price × size | an amount of the quote currency |
imbalance |
N units of drift in signed size | a size, in the instrument's units |
A threshold means the same amount on a pipeline result (whole ticks and lots)
and on a display frame: bars(result.trades, "volume", 0.5) and
bars(display_result(result).trades, "volume", 0.5) both close a bar every
0.5 BTC. The integer frame adds whole lots exactly. On the display frame a
float sum can land just short of a threshold that a run of trades meets
exactly, so the two can put one trade in a different bar.
The last four sample the market by activity rather than by the clock. That is why the quant literature prefers them: a quiet hour and a busy minute produce the same number of bars, so bar returns come much closer to being independent and identically distributed.
See the "Build bars from trades" how-to for a worked example, and Extending for writing a rule of your own.
Functions¶
bars ¶
bars(
trades: DataFrame,
rule: str = "time",
threshold: Any = None,
*,
target_bars: int = 50,
sign_method: str | None = None,
quotes: DataFrame | None = None,
) -> pd.DataFrame
Resample trades into bars.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trades
|
DataFrame
|
Trades with at least |
required |
rule
|
str
|
Registered rule name: |
'time'
|
threshold
|
optional
|
How much of the rule's own quantity closes a bar: a duration for
|
None
|
target_bars
|
int
|
How many bars the default threshold aims at. Ignored when threshold is given. |
50
|
sign_method
|
str or None
|
How to classify the aggressor side: |
None
|
quotes
|
DataFrame
|
Book snapshots for the Lee–Ready classifier — a frame with
|
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per bar, in time order, with the columns in
:data:
Every bar holds at least one trade: a clock span in which nothing
traded produces no row. The last bar is whatever trades were left
over, so it may not have reached the threshold; drop it with
The frame's |
Raises:
| Type | Description |
|---|---|
ConfigError
|
If required columns are missing, the threshold does not suit the
rule, or a size or price rule runs on integer lots or ticks with no
|
UnknownNameError
|
If rule is not registered; the message names the registered names closest to it. |
Notes
The output columns keep the units the trades arrive in: a pipeline
result holds prices as whole ticks and sizes as whole lots, and so do its
bars. Convert the frame first (see
:func:~ob_analytics.visualization.display_result) for bars in the
quote currency and the instrument's units. The threshold does not
depend on this: it means the same amount on either frame.
Examples:
register_bar_rule ¶
Register rule under its own :attr:~ob_analytics.protocols.BarRule.name.
Case-insensitive; overwriting an existing registration is allowed, so a rule of your own may deliberately shadow a built-in one.
get_bar_rule ¶
Return the bar rule registered under name (case-insensitive).
Raises:
| Type | Description |
|---|---|
UnknownNameError
|
If no rule is registered under name; the message names the registered names closest to it. |
load_bar_rule_plugins ¶
Discover and register bar rules advertised via entry points.
Scans the :data:ENTRY_POINT_GROUP entry-point group; each entry's value
is loaded to a :class:~ob_analytics.protocols.BarRule class, instantiated
with no arguments, and registered under its own name. This is what
lets a bar rule live in a separate installable package without editing
ob-analytics.
The scan runs once per process unless force is set. A plug-in that fails to load is skipped with a warning, so one broken package cannot stop the rest from loading.
Returns:
| Type | Description |
|---|---|
list of str
|
The names newly registered by this call. |
The built-in rules¶
ClockRule ¶
A new bar every fixed span of the clock — classic OHLCV.
The threshold is a fixed duration: a string pandas reads as one
("5s", "1min", "1h") or a :class:pandas.Timedelta. A
calendar step with no fixed length (a month, a business day) is not a
duration and is rejected.
Bars are aligned to the epoch, so "1min" cuts on the minute whatever
the first trade's timestamp. A span in which nothing traded produces no
bar: every row :func:bars returns holds at least one trade.
TickRule ¶
A new bar every N trades, whatever their size.
The threshold is that count. "Tick" here is the market-data sense of one printed trade, not the price increment.
AccumulationRule
dataclass
¶
AccumulationRule(
name: str,
quantity: Callable[[DataFrame], ndarray],
signed: bool = False,
units: tuple[str, ...] = (),
)
A new bar every N units of some quantity the trades carry.
One shape covers the three activity rules; they differ only in what they accumulate and in whether that quantity is signed:
"volume"— traded size."dollar"— price × size."imbalance"— signed size,+for buyer-initiated trades and-for seller-initiated ones.
An unsigned rule closes a bar when the running total reaches the threshold. A signed rule closes one when the running total reaches the threshold in either direction, so a bar ends on a burst of one-sided flow and a balanced stretch of trading stays inside one bar.
The threshold is fixed, not the moving estimate of López de Prado's original imbalance bars: a fixed threshold gives the same bars every time the same trades are read, which is what makes a bar table reproducible.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Registered rule name. |
quantity |
Callable
|
Maps the normalized trade frame to the per-trade amount accumulated. |
signed |
bool
|
Whether that amount carries the aggressor's sign. |
units |
tuple of str
|
The trade columns the amount multiplies together, so
:func: |
default_threshold ¶
Return the threshold that cuts frame into about target_bars bars.
An unsigned quantity simply splits its total target_bars ways. A signed one part-cancels, so its total says nothing about how far it drifts: the running sum wanders like a random walk, whose distance after a share of the trades is the root of that share's squared amounts.
assign ¶
Return the bar index of each trade by accumulating up to threshold.