flowbars 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. flowbars/__init__.py +163 -0
  2. flowbars/_version.py +8 -0
  3. flowbars/adapters/__init__.py +29 -0
  4. flowbars/adapters/polars.py +1115 -0
  5. flowbars/bars/__init__.py +3 -0
  6. flowbars/bars/accumulators.py +731 -0
  7. flowbars/bars/constructor.py +1254 -0
  8. flowbars/bars/information/__init__.py +41 -0
  9. flowbars/bars/information/imbalance_dollar_bars.py +200 -0
  10. flowbars/bars/information/imbalance_tick_bars.py +206 -0
  11. flowbars/bars/information/imbalance_volume_bars.py +200 -0
  12. flowbars/bars/information/run_dollar_bars.py +225 -0
  13. flowbars/bars/information/run_tick_bars.py +231 -0
  14. flowbars/bars/information/run_volume_bars.py +225 -0
  15. flowbars/bars/minute/__init__.py +99 -0
  16. flowbars/bars/minute/accumulators.py +303 -0
  17. flowbars/bars/minute/engine.py +244 -0
  18. flowbars/bars/minute/information.py +622 -0
  19. flowbars/bars/minute/numba.py +1043 -0
  20. flowbars/bars/minute/registry.py +22 -0
  21. flowbars/bars/minute/standard.py +261 -0
  22. flowbars/bars/minute/structural.py +474 -0
  23. flowbars/bars/numba_backend.py +1227 -0
  24. flowbars/bars/output.py +106 -0
  25. flowbars/bars/registry.py +47 -0
  26. flowbars/bars/spec.py +367 -0
  27. flowbars/bars/standard/__init__.py +17 -0
  28. flowbars/bars/standard/dollar_bars.py +168 -0
  29. flowbars/bars/standard/tick_bars.py +166 -0
  30. flowbars/bars/standard/time_bars.py +192 -0
  31. flowbars/bars/standard/volume_bars.py +168 -0
  32. flowbars/benchmarks.py +420 -0
  33. flowbars/calendars.py +368 -0
  34. flowbars/core.py +109 -0
  35. flowbars/py.typed +0 -0
  36. flowbars/quality.py +393 -0
  37. flowbars/sample_data/__init__.py +28 -0
  38. flowbars/sample_data/tick_data.csv +501 -0
  39. flowbars/schema.py +476 -0
  40. flowbars/thresholds.py +1167 -0
  41. flowbars/tick_rule.py +89 -0
  42. flowbars-0.1.0.dist-info/METADATA +650 -0
  43. flowbars-0.1.0.dist-info/RECORD +46 -0
  44. flowbars-0.1.0.dist-info/WHEEL +5 -0
  45. flowbars-0.1.0.dist-info/licenses/LICENSE +21 -0
  46. flowbars-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,225 @@
