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.
- flowbars/__init__.py +163 -0
- flowbars/_version.py +8 -0
- flowbars/adapters/__init__.py +29 -0
- flowbars/adapters/polars.py +1115 -0
- flowbars/bars/__init__.py +3 -0
- flowbars/bars/accumulators.py +731 -0
- flowbars/bars/constructor.py +1254 -0
- flowbars/bars/information/__init__.py +41 -0
- flowbars/bars/information/imbalance_dollar_bars.py +200 -0
- flowbars/bars/information/imbalance_tick_bars.py +206 -0
- flowbars/bars/information/imbalance_volume_bars.py +200 -0
- flowbars/bars/information/run_dollar_bars.py +225 -0
- flowbars/bars/information/run_tick_bars.py +231 -0
- flowbars/bars/information/run_volume_bars.py +225 -0
- flowbars/bars/minute/__init__.py +99 -0
- flowbars/bars/minute/accumulators.py +303 -0
- flowbars/bars/minute/engine.py +244 -0
- flowbars/bars/minute/information.py +622 -0
- flowbars/bars/minute/numba.py +1043 -0
- flowbars/bars/minute/registry.py +22 -0
- flowbars/bars/minute/standard.py +261 -0
- flowbars/bars/minute/structural.py +474 -0
- flowbars/bars/numba_backend.py +1227 -0
- flowbars/bars/output.py +106 -0
- flowbars/bars/registry.py +47 -0
- flowbars/bars/spec.py +367 -0
- flowbars/bars/standard/__init__.py +17 -0
- flowbars/bars/standard/dollar_bars.py +168 -0
- flowbars/bars/standard/tick_bars.py +166 -0
- flowbars/bars/standard/time_bars.py +192 -0
- flowbars/bars/standard/volume_bars.py +168 -0
- flowbars/benchmarks.py +420 -0
- flowbars/calendars.py +368 -0
- flowbars/core.py +109 -0
- flowbars/py.typed +0 -0
- flowbars/quality.py +393 -0
- flowbars/sample_data/__init__.py +28 -0
- flowbars/sample_data/tick_data.csv +501 -0
- flowbars/schema.py +476 -0
- flowbars/thresholds.py +1167 -0
- flowbars/tick_rule.py +89 -0
- flowbars-0.1.0.dist-info/METADATA +650 -0
- flowbars-0.1.0.dist-info/RECORD +46 -0
- flowbars-0.1.0.dist-info/WHEEL +5 -0
- flowbars-0.1.0.dist-info/licenses/LICENSE +21 -0
- flowbars-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Information-driven bar types: imbalance and run (tick, volume, dollar variants)."""
|
|
2
|
+
|
|
3
|
+
from flowbars.bars.information.imbalance_dollar_bars import (
|
|
4
|
+
ImbalanceDollarBarConstructor,
|
|
5
|
+
compute_imbalance_dollar_bars,
|
|
6
|
+
)
|
|
7
|
+
from flowbars.bars.information.imbalance_tick_bars import (
|
|
8
|
+
ImbalanceTickBarConstructor,
|
|
9
|
+
compute_imbalance_tick_bars,
|
|
10
|
+
)
|
|
11
|
+
from flowbars.bars.information.imbalance_volume_bars import (
|
|
12
|
+
ImbalanceVolumeBarConstructor,
|
|
13
|
+
compute_imbalance_volume_bars,
|
|
14
|
+
)
|
|
15
|
+
from flowbars.bars.information.run_dollar_bars import (
|
|
16
|
+
RunDollarBarConstructor,
|
|
17
|
+
compute_run_dollar_bars,
|
|
18
|
+
)
|
|
19
|
+
from flowbars.bars.information.run_tick_bars import (
|
|
20
|
+
RunTickBarConstructor,
|
|
21
|
+
compute_run_tick_bars,
|
|
22
|
+
)
|
|
23
|
+
from flowbars.bars.information.run_volume_bars import (
|
|
24
|
+
RunVolumeBarConstructor,
|
|
25
|
+
compute_run_volume_bars,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"ImbalanceDollarBarConstructor",
|
|
30
|
+
"ImbalanceTickBarConstructor",
|
|
31
|
+
"ImbalanceVolumeBarConstructor",
|
|
32
|
+
"RunDollarBarConstructor",
|
|
33
|
+
"RunTickBarConstructor",
|
|
34
|
+
"RunVolumeBarConstructor",
|
|
35
|
+
"compute_imbalance_dollar_bars",
|
|
36
|
+
"compute_imbalance_tick_bars",
|
|
37
|
+
"compute_imbalance_volume_bars",
|
|
38
|
+
"compute_run_dollar_bars",
|
|
39
|
+
"compute_run_tick_bars",
|
|
40
|
+
"compute_run_volume_bars",
|
|
41
|
+
]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Imbalance bars (dollar) — close when absolute signed dollar-imbalance crosses an adaptive threshold.
|
|
2
|
+
|
|
3
|
+
Same two-component EWMA formula as tick-imbalance bars, but the imbalance
|
|
4
|
+
is weighted 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 ImbalanceAccumulator
|
|
14
|
+
from flowbars.bars.constructor import BaseBarConstructor
|
|
15
|
+
from flowbars.bars.registry import BarRegistry
|
|
16
|
+
from flowbars.calendars import TradingCalendar
|
|
17
|
+
from flowbars.schema import SchemaMapping
|
|
18
|
+
from flowbars.thresholds import EWMAThresholdEstimator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ImbalanceDollarBarConstructor(BaseBarConstructor):
|
|
22
|
+
"""Bar constructor for dollar-imbalance bars.
|
|
23
|
+
|
|
24
|
+
A dollar-imbalance bar closes when the absolute signed dollar-imbalance
|
|
25
|
+
reaches or exceeds an **adaptive** threshold estimated by an EWMA.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
span : float, default 20.0
|
|
30
|
+
EWMA span for the two-component threshold formula.
|
|
31
|
+
halflife : float or None, default None
|
|
32
|
+
EWMA halflife. Takes precedence over *span* when given.
|
|
33
|
+
initial_ewa_t : float, default 1.0
|
|
34
|
+
Initial seed for :math:`E[T]` (expected dollar value per bar).
|
|
35
|
+
initial_ewa_proportion : float, default 0.5
|
|
36
|
+
Initial seed for :math:`E[\\theta]` (expected imbalance proportion).
|
|
37
|
+
warmup_bars : int, default 0
|
|
38
|
+
Number of initial bars to discard while the EWMA converges.
|
|
39
|
+
calendar : TradingCalendar, optional
|
|
40
|
+
schema : SchemaMapping, optional
|
|
41
|
+
watermark : str or None, default ``"timestamp"``
|
|
42
|
+
Dedup key for idempotent resume (``"timestamp"``, a column name, or
|
|
43
|
+
``None`` to disable).
|
|
44
|
+
strict_ordering : bool, default False
|
|
45
|
+
If True, raise ``TickDataError`` when a tick arrives with a timestamp
|
|
46
|
+
earlier than the previous tick's (out-of-order input).
|
|
47
|
+
backend : str, default ``"python"``
|
|
48
|
+
``"python"`` or ``"numba"``. The numba backend accelerates
|
|
49
|
+
:meth:`batch` only; ``update`` always uses Python.
|
|
50
|
+
stream_id : str, default ``"default"``
|
|
51
|
+
on_bar : callable or None
|
|
52
|
+
on_threshold_update : callable or None
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
span: float = 20.0,
|
|
58
|
+
halflife: float | None = None,
|
|
59
|
+
initial_ewa_t: float = 1.0,
|
|
60
|
+
initial_ewa_proportion: float = 0.5,
|
|
61
|
+
warmup_bars: int = 0,
|
|
62
|
+
warmup_minutes: int | None = None,
|
|
63
|
+
calendar: TradingCalendar | None = None,
|
|
64
|
+
schema: SchemaMapping | None = None,
|
|
65
|
+
watermark: str | None = "timestamp",
|
|
66
|
+
stream_id: str = "default",
|
|
67
|
+
strict_ordering: bool = False,
|
|
68
|
+
backend: Literal["python", "numba"] = "python",
|
|
69
|
+
on_bar: Any = None,
|
|
70
|
+
on_threshold_update: Any = None,
|
|
71
|
+
multiplier: float = 1.0,
|
|
72
|
+
volume_type: str = "base",
|
|
73
|
+
) -> None:
|
|
74
|
+
accumulator = ImbalanceAccumulator(bar_type="imbalance_dollar", metric="dollar")
|
|
75
|
+
estimator = EWMAThresholdEstimator(
|
|
76
|
+
bar_family="imbalance",
|
|
77
|
+
span=span,
|
|
78
|
+
halflife=halflife,
|
|
79
|
+
initial_ewa_t=initial_ewa_t,
|
|
80
|
+
initial_ewa_proportion=initial_ewa_proportion,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
super().__init__(
|
|
84
|
+
accumulator=accumulator,
|
|
85
|
+
threshold_estimator=estimator,
|
|
86
|
+
calendar=calendar,
|
|
87
|
+
schema=schema,
|
|
88
|
+
watermark=watermark,
|
|
89
|
+
stream_id=stream_id,
|
|
90
|
+
strict_ordering=strict_ordering,
|
|
91
|
+
warmup_bars=warmup_bars,
|
|
92
|
+
warmup_minutes=warmup_minutes,
|
|
93
|
+
backend=backend,
|
|
94
|
+
on_bar=on_bar,
|
|
95
|
+
on_threshold_update=on_threshold_update,
|
|
96
|
+
multiplier=multiplier,
|
|
97
|
+
volume_type=volume_type,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_state( # type: ignore[override]
|
|
102
|
+
cls,
|
|
103
|
+
state: dict[str, Any],
|
|
104
|
+
calendar: TradingCalendar | None = None,
|
|
105
|
+
schema: SchemaMapping | None = None,
|
|
106
|
+
*,
|
|
107
|
+
verify_identity: bool = True,
|
|
108
|
+
) -> ImbalanceDollarBarConstructor:
|
|
109
|
+
"""Reconstruct an ``ImbalanceDollarBarConstructor`` from a saved state dict."""
|
|
110
|
+
acc_state = state["accumulator"]
|
|
111
|
+
acc = ImbalanceAccumulator(
|
|
112
|
+
bar_type=state["bar_type"],
|
|
113
|
+
metric=acc_state["metric"],
|
|
114
|
+
)
|
|
115
|
+
est = EWMAThresholdEstimator.from_state(state["threshold_estimator"])
|
|
116
|
+
inst: ImbalanceDollarBarConstructor = super().from_state(
|
|
117
|
+
state, acc, est, calendar, schema, verify_identity=verify_identity
|
|
118
|
+
) # type: ignore[assignment]
|
|
119
|
+
return inst
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def compute_imbalance_dollar_bars(
|
|
123
|
+
ticks_df: pd.DataFrame,
|
|
124
|
+
span: float = 20.0,
|
|
125
|
+
halflife: float | None = None,
|
|
126
|
+
initial_ewa_t: float = 1.0,
|
|
127
|
+
initial_ewa_proportion: float = 0.5,
|
|
128
|
+
warmup_bars: int = 0,
|
|
129
|
+
warmup_minutes: int | None = None,
|
|
130
|
+
schema: SchemaMapping | None = None,
|
|
131
|
+
watermark: str | None = "timestamp",
|
|
132
|
+
strict_ordering: bool = False,
|
|
133
|
+
calendar: TradingCalendar | None = None,
|
|
134
|
+
*,
|
|
135
|
+
backend: Literal["python", "numba"] = "python",
|
|
136
|
+
stream_id: str = "default",
|
|
137
|
+
on_bar: Any = None,
|
|
138
|
+
on_threshold_update: Any = None,
|
|
139
|
+
multiplier: float = 1.0,
|
|
140
|
+
volume_type: str = "base",
|
|
141
|
+
) -> pd.DataFrame:
|
|
142
|
+
"""Build dollar-imbalance bars from a DataFrame of ticks.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
ticks_df : pd.DataFrame
|
|
147
|
+
Tick-level data with user-mapped columns.
|
|
148
|
+
span : float, default 20.0
|
|
149
|
+
EWMA span for the adaptive threshold.
|
|
150
|
+
halflife : float or None, default None
|
|
151
|
+
EWMA halflife (takes precedence over *span*).
|
|
152
|
+
initial_ewa_t : float, default 1.0
|
|
153
|
+
Initial seed for expected dollar value per bar.
|
|
154
|
+
initial_ewa_proportion : float, default 0.5
|
|
155
|
+
Initial seed for expected imbalance proportion.
|
|
156
|
+
warmup_bars : int, default 0
|
|
157
|
+
Number of initial bars to discard.
|
|
158
|
+
schema : SchemaMapping, optional
|
|
159
|
+
watermark : str or None, default ``"timestamp"``
|
|
160
|
+
Dedup key for idempotent resume (``"timestamp"``, a column name, or
|
|
161
|
+
``None`` to disable).
|
|
162
|
+
strict_ordering : bool, default False
|
|
163
|
+
If True, raise ``TickDataError`` when a tick arrives with a timestamp
|
|
164
|
+
earlier than the previous tick's (out-of-order input).
|
|
165
|
+
calendar : TradingCalendar, optional
|
|
166
|
+
backend : str, default ``"python"``
|
|
167
|
+
``"python"`` or ``"numba"``.
|
|
168
|
+
stream_id : str, default ``"default"``
|
|
169
|
+
on_bar : callable or None
|
|
170
|
+
on_threshold_update : callable or None
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
pd.DataFrame
|
|
175
|
+
Completed bars as a DataFrame.
|
|
176
|
+
"""
|
|
177
|
+
ctor = ImbalanceDollarBarConstructor(
|
|
178
|
+
span=span,
|
|
179
|
+
halflife=halflife,
|
|
180
|
+
initial_ewa_t=initial_ewa_t,
|
|
181
|
+
initial_ewa_proportion=initial_ewa_proportion,
|
|
182
|
+
warmup_bars=warmup_bars,
|
|
183
|
+
warmup_minutes=warmup_minutes,
|
|
184
|
+
calendar=calendar,
|
|
185
|
+
schema=schema,
|
|
186
|
+
watermark=watermark,
|
|
187
|
+
strict_ordering=strict_ordering,
|
|
188
|
+
backend=backend,
|
|
189
|
+
stream_id=stream_id,
|
|
190
|
+
on_bar=on_bar,
|
|
191
|
+
on_threshold_update=on_threshold_update,
|
|
192
|
+
multiplier=multiplier,
|
|
193
|
+
volume_type=volume_type,
|
|
194
|
+
)
|
|
195
|
+
return ctor.batch(ticks_df)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
BarRegistry.register(
|
|
199
|
+
"imbalance_dollar", ImbalanceDollarBarConstructor, batch_fn=compute_imbalance_dollar_bars
|
|
200
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Imbalance bars (tick) — close when absolute signed tick-imbalance 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 |E[\\theta]_n|
|
|
8
|
+
|
|
9
|
+
where :math:`E[T]_n` is the expected tick count per bar and
|
|
10
|
+
:math:`E[\\theta]_n` is the expected signed-imbalance 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 ImbalanceAccumulator
|
|
20
|
+
from flowbars.bars.constructor import BaseBarConstructor
|
|
21
|
+
from flowbars.bars.registry import BarRegistry
|
|
22
|
+
from flowbars.calendars import TradingCalendar
|
|
23
|
+
from flowbars.schema import SchemaMapping
|
|
24
|
+
from flowbars.thresholds import EWMAThresholdEstimator
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ImbalanceTickBarConstructor(BaseBarConstructor):
|
|
28
|
+
"""Bar constructor for tick-imbalance bars.
|
|
29
|
+
|
|
30
|
+
A tick-imbalance bar closes when the absolute signed tick-imbalance
|
|
31
|
+
reaches or exceeds an **adaptive** threshold estimated by an EWMA.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
span : float, default 20.0
|
|
36
|
+
EWMA span for the two-component threshold formula.
|
|
37
|
+
halflife : float or None, default None
|
|
38
|
+
EWMA halflife. Takes precedence over *span* when given.
|
|
39
|
+
initial_ewa_t : float, default 1.0
|
|
40
|
+
Initial seed for :math:`E[T]` (expected tick count per bar).
|
|
41
|
+
initial_ewa_proportion : float, default 0.5
|
|
42
|
+
Initial seed for :math:`E[\\theta]` (expected imbalance proportion).
|
|
43
|
+
warmup_bars : int, default 0
|
|
44
|
+
Number of initial bars to discard while the EWMA converges.
|
|
45
|
+
calendar : TradingCalendar, optional
|
|
46
|
+
schema : SchemaMapping, optional
|
|
47
|
+
watermark : str or None, default ``"timestamp"``
|
|
48
|
+
Dedup key for idempotent resume (``"timestamp"``, a column name, or
|
|
49
|
+
``None`` to disable).
|
|
50
|
+
strict_ordering : bool, default False
|
|
51
|
+
If True, raise ``TickDataError`` when a tick arrives with a timestamp
|
|
52
|
+
earlier than the previous tick's (out-of-order input).
|
|
53
|
+
backend : str, default ``"python"``
|
|
54
|
+
``"python"`` or ``"numba"``. The numba backend accelerates
|
|
55
|
+
:meth:`batch` only; ``update`` always uses Python.
|
|
56
|
+
stream_id : str, default ``"default"``
|
|
57
|
+
on_bar : callable or None
|
|
58
|
+
on_threshold_update : callable or None
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
span: float = 20.0,
|
|
64
|
+
halflife: float | None = None,
|
|
65
|
+
initial_ewa_t: float = 1.0,
|
|
66
|
+
initial_ewa_proportion: float = 0.5,
|
|
67
|
+
warmup_bars: int = 0,
|
|
68
|
+
warmup_minutes: int | None = None,
|
|
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
|
+
accumulator = ImbalanceAccumulator(bar_type="imbalance_tick", metric="tick")
|
|
81
|
+
estimator = EWMAThresholdEstimator(
|
|
82
|
+
bar_family="imbalance",
|
|
83
|
+
span=span,
|
|
84
|
+
halflife=halflife,
|
|
85
|
+
initial_ewa_t=initial_ewa_t,
|
|
86
|
+
initial_ewa_proportion=initial_ewa_proportion,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
super().__init__(
|
|
90
|
+
accumulator=accumulator,
|
|
91
|
+
threshold_estimator=estimator,
|
|
92
|
+
calendar=calendar,
|
|
93
|
+
schema=schema,
|
|
94
|
+
watermark=watermark,
|
|
95
|
+
stream_id=stream_id,
|
|
96
|
+
strict_ordering=strict_ordering,
|
|
97
|
+
warmup_bars=warmup_bars,
|
|
98
|
+
warmup_minutes=warmup_minutes,
|
|
99
|
+
backend=backend,
|
|
100
|
+
on_bar=on_bar,
|
|
101
|
+
on_threshold_update=on_threshold_update,
|
|
102
|
+
multiplier=multiplier,
|
|
103
|
+
volume_type=volume_type,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_state( # type: ignore[override]
|
|
108
|
+
cls,
|
|
109
|
+
state: dict[str, Any],
|
|
110
|
+
calendar: TradingCalendar | None = None,
|
|
111
|
+
schema: SchemaMapping | None = None,
|
|
112
|
+
*,
|
|
113
|
+
verify_identity: bool = True,
|
|
114
|
+
) -> ImbalanceTickBarConstructor:
|
|
115
|
+
"""Reconstruct an ``ImbalanceTickBarConstructor`` from a saved state dict."""
|
|
116
|
+
acc_state = state["accumulator"]
|
|
117
|
+
acc = ImbalanceAccumulator(
|
|
118
|
+
bar_type=state["bar_type"],
|
|
119
|
+
metric=acc_state["metric"],
|
|
120
|
+
)
|
|
121
|
+
est = EWMAThresholdEstimator.from_state(state["threshold_estimator"])
|
|
122
|
+
inst: ImbalanceTickBarConstructor = super().from_state(
|
|
123
|
+
state, acc, est, calendar, schema, verify_identity=verify_identity
|
|
124
|
+
) # type: ignore[assignment]
|
|
125
|
+
return inst
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def compute_imbalance_tick_bars(
|
|
129
|
+
ticks_df: pd.DataFrame,
|
|
130
|
+
span: float = 20.0,
|
|
131
|
+
halflife: float | None = None,
|
|
132
|
+
initial_ewa_t: float = 1.0,
|
|
133
|
+
initial_ewa_proportion: float = 0.5,
|
|
134
|
+
warmup_bars: int = 0,
|
|
135
|
+
warmup_minutes: int | None = None,
|
|
136
|
+
schema: SchemaMapping | None = None,
|
|
137
|
+
watermark: str | None = "timestamp",
|
|
138
|
+
strict_ordering: bool = False,
|
|
139
|
+
calendar: TradingCalendar | None = None,
|
|
140
|
+
*,
|
|
141
|
+
backend: Literal["python", "numba"] = "python",
|
|
142
|
+
stream_id: str = "default",
|
|
143
|
+
on_bar: Any = None,
|
|
144
|
+
on_threshold_update: Any = None,
|
|
145
|
+
multiplier: float = 1.0,
|
|
146
|
+
volume_type: str = "base",
|
|
147
|
+
) -> pd.DataFrame:
|
|
148
|
+
"""Build tick-imbalance bars from a DataFrame of ticks.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
ticks_df : pd.DataFrame
|
|
153
|
+
Tick-level data with user-mapped columns.
|
|
154
|
+
span : float, default 20.0
|
|
155
|
+
EWMA span for the adaptive threshold.
|
|
156
|
+
halflife : float or None, default None
|
|
157
|
+
EWMA halflife (takes precedence over *span*).
|
|
158
|
+
initial_ewa_t : float, default 1.0
|
|
159
|
+
Initial seed for expected tick count per bar.
|
|
160
|
+
initial_ewa_proportion : float, default 0.5
|
|
161
|
+
Initial seed for expected imbalance proportion.
|
|
162
|
+
warmup_bars : int, default 0
|
|
163
|
+
Number of initial bars to discard.
|
|
164
|
+
schema : SchemaMapping, optional
|
|
165
|
+
watermark : str or None, default ``"timestamp"``
|
|
166
|
+
Dedup key for idempotent resume (``"timestamp"``, a column name, or
|
|
167
|
+
``None`` to disable).
|
|
168
|
+
strict_ordering : bool, default False
|
|
169
|
+
If True, raise ``TickDataError`` when a tick arrives with a timestamp
|
|
170
|
+
earlier than the previous tick's (out-of-order input).
|
|
171
|
+
calendar : TradingCalendar, optional
|
|
172
|
+
backend : str, default ``"python"``
|
|
173
|
+
``"python"`` or ``"numba"``.
|
|
174
|
+
stream_id : str, default ``"default"``
|
|
175
|
+
on_bar : callable or None
|
|
176
|
+
on_threshold_update : callable or None
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
pd.DataFrame
|
|
181
|
+
Completed bars as a DataFrame.
|
|
182
|
+
"""
|
|
183
|
+
ctor = ImbalanceTickBarConstructor(
|
|
184
|
+
span=span,
|
|
185
|
+
halflife=halflife,
|
|
186
|
+
initial_ewa_t=initial_ewa_t,
|
|
187
|
+
initial_ewa_proportion=initial_ewa_proportion,
|
|
188
|
+
warmup_bars=warmup_bars,
|
|
189
|
+
warmup_minutes=warmup_minutes,
|
|
190
|
+
calendar=calendar,
|
|
191
|
+
schema=schema,
|
|
192
|
+
watermark=watermark,
|
|
193
|
+
strict_ordering=strict_ordering,
|
|
194
|
+
backend=backend,
|
|
195
|
+
stream_id=stream_id,
|
|
196
|
+
on_bar=on_bar,
|
|
197
|
+
on_threshold_update=on_threshold_update,
|
|
198
|
+
multiplier=multiplier,
|
|
199
|
+
volume_type=volume_type,
|
|
200
|
+
)
|
|
201
|
+
return ctor.batch(ticks_df)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
BarRegistry.register(
|
|
205
|
+
"imbalance_tick", ImbalanceTickBarConstructor, batch_fn=compute_imbalance_tick_bars
|
|
206
|
+
)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Imbalance bars (volume) — close when absolute signed volume-imbalance crosses an adaptive threshold.
|
|
2
|
+
|
|
3
|
+
Same two-component EWMA formula as tick-imbalance bars, but the imbalance
|
|
4
|
+
is weighted 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 ImbalanceAccumulator
|
|
14
|
+
from flowbars.bars.constructor import BaseBarConstructor
|
|
15
|
+
from flowbars.bars.registry import BarRegistry
|
|
16
|
+
from flowbars.calendars import TradingCalendar
|
|
17
|
+
from flowbars.schema import SchemaMapping
|
|
18
|
+
from flowbars.thresholds import EWMAThresholdEstimator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ImbalanceVolumeBarConstructor(BaseBarConstructor):
|
|
22
|
+
"""Bar constructor for volume-imbalance bars.
|
|
23
|
+
|
|
24
|
+
A volume-imbalance bar closes when the absolute signed volume-imbalance
|
|
25
|
+
reaches or exceeds an **adaptive** threshold estimated by an EWMA.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
span : float, default 20.0
|
|
30
|
+
EWMA span for the two-component threshold formula.
|
|
31
|
+
halflife : float or None, default None
|
|
32
|
+
EWMA halflife. Takes precedence over *span* when given.
|
|
33
|
+
initial_ewa_t : float, default 1.0
|
|
34
|
+
Initial seed for :math:`E[T]` (expected volume per bar).
|
|
35
|
+
initial_ewa_proportion : float, default 0.5
|
|
36
|
+
Initial seed for :math:`E[\\theta]` (expected imbalance proportion).
|
|
37
|
+
warmup_bars : int, default 0
|
|
38
|
+
Number of initial bars to discard while the EWMA converges.
|
|
39
|
+
calendar : TradingCalendar, optional
|
|
40
|
+
schema : SchemaMapping, optional
|
|
41
|
+
watermark : str or None, default ``"timestamp"``
|
|
42
|
+
Dedup key for idempotent resume (``"timestamp"``, a column name, or
|
|
43
|
+
``None`` to disable).
|
|
44
|
+
strict_ordering : bool, default False
|
|
45
|
+
If True, raise ``TickDataError`` when a tick arrives with a timestamp
|
|
46
|
+
earlier than the previous tick's (out-of-order input).
|
|
47
|
+
backend : str, default ``"python"``
|
|
48
|
+
``"python"`` or ``"numba"``. The numba backend accelerates
|
|
49
|
+
:meth:`batch` only; ``update`` always uses Python.
|
|
50
|
+
stream_id : str, default ``"default"``
|
|
51
|
+
on_bar : callable or None
|
|
52
|
+
on_threshold_update : callable or None
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
span: float = 20.0,
|
|
58
|
+
halflife: float | None = None,
|
|
59
|
+
initial_ewa_t: float = 1.0,
|
|
60
|
+
initial_ewa_proportion: float = 0.5,
|
|
61
|
+
warmup_bars: int = 0,
|
|
62
|
+
warmup_minutes: int | None = None,
|
|
63
|
+
calendar: TradingCalendar | None = None,
|
|
64
|
+
schema: SchemaMapping | None = None,
|
|
65
|
+
watermark: str | None = "timestamp",
|
|
66
|
+
stream_id: str = "default",
|
|
67
|
+
strict_ordering: bool = False,
|
|
68
|
+
backend: Literal["python", "numba"] = "python",
|
|
69
|
+
on_bar: Any = None,
|
|
70
|
+
on_threshold_update: Any = None,
|
|
71
|
+
multiplier: float = 1.0,
|
|
72
|
+
volume_type: str = "base",
|
|
73
|
+
) -> None:
|
|
74
|
+
accumulator = ImbalanceAccumulator(bar_type="imbalance_volume", metric="volume")
|
|
75
|
+
estimator = EWMAThresholdEstimator(
|
|
76
|
+
bar_family="imbalance",
|
|
77
|
+
span=span,
|
|
78
|
+
halflife=halflife,
|
|
79
|
+
initial_ewa_t=initial_ewa_t,
|
|
80
|
+
initial_ewa_proportion=initial_ewa_proportion,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
super().__init__(
|
|
84
|
+
accumulator=accumulator,
|
|
85
|
+
threshold_estimator=estimator,
|
|
86
|
+
calendar=calendar,
|
|
87
|
+
schema=schema,
|
|
88
|
+
watermark=watermark,
|
|
89
|
+
stream_id=stream_id,
|
|
90
|
+
strict_ordering=strict_ordering,
|
|
91
|
+
warmup_bars=warmup_bars,
|
|
92
|
+
warmup_minutes=warmup_minutes,
|
|
93
|
+
backend=backend,
|
|
94
|
+
on_bar=on_bar,
|
|
95
|
+
on_threshold_update=on_threshold_update,
|
|
96
|
+
multiplier=multiplier,
|
|
97
|
+
volume_type=volume_type,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_state( # type: ignore[override]
|
|
102
|
+
cls,
|
|
103
|
+
state: dict[str, Any],
|
|
104
|
+
calendar: TradingCalendar | None = None,
|
|
105
|
+
schema: SchemaMapping | None = None,
|
|
106
|
+
*,
|
|
107
|
+
verify_identity: bool = True,
|
|
108
|
+
) -> ImbalanceVolumeBarConstructor:
|
|
109
|
+
"""Reconstruct an ``ImbalanceVolumeBarConstructor`` from a saved state dict."""
|
|
110
|
+
acc_state = state["accumulator"]
|
|
111
|
+
acc = ImbalanceAccumulator(
|
|
112
|
+
bar_type=state["bar_type"],
|
|
113
|
+
metric=acc_state["metric"],
|
|
114
|
+
)
|
|
115
|
+
est = EWMAThresholdEstimator.from_state(state["threshold_estimator"])
|
|
116
|
+
inst: ImbalanceVolumeBarConstructor = super().from_state(
|
|
117
|
+
state, acc, est, calendar, schema, verify_identity=verify_identity
|
|
118
|
+
) # type: ignore[assignment]
|
|
119
|
+
return inst
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def compute_imbalance_volume_bars(
|
|
123
|
+
ticks_df: pd.DataFrame,
|
|
124
|
+
span: float = 20.0,
|
|
125
|
+
halflife: float | None = None,
|
|
126
|
+
initial_ewa_t: float = 1.0,
|
|
127
|
+
initial_ewa_proportion: float = 0.5,
|
|
128
|
+
warmup_bars: int = 0,
|
|
129
|
+
warmup_minutes: int | None = None,
|
|
130
|
+
schema: SchemaMapping | None = None,
|
|
131
|
+
watermark: str | None = "timestamp",
|
|
132
|
+
strict_ordering: bool = False,
|
|
133
|
+
calendar: TradingCalendar | None = None,
|
|
134
|
+
*,
|
|
135
|
+
backend: Literal["python", "numba"] = "python",
|
|
136
|
+
stream_id: str = "default",
|
|
137
|
+
on_bar: Any = None,
|
|
138
|
+
on_threshold_update: Any = None,
|
|
139
|
+
multiplier: float = 1.0,
|
|
140
|
+
volume_type: str = "base",
|
|
141
|
+
) -> pd.DataFrame:
|
|
142
|
+
"""Build volume-imbalance bars from a DataFrame of ticks.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
ticks_df : pd.DataFrame
|
|
147
|
+
Tick-level data with user-mapped columns.
|
|
148
|
+
span : float, default 20.0
|
|
149
|
+
EWMA span for the adaptive threshold.
|
|
150
|
+
halflife : float or None, default None
|
|
151
|
+
EWMA halflife (takes precedence over *span*).
|
|
152
|
+
initial_ewa_t : float, default 1.0
|
|
153
|
+
Initial seed for expected volume per bar.
|
|
154
|
+
initial_ewa_proportion : float, default 0.5
|
|
155
|
+
Initial seed for expected imbalance proportion.
|
|
156
|
+
warmup_bars : int, default 0
|
|
157
|
+
Number of initial bars to discard.
|
|
158
|
+
schema : SchemaMapping, optional
|
|
159
|
+
watermark : str or None, default ``"timestamp"``
|
|
160
|
+
Dedup key for idempotent resume (``"timestamp"``, a column name, or
|
|
161
|
+
``None`` to disable).
|
|
162
|
+
strict_ordering : bool, default False
|
|
163
|
+
If True, raise ``TickDataError`` when a tick arrives with a timestamp
|
|
164
|
+
earlier than the previous tick's (out-of-order input).
|
|
165
|
+
calendar : TradingCalendar, optional
|
|
166
|
+
backend : str, default ``"python"``
|
|
167
|
+
``"python"`` or ``"numba"``.
|
|
168
|
+
stream_id : str, default ``"default"``
|
|
169
|
+
on_bar : callable or None
|
|
170
|
+
on_threshold_update : callable or None
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
pd.DataFrame
|
|
175
|
+
Completed bars as a DataFrame.
|
|
176
|
+
"""
|
|
177
|
+
ctor = ImbalanceVolumeBarConstructor(
|
|
178
|
+
span=span,
|
|
179
|
+
halflife=halflife,
|
|
180
|
+
initial_ewa_t=initial_ewa_t,
|
|
181
|
+
initial_ewa_proportion=initial_ewa_proportion,
|
|
182
|
+
warmup_bars=warmup_bars,
|
|
183
|
+
warmup_minutes=warmup_minutes,
|
|
184
|
+
calendar=calendar,
|
|
185
|
+
schema=schema,
|
|
186
|
+
watermark=watermark,
|
|
187
|
+
strict_ordering=strict_ordering,
|
|
188
|
+
backend=backend,
|
|
189
|
+
stream_id=stream_id,
|
|
190
|
+
on_bar=on_bar,
|
|
191
|
+
on_threshold_update=on_threshold_update,
|
|
192
|
+
multiplier=multiplier,
|
|
193
|
+
volume_type=volume_type,
|
|
194
|
+
)
|
|
195
|
+
return ctor.batch(ticks_df)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
BarRegistry.register(
|
|
199
|
+
"imbalance_volume", ImbalanceVolumeBarConstructor, batch_fn=compute_imbalance_volume_bars
|
|
200
|
+
)
|