1
+ """Run bars (dollar) — close when the cumulative same-sign dollar-run crosses an adaptive threshold.
2
+
3
+ Same two-component EWMA formula as tick-run bars, but runs are weighted
4
+ by notional value (price × volume) instead of tick count.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Literal
10
+
11
+ import pandas as pd
12
+
13
+ from flowbars.bars.accumulators import RunAccumulator
14
+ from flowbars.bars.constructor import BaseBarConstructor
15
+ from flowbars.bars.registry import BarRegistry
16
+ from flowbars.calendars import TradingCalendar
17
+ from flowbars.core import Bar
18
+ from flowbars.schema import SchemaMapping
19
+ from flowbars.thresholds import EWMAThresholdEstimator
20
+
21
+
22
+ class RunDollarBarConstructor(BaseBarConstructor):
23
+ """Bar constructor for dollar-run bars.
24
+
25
+ A dollar-run bar closes when the cumulative same-sign dollar-run total
26
+ reaches or exceeds an **adaptive** threshold estimated by an EWMA.
27
+
28
+ Parameters
29
+ ----------
30
+ span : float, default 20.0
31
+ EWMA span for the two-component threshold formula.
32
+ halflife : float or None, default None
33
+ EWMA halflife. Takes precedence over *span* when given.
34
+ initial_ewa_t : float, default 1.0
35
+ Initial seed for :math:`E[T]` (expected run dollar value per bar).
36
+ initial_ewa_proportion : float, default 0.5
37
+ Initial seed for :math:`E[P^+]` (expected buy-run proportion).
38
+ warmup_bars : int, default 0
39
+ Number of initial bars to discard while the EWMA converges.
40
+ min_run_length : int, default 0
41
+ Minimum number of ticks a run bar must contain to be returned.
42
+ Bars with fewer ticks are discarded (they still update the EWMA).
43
+ Default 0 — no filtering.
44
+ calendar : TradingCalendar, optional
45
+ schema : SchemaMapping, optional
46
+ watermark : str or None, default ``"timestamp"``
47
+ Dedup key for idempotent resume (``"timestamp"``, a column name, or
48
+ ``None`` to disable).
49
+ strict_ordering : bool, default False
50
+ If True, raise ``TickDataError`` when a tick arrives with a timestamp
51
+ earlier than the previous tick's (out-of-order input).
52
+ backend : str, default ``"python"``
53
+ ``"python"`` or ``"numba"``. The numba backend accelerates
54
+ :meth:`batch` only; ``update`` always uses Python.
55
+ stream_id : str, default ``"default"``
56
+ on_bar : callable or None
57
+ on_threshold_update : callable or None
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ span: float = 20.0,
63
+ halflife: float | None = None,
64
+ initial_ewa_t: float = 1.0,
65
+ initial_ewa_proportion: float = 0.5,
66
+ warmup_bars: int = 0,
67
+ warmup_minutes: int | None = None,
68
+ min_run_length: int = 0,
69
+ calendar: TradingCalendar | None = None,
70
+ schema: SchemaMapping | None = None,
71
+ watermark: str | None = "timestamp",
72
+ stream_id: str = "default",
73
+ strict_ordering: bool = False,
74
+ backend: Literal["python", "numba"] = "python",
75
+ on_bar: Any = None,
76
+ on_threshold_update: Any = None,
77
+ multiplier: float = 1.0,
78
+ volume_type: str = "base",
79
+ ) -> None:
80
+ if min_run_length < 0:
81
+ raise ValueError(f"min_run_length must be non-negative, got {min_run_length}")
82
+ self._min_run_length = min_run_length
83
+
84
+ accumulator = RunAccumulator(bar_type="run_dollar", metric="dollar")
85
+ estimator = EWMAThresholdEstimator(
86
+ bar_family="run",
87
+ span=span,
88
+ halflife=halflife,
89
+ initial_ewa_t=initial_ewa_t,
90
+ initial_ewa_proportion=initial_ewa_proportion,
91
+ )
92
+
93
+ super().__init__(
94
+ accumulator=accumulator,
95
+ threshold_estimator=estimator,
96
+ calendar=calendar,
97
+ schema=schema,
98
+ watermark=watermark,
99
+ stream_id=stream_id,
100
+ strict_ordering=strict_ordering,
101
+ warmup_bars=warmup_bars,
102
+ warmup_minutes=warmup_minutes,
103
+ backend=backend,
104
+ on_bar=on_bar,
105
+ on_threshold_update=on_threshold_update,
106
+ multiplier=multiplier,
107
+ volume_type=volume_type,
108
+ )
109
+
110
+ def _should_return(self, bar: Bar) -> bool:
111
+ """Filter bars below ``min_run_length`` ticks."""
112
+ return bar.num_ticks >= self._min_run_length
113
+
114
+ def _extra_params(self) -> dict[str, Any]:
115
+ return {"min_run_length": self._min_run_length}
116
+
117
+ def _extra_state(self) -> dict[str, Any]:
118
+ return {"min_run_length": self._min_run_length}
119
+
120
+ def _load_extra_state(self, state: dict[str, Any]) -> None:
121
+ self._min_run_length = int(state.get("min_run_length", 0))
122
+
123
+ @classmethod
124
+ def from_state( # type: ignore[override]
125
+ cls,
126
+ state: dict[str, Any],
127
+ calendar: TradingCalendar | None = None,
128
+ schema: SchemaMapping | None = None,
129
+ *,
130
+ verify_identity: bool = True,
131
+ ) -> RunDollarBarConstructor:
132
+ """Reconstruct a ``RunDollarBarConstructor`` from a saved state dict."""
133
+ acc_state = state["accumulator"]
134
+ acc = RunAccumulator(
135
+ bar_type=state["bar_type"],
136
+ metric=acc_state["metric"],
137
+ )
138
+ est = EWMAThresholdEstimator.from_state(state["threshold_estimator"])
139
+ inst: RunDollarBarConstructor = super().from_state(
140
+ state, acc, est, calendar, schema, verify_identity=verify_identity
141
+ ) # type: ignore[assignment]
142
+ return inst
143
+
144
+
145
+ def compute_run_dollar_bars(
146
+ ticks_df: pd.DataFrame,
147
+ span: float = 20.0,
148
+ halflife: float | None = None,
149
+ initial_ewa_t: float = 1.0,
150
+ initial_ewa_proportion: float = 0.5,
151
+ warmup_bars: int = 0,
152
+ warmup_minutes: int | None = None,
153
+ min_run_length: int = 0,
154
+ schema: SchemaMapping | None = None,
155
+ watermark: str | None = "timestamp",
156
+ strict_ordering: bool = False,
157
+ calendar: TradingCalendar | None = None,
158
+ *,
159
+ backend: Literal["python", "numba"] = "python",
160
+ stream_id: str = "default",
161
+ on_bar: Any = None,
162
+ on_threshold_update: Any = None,
163
+ multiplier: float = 1.0,
164
+ volume_type: str = "base",
165
+ ) -> pd.DataFrame:
166
+ """Build dollar-run bars from a DataFrame of ticks.
167
+
168
+ Parameters
169
+ ----------
170
+ ticks_df : pd.DataFrame
171
+ Tick-level data with user-mapped columns.
172
+ span : float, default 20.0
173
+ EWMA span for the adaptive threshold.
174
+ halflife : float or None, default None
175
+ EWMA halflife (takes precedence over *span*).
176
+ initial_ewa_t : float, default 1.0
177
+ Initial seed for expected run dollar value per bar.
178
+ initial_ewa_proportion : float, default 0.5
179
+ Initial seed for expected buy-run proportion.
180
+ warmup_bars : int, default 0
181
+ Number of initial bars to discard.
182
+ min_run_length : int, default 0
183
+ Minimum ticks per bar to return.
184
+ schema : SchemaMapping, optional
185
+ watermark : str or None, default ``"timestamp"``
186
+ Dedup key for idempotent resume (``"timestamp"``, a column name, or
187
+ ``None`` to disable).
188
+ strict_ordering : bool, default False
189
+ If True, raise ``TickDataError`` when a tick arrives with a timestamp
190
+ earlier than the previous tick's (out-of-order input).
191
+ calendar : TradingCalendar, optional
192
+ backend : str, default ``"python"``
193
+ ``"python"`` or ``"numba"``.
194
+ stream_id : str, default ``"default"``
195
+ on_bar : callable or None
196
+ on_threshold_update : callable or None
197
+
198
+ Returns
199
+ -------
200
+ pd.DataFrame
201
+ Completed bars as a DataFrame.
202
+ """
203
+ ctor = RunDollarBarConstructor(
204
+ span=span,
205
+ halflife=halflife,
206
+ initial_ewa_t=initial_ewa_t,
207
+ initial_ewa_proportion=initial_ewa_proportion,
208
+ warmup_bars=warmup_bars,
209
+ warmup_minutes=warmup_minutes,
210
+ min_run_length=min_run_length,
211
+ calendar=calendar,
212
+ schema=schema,
213
+ watermark=watermark,
214
+ strict_ordering=strict_ordering,
215
+ backend=backend,
216
+ stream_id=stream_id,
217
+ on_bar=on_bar,
218
+ on_threshold_update=on_threshold_update,
219
+ multiplier=multiplier,
220
+ volume_type=volume_type,
221
+ )
222
+ return ctor.batch(ticks_df)
223
+
224
+
225
+ BarRegistry.register("run_dollar", RunDollarBarConstructor, batch_fn=compute_run_dollar_bars)
@@ -0,0 +1,231 @@
1
+ """Run bars (tick) — close when the cumulative same-sign tick-run crosses an adaptive threshold.
2
+
3
+ Uses the two-component EWMA formula from AFML §2.4:
4
+
5
+ .. math::
6
+
7
+ T_n = E[T]_n \\times \\max(E[P^+]_n,\\, 1 - E[P^+]_n)
8
+
9
+ where :math:`E[T]_n` is the expected run length per bar and
10
+ :math:`E[P^+]_n` is the expected buy-run proportion.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Literal
16
+
17
+ import pandas as pd
18
+
19
+ from flowbars.bars.accumulators import RunAccumulator
20
+ from flowbars.bars.constructor import BaseBarConstructor
21
+ from flowbars.bars.registry import BarRegistry
22
+ from flowbars.calendars import TradingCalendar
23
+ from flowbars.core import Bar
24
+ from flowbars.schema import SchemaMapping
25
+ from flowbars.thresholds import EWMAThresholdEstimator
26
+
27
+
28
+ class RunTickBarConstructor(BaseBarConstructor):
29
+ """Bar constructor for tick-run bars.
30
+
31
+ A tick-run bar closes when the cumulative same-sign tick-run total
32
+ reaches or exceeds an **adaptive** threshold estimated by an EWMA.
33
+
34
+ Parameters
35
+ ----------
36
+ span : float, default 20.0
37
+ EWMA span for the two-component threshold formula.
38
+ halflife : float or None, default None
39
+ EWMA halflife. Takes precedence over *span* when given.
40
+ initial_ewa_t : float, default 1.0
41
+ Initial seed for :math:`E[T]` (expected run length per bar).
42
+ initial_ewa_proportion : float, default 0.5
43
+ Initial seed for :math:`E[P^+]` (expected buy-run proportion).
44
+ warmup_bars : int, default 0
45
+ Number of initial bars to discard while the EWMA converges.
46
+ min_run_length : int, default 0
47
+ Minimum number of ticks a run bar must contain to be returned.
48
+ Bars with fewer ticks are discarded (they still update the EWMA).
49
+ Default 0 — no filtering.
50
+ calendar : TradingCalendar, optional
51
+ schema : SchemaMapping, optional
52
+ watermark : str or None, default ``"timestamp"``
53
+ Dedup key for idempotent resume (``"timestamp"``, a column name, or
54
+ ``None`` to disable).
55
+ strict_ordering : bool, default False
56
+ If True, raise ``TickDataError`` when a tick arrives with a timestamp
57
+ earlier than the previous tick's (out-of-order input).
58
+ backend : str, default ``"python"``
59
+ ``"python"`` or ``"numba"``. The numba backend accelerates
60
+ :meth:`batch` only; ``update`` always uses Python.
61
+ stream_id : str, default ``"default"``
62
+ on_bar : callable or None
63
+ on_threshold_update : callable or None
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ span: float = 20.0,
69
+ halflife: float | None = None,
70
+ initial_ewa_t: float = 1.0,
71
+ initial_ewa_proportion: float = 0.5,
72
+ warmup_bars: int = 0,
73
+ warmup_minutes: int | None = None,
74
+ min_run_length: int = 0,
75
+ calendar: TradingCalendar | None = None,
76
+ schema: SchemaMapping | None = None,
77
+ watermark: str | None = "timestamp",
78
+ stream_id: str = "default",
79
+ strict_ordering: bool = False,
80
+ backend: Literal["python", "numba"] = "python",
81
+ on_bar: Any = None,
82
+ on_threshold_update: Any = None,
83
+ multiplier: float = 1.0,
84
+ volume_type: str = "base",
85
+ ) -> None:
86
+ if min_run_length < 0:
87
+ raise ValueError(f"min_run_length must be non-negative, got {min_run_length}")
88
+ self._min_run_length = min_run_length
89
+
90
+ accumulator = RunAccumulator(bar_type="run_tick", metric="tick")
91
+ estimator = EWMAThresholdEstimator(
92
+ bar_family="run",
93
+ span=span,
94
+ halflife=halflife,
95
+ initial_ewa_t=initial_ewa_t,
96
+ initial_ewa_proportion=initial_ewa_proportion,
97
+ )
98
+
99
+ super().__init__(
100
+ accumulator=accumulator,
101
+ threshold_estimator=estimator,
102
+ calendar=calendar,
103
+ schema=schema,
104
+ watermark=watermark,
105
+ stream_id=stream_id,
106
+ strict_ordering=strict_ordering,
107
+ warmup_bars=warmup_bars,
108
+ warmup_minutes=warmup_minutes,
109
+ backend=backend,
110
+ on_bar=on_bar,
111
+ on_threshold_update=on_threshold_update,
112
+ multiplier=multiplier,
113
+ volume_type=volume_type,
114
+ )
115
+
116
+ def _should_return(self, bar: Bar) -> bool:
117
+ """Filter bars below ``min_run_length`` ticks."""
118
+ return bar.num_ticks >= self._min_run_length
119
+
120
+ def _extra_params(self) -> dict[str, Any]:
121
+ return {"min_run_length": self._min_run_length}
122
+
123
+ def _extra_state(self) -> dict[str, Any]:
124
+ return {"min_run_length": self._min_run_length}
125
+
126
+ def _load_extra_state(self, state: dict[str, Any]) -> None:
127
+ self._min_run_length = int(state.get("min_run_length", 0))
128
+
129
+ @classmethod
130
+ def from_state( # type: ignore[override]
131
+ cls,
132
+ state: dict[str, Any],
133
+ calendar: TradingCalendar | None = None,
134
+ schema: SchemaMapping | None = None,
135
+ *,
136
+ verify_identity: bool = True,
137
+ ) -> RunTickBarConstructor:
138
+ """Reconstruct a ``RunTickBarConstructor`` from a saved state dict."""
139
+ acc_state = state["accumulator"]
140
+ acc = RunAccumulator(
141
+ bar_type=state["bar_type"],
142
+ metric=acc_state["metric"],
143
+ )
144
+ est = EWMAThresholdEstimator.from_state(state["threshold_estimator"])
145
+ inst: RunTickBarConstructor = super().from_state(
146
+ state, acc, est, calendar, schema, verify_identity=verify_identity
147
+ ) # type: ignore[assignment]
148
+ return inst
149
+
150
+
151
+ def compute_run_tick_bars(
152
+ ticks_df: pd.DataFrame,
153
+ span: float = 20.0,
154
+ halflife: float | None = None,
155
+ initial_ewa_t: float = 1.0,
156
+ initial_ewa_proportion: float = 0.5,
157
+ warmup_bars: int = 0,
158
+ warmup_minutes: int | None = None,
159
+ min_run_length: int = 0,
160
+ schema: SchemaMapping | None = None,
161
+ watermark: str | None = "timestamp",
162
+ strict_ordering: bool = False,
163
+ calendar: TradingCalendar | None = None,
164
+ *,
165
+ backend: Literal["python", "numba"] = "python",
166
+ stream_id: str = "default",
167
+ on_bar: Any = None,
168
+ on_threshold_update: Any = None,
169
+ multiplier: float = 1.0,
170
+ volume_type: str = "base",
171
+ ) -> pd.DataFrame:
172
+ """Build tick-run bars from a DataFrame of ticks.
173
+
174
+ Parameters
175
+ ----------
176
+ ticks_df : pd.DataFrame
177
+ Tick-level data with user-mapped columns.
178
+ span : float, default 20.0
179
+ EWMA span for the adaptive threshold.
180
+ halflife : float or None, default None
181
+ EWMA halflife (takes precedence over *span*).
182
+ initial_ewa_t : float, default 1.0
183
+ Initial seed for expected run length per bar.
184
+ initial_ewa_proportion : float, default 0.5
185
+ Initial seed for expected buy-run proportion.
186
+ warmup_bars : int, default 0
187
+ Number of initial bars to discard.
188
+ min_run_length : int, default 0
189
+ Minimum ticks per bar to return.
190
+ schema : SchemaMapping, optional
191
+ watermark : str or None, default ``"timestamp"``
192
+ Dedup key for idempotent resume (``"timestamp"``, a column name, or
193
+ ``None`` to disable).
194
+ strict_ordering : bool, default False
195
+ If True, raise ``TickDataError`` when a tick arrives with a timestamp
196
+ earlier than the previous tick's (out-of-order input).
197
+ calendar : TradingCalendar, optional
198
+ backend : str, default ``"python"``
199
+ ``"python"`` or ``"numba"``.
200
+ stream_id : str, default ``"default"``
201
+ on_bar : callable or None
202
+ on_threshold_update : callable or None
203
+
204
+ Returns
205
+ -------
206
+ pd.DataFrame
207
+ Completed bars as a DataFrame.
208
+ """
209
+ ctor = RunTickBarConstructor(
210
+ span=span,
211
+ halflife=halflife,
212
+ initial_ewa_t=initial_ewa_t,
213
+ initial_ewa_proportion=initial_ewa_proportion,
214
+ warmup_bars=warmup_bars,
215
+ warmup_minutes=warmup_minutes,
216
+ min_run_length=min_run_length,
217
+ calendar=calendar,
218
+ schema=schema,
219
+ watermark=watermark,
220
+ strict_ordering=strict_ordering,
221
+ backend=backend,
222
+ stream_id=stream_id,
223
+ on_bar=on_bar,
224
+ on_threshold_update=on_threshold_update,
225
+ multiplier=multiplier,
226
+ volume_type=volume_type,
227
+ )
228
+ return ctor.batch(ticks_df)
229
+
230
+
231
+ BarRegistry.register("run_tick", RunTickBarConstructor, batch_fn=compute_run_tick_bars)
@@ -0,0 +1,225 @@
1
+ """Run bars (volume) — close when the cumulative same-sign volume-run crosses an adaptive threshold.
2
+
3
+ Same two-component EWMA formula as tick-run bars, but runs are weighted
4
+ by trade volume instead of tick count.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Literal
10
+
11
+ import pandas as pd
12
+
13
+ from flowbars.bars.accumulators import RunAccumulator
14
+ from flowbars.bars.constructor import BaseBarConstructor
15
+ from flowbars.bars.registry import BarRegistry
16
+ from flowbars.calendars import TradingCalendar
17
+ from flowbars.core import Bar
18
+ from flowbars.schema import SchemaMapping
19
+ from flowbars.thresholds import EWMAThresholdEstimator
20
+
21
+
22
+ class RunVolumeBarConstructor(BaseBarConstructor):
23
+ """Bar constructor for volume-run bars.
24
+
25
+ A volume-run bar closes when the cumulative same-sign volume-run total
26
+ reaches or exceeds an **adaptive** threshold estimated by an EWMA.
27
+
28
+ Parameters
29
+ ----------
30
+ span : float, default 20.0
31
+ EWMA span for the two-component threshold formula.
32
+ halflife : float or None, default None
33
+ EWMA halflife. Takes precedence over *span* when given.
34
+ initial_ewa_t : float, default 1.0
35
+ Initial seed for :math:`E[T]` (expected run volume per bar).
36
+ initial_ewa_proportion : float, default 0.5
37
+ Initial seed for :math:`E[P^+]` (expected buy-run proportion).
38
+ warmup_bars : int, default 0
39
+ Number of initial bars to discard while the EWMA converges.
40
+ min_run_length : int, default 0
41
+ Minimum number of ticks a run bar must contain to be returned.
42
+ Bars with fewer ticks are discarded (they still update the EWMA).
43
+ Default 0 — no filtering.
44
+ calendar : TradingCalendar, optional
45
+ schema : SchemaMapping, optional
46
+ watermark : str or None, default ``"timestamp"``
47
+ Dedup key for idempotent resume (``"timestamp"``, a column name, or
48
+ ``None`` to disable).
49
+ strict_ordering : bool, default False
50
+ If True, raise ``TickDataError`` when a tick arrives with a timestamp
51
+ earlier than the previous tick's (out-of-order input).
52
+ backend : str, default ``"python"``
53
+ ``"python"`` or ``"numba"``. The numba backend accelerates
54
+ :meth:`batch` only; ``update`` always uses Python.
55
+ stream_id : str, default ``"default"``
56
+ on_bar : callable or None
57
+ on_threshold_update : callable or None
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ span: float = 20.0,
63
+ halflife: float | None = None,
64
+ initial_ewa_t: float = 1.0,
65
+ initial_ewa_proportion: float = 0.5,
66
+ warmup_bars: int = 0,
67
+ warmup_minutes: int | None = None,
68
+ min_run_length: int = 0,
69
+ calendar: TradingCalendar | None = None,
70
+ schema: SchemaMapping | None = None,
71
+ watermark: str | None = "timestamp",
72
+ stream_id: str = "default",
73
+ strict_ordering: bool = False,
74
+ backend: Literal["python", "numba"] = "python",
75
+ on_bar: Any = None,
76
+ on_threshold_update: Any = None,
77
+ multiplier: float = 1.0,
78
+ volume_type: str = "base",
79
+ ) -> None:
80
+ if min_run_length < 0:
81
+ raise ValueError(f"min_run_length must be non-negative, got {min_run_length}")
82
+ self._min_run_length = min_run_length
83
+
84
+ accumulator = RunAccumulator(bar_type="run_volume", metric="volume")
85
+ estimator = EWMAThresholdEstimator(
86
+ bar_family="run",
87
+ span=span,
88
+ halflife=halflife,
89
+ initial_ewa_t=initial_ewa_t,
90
+ initial_ewa_proportion=initial_ewa_proportion,
91
+ )
92
+
93
+ super().__init__(
94
+ accumulator=accumulator,
95
+ threshold_estimator=estimator,
96
+ calendar=calendar,
97
+ schema=schema,
98
+ watermark=watermark,
99
+ stream_id=stream_id,
100
+ strict_ordering=strict_ordering,
101
+ warmup_bars=warmup_bars,
102
+ warmup_minutes=warmup_minutes,
103
+ backend=backend,
104
+ on_bar=on_bar,
105
+ on_threshold_update=on_threshold_update,
106
+ multiplier=multiplier,
107
+ volume_type=volume_type,
108
+ )
109
+
110
+ def _should_return(self, bar: Bar) -> bool:
111
+ """Filter bars below ``min_run_length`` ticks."""
112
+ return bar.num_ticks >= self._min_run_length
113
+
114
+ def _extra_params(self) -> dict[str, Any]:
115
+ return {"min_run_length": self._min_run_length}
116
+
117
+ def _extra_state(self) -> dict[str, Any]:
118
+ return {"min_run_length": self._min_run_length}
119
+
120
+ def _load_extra_state(self, state: dict[str, Any]) -> None:
121
+ self._min_run_length = int(state.get("min_run_length", 0))
122
+
123
+ @classmethod
124
+ def from_state( # type: ignore[override]
125
+ cls,
126
+ state: dict[str, Any],
127
+ calendar: TradingCalendar | None = None,
128
+ schema: SchemaMapping | None = None,
129
+ *,
130
+ verify_identity: bool = True,
131
+ ) -> RunVolumeBarConstructor:
132
+ """Reconstruct a ``RunVolumeBarConstructor`` from a saved state dict."""
133
+ acc_state = state["accumulator"]
134
+ acc = RunAccumulator(
135
+ bar_type=state["bar_type"],
136
+ metric=acc_state["metric"],
137
+ )
138
+ est = EWMAThresholdEstimator.from_state(state["threshold_estimator"])
139
+ inst: RunVolumeBarConstructor = super().from_state(
140
+ state, acc, est, calendar, schema, verify_identity=verify_identity
141
+ ) # type: ignore[assignment]
142
+ return inst
143
+
144
+
145
+ def compute_run_volume_bars(
146
+ ticks_df: pd.DataFrame,
147
+ span: float = 20.0,
148
+ halflife: float | None = None,
149
+ initial_ewa_t: float = 1.0,
150
+ initial_ewa_proportion: float = 0.5,
151
+ warmup_bars: int = 0,
152
+ warmup_minutes: int | None = None,
153
+ min_run_length: int = 0,
154
+ schema: SchemaMapping | None = None,
155
+ watermark: str | None = "timestamp",
156
+ strict_ordering: bool = False,
157
+ calendar: TradingCalendar | None = None,
158
+ *,
159
+ backend: Literal["python", "numba"] = "python",
160
+ stream_id: str = "default",
161
+ on_bar: Any = None,
162
+ on_threshold_update: Any = None,
163
+ multiplier: float = 1.0,
164
+ volume_type: str = "base",
165
+ ) -> pd.DataFrame:
166
+ """Build volume-run bars from a DataFrame of ticks.
167
+
168
+ Parameters
169
+ ----------
170
+ ticks_df : pd.DataFrame
171
+ Tick-level data with user-mapped columns.
172
+ span : float, default 20.0
173
+ EWMA span for the adaptive threshold.
174
+ halflife : float or None, default None
175
+ EWMA halflife (takes precedence over *span*).
176
+ initial_ewa_t : float, default 1.0
177
+ Initial seed for expected run volume per bar.
178
+ initial_ewa_proportion : float, default 0.5
179
+ Initial seed for expected buy-run proportion.
180
+ warmup_bars : int, default 0
181
+ Number of initial bars to discard.
182
+ min_run_length : int, default 0
183
+ Minimum ticks per bar to return.
184
+ schema : SchemaMapping, optional
185
+ watermark : str or None, default ``"timestamp"``
186
+ Dedup key for idempotent resume (``"timestamp"``, a column name, or
187
+ ``None`` to disable).
188
+ strict_ordering : bool, default False
189
+ If True, raise ``TickDataError`` when a tick arrives with a timestamp
190
+ earlier than the previous tick's (out-of-order input).
191
+ calendar : TradingCalendar, optional
192
+ backend : str, default ``"python"``
193
+ ``"python"`` or ``"numba"``.
194
+ stream_id : str, default ``"default"``
195
+ on_bar : callable or None
196
+ on_threshold_update : callable or None
197
+
198
+ Returns
199
+ -------
200
+ pd.DataFrame
201
+ Completed bars as a DataFrame.
202
+ """
203
+ ctor = RunVolumeBarConstructor(
204
+ span=span,
205
+ halflife=halflife,
206
+ initial_ewa_t=initial_ewa_t,
207
+ initial_ewa_proportion=initial_ewa_proportion,
208
+ warmup_bars=warmup_bars,
209
+ warmup_minutes=warmup_minutes,
210
+ min_run_length=min_run_length,
211
+ calendar=calendar,
212
+ schema=schema,
213
+ watermark=watermark,
214
+ strict_ordering=strict_ordering,
215
+ backend=backend,
216
+ stream_id=stream_id,
217
+ on_bar=on_bar,
218
+ on_threshold_update=on_threshold_update,
219
+ multiplier=multiplier,
220
+ volume_type=volume_type,
221
+ )
222
+ return ctor.batch(ticks_df)
223
+
224
+
225
+ BarRegistry.register("run_volume", RunVolumeBarConstructor, batch_fn=compute_run_volume_bars)