flowbars 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- flowbars-0.1.0/MANIFEST.in +1 -0
- flowbars-0.1.0/PKG-INFO +650 -0
- flowbars-0.1.0/README.md +605 -0
- flowbars-0.1.0/flowbars/__init__.py +163 -0
- flowbars-0.1.0/flowbars/_version.py +8 -0
- flowbars-0.1.0/flowbars/adapters/__init__.py +29 -0
- flowbars-0.1.0/flowbars/adapters/polars.py +1115 -0
- flowbars-0.1.0/flowbars/bars/__init__.py +3 -0
- flowbars-0.1.0/flowbars/bars/accumulators.py +731 -0
- flowbars-0.1.0/flowbars/bars/constructor.py +1254 -0
- flowbars-0.1.0/flowbars/bars/information/__init__.py +41 -0
- flowbars-0.1.0/flowbars/bars/information/imbalance_dollar_bars.py +200 -0
- flowbars-0.1.0/flowbars/bars/information/imbalance_tick_bars.py +206 -0
- flowbars-0.1.0/flowbars/bars/information/imbalance_volume_bars.py +200 -0
- flowbars-0.1.0/flowbars/bars/information/run_dollar_bars.py +225 -0
- flowbars-0.1.0/flowbars/bars/information/run_tick_bars.py +231 -0
- flowbars-0.1.0/flowbars/bars/information/run_volume_bars.py +225 -0
- flowbars-0.1.0/flowbars/bars/minute/__init__.py +99 -0
- flowbars-0.1.0/flowbars/bars/minute/accumulators.py +303 -0
- flowbars-0.1.0/flowbars/bars/minute/engine.py +244 -0
- flowbars-0.1.0/flowbars/bars/minute/information.py +622 -0
- flowbars-0.1.0/flowbars/bars/minute/numba.py +1043 -0
- flowbars-0.1.0/flowbars/bars/minute/registry.py +22 -0
- flowbars-0.1.0/flowbars/bars/minute/standard.py +261 -0
- flowbars-0.1.0/flowbars/bars/minute/structural.py +474 -0
- flowbars-0.1.0/flowbars/bars/numba_backend.py +1227 -0
- flowbars-0.1.0/flowbars/bars/output.py +106 -0
- flowbars-0.1.0/flowbars/bars/registry.py +47 -0
- flowbars-0.1.0/flowbars/bars/spec.py +367 -0
- flowbars-0.1.0/flowbars/bars/standard/__init__.py +17 -0
- flowbars-0.1.0/flowbars/bars/standard/dollar_bars.py +168 -0
- flowbars-0.1.0/flowbars/bars/standard/tick_bars.py +166 -0
- flowbars-0.1.0/flowbars/bars/standard/time_bars.py +192 -0
- flowbars-0.1.0/flowbars/bars/standard/volume_bars.py +168 -0
- flowbars-0.1.0/flowbars/benchmarks.py +420 -0
- flowbars-0.1.0/flowbars/calendars.py +368 -0
- flowbars-0.1.0/flowbars/core.py +109 -0
- flowbars-0.1.0/flowbars/py.typed +0 -0
- flowbars-0.1.0/flowbars/quality.py +393 -0
- flowbars-0.1.0/flowbars/sample_data/__init__.py +28 -0
- flowbars-0.1.0/flowbars/sample_data/tick_data.csv +501 -0
- flowbars-0.1.0/flowbars/schema.py +476 -0
- flowbars-0.1.0/flowbars/thresholds.py +1167 -0
- flowbars-0.1.0/flowbars/tick_rule.py +89 -0
- flowbars-0.1.0/flowbars.egg-info/PKG-INFO +650 -0
- flowbars-0.1.0/flowbars.egg-info/SOURCES.txt +83 -0
- flowbars-0.1.0/flowbars.egg-info/dependency_links.txt +1 -0
- flowbars-0.1.0/flowbars.egg-info/requires.txt +24 -0
- flowbars-0.1.0/flowbars.egg-info/top_level.txt +1 -0
- flowbars-0.1.0/pyproject.toml +110 -0
- flowbars-0.1.0/setup.cfg +4 -0
- flowbars-0.1.0/tests/test_accumulators.py +870 -0
- flowbars-0.1.0/tests/test_bar_type_spec.py +165 -0
- flowbars-0.1.0/tests/test_calendars.py +357 -0
- flowbars-0.1.0/tests/test_calendars_extra.py +99 -0
- flowbars-0.1.0/tests/test_constructor.py +948 -0
- flowbars-0.1.0/tests/test_contract_metadata.py +316 -0
- flowbars-0.1.0/tests/test_information_bars.py +960 -0
- flowbars-0.1.0/tests/test_minute_bars.py +341 -0
- flowbars-0.1.0/tests/test_minute_calibration.py +283 -0
- flowbars-0.1.0/tests/test_minute_engine.py +437 -0
- flowbars-0.1.0/tests/test_minute_new_types.py +431 -0
- flowbars-0.1.0/tests/test_minute_numba.py +303 -0
- flowbars-0.1.0/tests/test_minute_session_policies.py +405 -0
- flowbars-0.1.0/tests/test_minute_state.py +278 -0
- flowbars-0.1.0/tests/test_numba_backend.py +1150 -0
- flowbars-0.1.0/tests/test_output_contract.py +175 -0
- flowbars-0.1.0/tests/test_polars_adapter.py +534 -0
- flowbars-0.1.0/tests/test_public_api.py +558 -0
- flowbars-0.1.0/tests/test_quality.py +495 -0
- flowbars-0.1.0/tests/test_sample_data.py +120 -0
- flowbars-0.1.0/tests/test_schema.py +363 -0
- flowbars-0.1.0/tests/test_session_gaps.py +350 -0
- flowbars-0.1.0/tests/test_smoke.py +74 -0
- flowbars-0.1.0/tests/test_standard_bars.py +554 -0
- flowbars-0.1.0/tests/test_state_identity.py +394 -0
- flowbars-0.1.0/tests/test_state_v2.py +283 -0
- flowbars-0.1.0/tests/test_strict_ordering.py +106 -0
- flowbars-0.1.0/tests/test_thresholds.py +637 -0
- flowbars-0.1.0/tests/test_tick_rule.py +173 -0
- flowbars-0.1.0/tests/test_update_many.py +253 -0
- flowbars-0.1.0/tests/test_volume_semantics.py +540 -0
- flowbars-0.1.0/tests/test_warmup_contract.py +297 -0
- flowbars-0.1.0/tests/test_watermark.py +210 -0
flowbars-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Syed Qaisar Jalil
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include flowbars/py.typed
|
flowbars-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowbars
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-grade bar construction for financial ML — standard and information-driven bars, correct per AFML.
|
|
5
|
+
Author: Syed Qaisar Jalil
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/syedqaisarjalil/flowbars
|
|
8
|
+
Project-URL: Repository, https://github.com/syedqaisarjalil/flowbars
|
|
9
|
+
Project-URL: Issues, https://github.com/syedqaisarjalil/flowbars/issues
|
|
10
|
+
Keywords: financial-data,trading,ohlcv,market-microstructure,machine-learning,afml,bar-construction
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Requires-Dist: pandas>=1.5
|
|
28
|
+
Provides-Extra: numba
|
|
29
|
+
Requires-Dist: numba>=0.57; extra == "numba"
|
|
30
|
+
Provides-Extra: polars
|
|
31
|
+
Requires-Dist: polars>=0.19; extra == "polars"
|
|
32
|
+
Requires-Dist: pyarrow>=12; extra == "polars"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff; extra == "dev"
|
|
38
|
+
Provides-Extra: quality
|
|
39
|
+
Requires-Dist: scipy>=1.9; extra == "quality"
|
|
40
|
+
Provides-Extra: calendars
|
|
41
|
+
Requires-Dist: exchange_calendars>=4.0; extra == "calendars"
|
|
42
|
+
Provides-Extra: all
|
|
43
|
+
Requires-Dist: flowbars[calendars,numba,polars,quality]; extra == "all"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# flowbars
|
|
47
|
+
|
|
48
|
+
Production-grade bar construction for financial ML — standard and
|
|
49
|
+
information-driven bars, correct per *Advances in Financial Machine Learning*
|
|
50
|
+
(López de Prado, 2018).
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from flowbars import load_sample_data, compute_tick_bars, bar_quality_report
|
|
54
|
+
from flowbars.schema import SchemaMapping
|
|
55
|
+
|
|
56
|
+
df = load_sample_data()
|
|
57
|
+
schema = SchemaMapping({"timestamp": "timestamp", "price": "price", "volume": "volume"})
|
|
58
|
+
bars = compute_tick_bars(df, threshold=100, schema=schema)
|
|
59
|
+
report = bar_quality_report(bars)
|
|
60
|
+
print(f"Ljung-Box p-value: {report['ljung_box']['p_value']:.4f}")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## What makes this different
|
|
64
|
+
|
|
65
|
+
### Correct AFML-spec thresholds
|
|
66
|
+
|
|
67
|
+
The two-component EWMA formula from AFML §2.4 is implemented exactly:
|
|
68
|
+
|
|
69
|
+
| Bar family | Threshold formula |
|
|
70
|
+
|---|---|
|
|
71
|
+
| Imbalance | *Tₙ = E₀[T]ₙ × \|E₀[θ]ₙ\|* |
|
|
72
|
+
| Run | *Tₙ = E₀[T]ₙ × max(E₀[P⁺]ₙ, 1 − E₀[P⁺]ₙ)* |
|
|
73
|
+
|
|
74
|
+
Both terms are updated **at bar-close time** using the bar-level statistics
|
|
75
|
+
(tick count or volume, signed-proportion or buy-fraction). This is not a
|
|
76
|
+
single-EWMA approximation — it is the specification.
|
|
77
|
+
|
|
78
|
+
Other libraries (mlfinlab, mlfinpy) use a single EWMA on the raw cumulative
|
|
79
|
+
metric, which produces different bar counts and different information content.
|
|
80
|
+
If you are reproducing research, this matters.
|
|
81
|
+
|
|
82
|
+
### Streaming + resumable
|
|
83
|
+
|
|
84
|
+
Every bar constructor supports `update(tick)` for real-time streaming and
|
|
85
|
+
`get_state()` / `load_state()` / `from_state()` for crash-safe resume:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# Save mid-stream state
|
|
89
|
+
state = ctor.get_state() # plain dict, JSON-serializable
|
|
90
|
+
|
|
91
|
+
# Resume later — identical output as uninterrupted
|
|
92
|
+
ctor2 = TickBarConstructor.from_state(state)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Resume is **idempotent** via a configurable watermark: re-feeding ticks that
|
|
96
|
+
were already processed is silently discarded. Defaults to the tick timestamp;
|
|
97
|
+
pass `watermark="seq"` (or any column name) for feeds that emit multiple ticks
|
|
98
|
+
per millisecond, and `watermark=None` to disable.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
ctor = TickBarConstructor(threshold=100, schema=schema, watermark="timestamp")
|
|
102
|
+
state = ctor.get_state()
|
|
103
|
+
|
|
104
|
+
# Re-feeding an already-processed tick is a no-op
|
|
105
|
+
ctor2 = TickBarConstructor.from_state(state, schema=schema)
|
|
106
|
+
ctor2.update(tick_already_processed) # -> None (deduplicated)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
State is versioned (`schema_version`/`engine_version`) and carries the full
|
|
110
|
+
params plus a SHA-256 params hash, so resuming with a mismatched threshold,
|
|
111
|
+
multiplier, volume type, or session policy raises `StateValidationError`
|
|
112
|
+
instead of silently changing bar semantics. The state also records the
|
|
113
|
+
canonical registry key (`registry_name`), the calendar identity
|
|
114
|
+
(`calendar` + `calendar_fingerprint`), the input column mapping
|
|
115
|
+
(`schema_mapping` + `schema_fingerprint`), and the active session
|
|
116
|
+
(`session_id`, `last_session_end`, `in_session`). Resuming with a different
|
|
117
|
+
calendar or schema mapping therefore fails loudly — pass
|
|
118
|
+
`verify_identity=False` to `load_state()` / `from_state()` to opt out.
|
|
119
|
+
Tick and minute state can never be cross-loaded (`input_kind` is validated).
|
|
120
|
+
`update_many(df)` streams a whole DataFrame through the same `update()` path.
|
|
121
|
+
|
|
122
|
+
### Warm-up contract
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
ctor = TickBarConstructor(
|
|
126
|
+
threshold=100,
|
|
127
|
+
schema=schema,
|
|
128
|
+
warmup_minutes=30, # discard bars closing in the first 30 minutes
|
|
129
|
+
)
|
|
130
|
+
# or warmup_bars=N — the two are mutually exclusive
|
|
131
|
+
...
|
|
132
|
+
assert ctor.warmup_done # True once the warm-up window has elapsed
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Warm-up bars keep their original `bar_id`, still fire `on_bar`, and still feed
|
|
136
|
+
the adaptive estimator; only their return is suppressed.
|
|
137
|
+
|
|
138
|
+
### Session policies
|
|
139
|
+
|
|
140
|
+
On each session-boundary transition (weekends, holidays, early closes, DST —
|
|
141
|
+
whatever the calendar reports) the in-progress bar is handled per
|
|
142
|
+
`session_policy`:
|
|
143
|
+
|
|
144
|
+
| Policy | Behavior |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `"close"` (default) | Force-close as a `partial` bar; no bar spans two sessions |
|
|
147
|
+
| `"discard"` | Drop the in-progress bar silently (no callback, no overflow carried) |
|
|
148
|
+
| `"carry"` | Keep the bar open across the boundary (opt-in); the gap adds no return |
|
|
149
|
+
|
|
150
|
+
Session-only feeds (equity/FX data that never prints outside the trading day)
|
|
151
|
+
are handled too: the engine compares each in-session input against the
|
|
152
|
+
calendar's next session open, so the first input of a later session
|
|
153
|
+
force-closes the hanging bar at the **prior session's close** (`partial=True`,
|
|
154
|
+
`close_ts` = last session end) instead of letting one bar span the weekend or a
|
|
155
|
+
holiday. Minute volatility/hybrid bars exclude the gap return, and the minute
|
|
156
|
+
tick rule derives no sign across the gap.
|
|
157
|
+
|
|
158
|
+
When a stream simply ends mid-session (no later input to reveal the gap), call
|
|
159
|
+
`flush()` to emit the open bar as a `partial` bar:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
bar = ctor.flush() # close_ts = calendar session end of the last input
|
|
163
|
+
bar = ctor.flush(now_ms) # or an explicit close instant
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`session_estimator="persist"` (default) keeps EWMA learning across sessions;
|
|
167
|
+
`"reset"` restores the seeds at each boundary. Policies are per constructor,
|
|
168
|
+
so different series can choose independently.
|
|
169
|
+
|
|
170
|
+
### Discover bar types
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from flowbars.bars.registry import BarRegistry
|
|
174
|
+
|
|
175
|
+
BarRegistry.list() # ['dollar', 'imbalance_tick', ...]
|
|
176
|
+
spec = BarRegistry.get_spec("volume")
|
|
177
|
+
spec.params_schema # JSON Schema for constructor params
|
|
178
|
+
spec.output_columns # core_columns + extras_columns
|
|
179
|
+
spec.core_columns # the 13 contract columns
|
|
180
|
+
spec.extras_columns # typed bar-specific extras ({} for built-ins)
|
|
181
|
+
spec.session_policies # ('close',)
|
|
182
|
+
spec.state_schema_version # 2
|
|
183
|
+
spec.spec_version # output-contract version (pin with params_hash)
|
|
184
|
+
spec.warmup_minutes # recommended warmup; 0 = none required
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`MinuteBarRegistry` mirrors this for the `minute_*` types. The registry key
|
|
188
|
+
is the canonical identity (`minute_volume`), while each row's `bar_type`
|
|
189
|
+
label is the shorter family name (`volume`); `frame.attrs["registry_name"]`
|
|
190
|
+
and `state["registry_name"]` carry the unambiguous key.
|
|
191
|
+
|
|
192
|
+
Rebuild any registered type from saved state without knowing its concrete
|
|
193
|
+
constructor:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
resumed = BarRegistry.constructor_from_state("volume", state, schema=schema)
|
|
197
|
+
resumed_minute = MinuteBarRegistry.constructor_from_state(
|
|
198
|
+
"minute_renko", state, schema=minute_schema
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Pin a downstream contract with `params_hash(params)` (canonical JSON: sorted
|
|
203
|
+
keys, no whitespace, NaN/Infinity rejected) — frame attrs expose
|
|
204
|
+
`(registry_name, spec_version, params_hash)` for exactly this.
|
|
205
|
+
|
|
206
|
+
### Dual backend
|
|
207
|
+
|
|
208
|
+
| Backend | Dependency | Speed vs. pure-Python |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `"python"` | numpy, pandas | 1× (baseline) |
|
|
211
|
+
| `"numba"` | + numba | 85× – 237× |
|
|
212
|
+
|
|
213
|
+
The numba backend JIT-compiles the tick-ingestion loop. Compilation happens
|
|
214
|
+
on first call; subsequent calls run at native speed. If numba is not installed
|
|
215
|
+
or compilation fails, the Python path is used automatically with a warning.
|
|
216
|
+
|
|
217
|
+
### Honest benchmarks
|
|
218
|
+
|
|
219
|
+
Measured, not assumed. Compilation cost reported separately. Benchmark script
|
|
220
|
+
in the repo — run it yourself:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
python -m flowbars.benchmarks
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Results on 250,000 synthetic ticks (Intel/AMD x86-64, numba 0.57+):
|
|
227
|
+
|
|
228
|
+
| Bar type | Python (s) | numba (ms) | Speedup |
|
|
229
|
+
|---|---|---|---|
|
|
230
|
+
| tick | 0.56 | 6.6 | **86×** |
|
|
231
|
+
| volume | 0.56 | 5.1 | **109×** |
|
|
232
|
+
| dollar | 0.68 | 4.7 | **145×** |
|
|
233
|
+
| time (5-min) | 0.57 | 3.2 | **181×** |
|
|
234
|
+
| imbalance_tick | 0.61 | 3.5 | **177×** |
|
|
235
|
+
| imbalance_volume | 0.64 | 3.3 | **194×** |
|
|
236
|
+
| imbalance_dollar | 0.75 | 6.1 | **123×** |
|
|
237
|
+
| run_tick | 1.04 | 5.0 | **210×** |
|
|
238
|
+
| run_volume | 1.00 | 4.2 | **237×** |
|
|
239
|
+
| run_dollar | 1.24 | 6.7 | **185×** |
|
|
240
|
+
| imbalance_tick (EWMA) | 0.59 | 5.3 | **111×** |
|
|
241
|
+
| run_tick (EWMA) | 2.40 | 18.8 | **127×** |
|
|
242
|
+
|
|
243
|
+
### Pluggable calendars
|
|
244
|
+
|
|
245
|
+
Four calendars cover every asset class, with full holiday/DST support where it
|
|
246
|
+
matters:
|
|
247
|
+
|
|
248
|
+
| Asset class | Calendar |
|
|
249
|
+
|---|---|
|
|
250
|
+
| Crypto (spot/perp, 24/7) | `ContinuousCalendar` |
|
|
251
|
+
| FX majors/minors (24/5) | `WeekdayCalendar` |
|
|
252
|
+
| US equities | `ExchangeCalendar("XNYS")` |
|
|
253
|
+
| US futures / commodities | `ExchangeCalendar("CME")` |
|
|
254
|
+
| LSE / EU equities | `ExchangeCalendar("LSE")` / `("XETR")` |
|
|
255
|
+
| Custom fixed UTC session | `SessionCalendar(...)` |
|
|
256
|
+
|
|
257
|
+
- `ContinuousCalendar` — always open; never a boundary.
|
|
258
|
+
- `WeekdayCalendar` — open Mon–Fri; boundaries only at weekends.
|
|
259
|
+
- `SessionCalendar` — fixed daily UTC hours (no holidays/DST — use only for a
|
|
260
|
+
constant UTC window).
|
|
261
|
+
- `ExchangeCalendar` — wraps `exchange_calendars` for per-exchange holidays,
|
|
262
|
+
half-days, and DST-aware sessions (`pip install flowbars[calendars]`).
|
|
263
|
+
|
|
264
|
+
Session boundaries trigger automatic bar close and accumulator reset.
|
|
265
|
+
|
|
266
|
+
### No silent guesses
|
|
267
|
+
|
|
268
|
+
Explicit schema mapping — you tell the library your column names. No
|
|
269
|
+
auto-detection, no magic. If a required column is missing, you get a clear
|
|
270
|
+
`SchemaError`.
|
|
271
|
+
|
|
272
|
+
### Bar quality diagnostics
|
|
273
|
+
|
|
274
|
+
`bar_quality_report()` provides honest, standard statistical tests. `scipy`
|
|
275
|
+
is optional — without it the Ljung-Box / Jarque-Bera statistics are still
|
|
276
|
+
computed (in numpy) but their p-values and reject flags are `None`.
|
|
277
|
+
|
|
278
|
+
| Diagnostic | Test | What it flags |
|
|
279
|
+
|---|---|---|
|
|
280
|
+
| Autocorrelation | Ljung-Box | Serially-correlated returns |
|
|
281
|
+
| Normality | Jarque-Bera | Non-normal return distribution |
|
|
282
|
+
| Stability | CV of per-day bar counts | Uneven sampling rate |
|
|
283
|
+
| Fragmentation | Fraction of run-bars ≤ 2 ticks/minutes | Over-fragmented run bars |
|
|
284
|
+
| Return moments | mean, std, skewness, kurtosis | Distribution shape |
|
|
285
|
+
| Return ACF | lag-1, lag-5 (raw and `\|r\|`) | Serial dependence, volatility clustering |
|
|
286
|
+
| Variance ratio | lag-2, lag-5 | Random-walk departure |
|
|
287
|
+
| Entropy | Histogram Shannon entropy | Information content (normalized) |
|
|
288
|
+
| Bar-size CV | CV of volume (or `num_ticks`) | Uneven bar sizes |
|
|
289
|
+
| Duration CV | CV of `close_ts − open_ts` | Uneven bar durations |
|
|
290
|
+
| Effective sample size | `n·(1−ρ₁)/(1+ρ₁)` | Overstated sample size from autocorrelation |
|
|
291
|
+
| OHLC integrity | Violations of `low ≤ open, close ≤ high` | Malformed bars |
|
|
292
|
+
| Composite score | Mean of available sub-scores | One-number quality summary |
|
|
293
|
+
|
|
294
|
+
All metrics are deterministic (fixed lag set, fixed histogram rule, no RNG).
|
|
295
|
+
For minute bars pass `num_ticks_unit="minutes"`; the run-fragmentation
|
|
296
|
+
threshold is configurable with `fragmentation_threshold=`. Run fragmentation
|
|
297
|
+
is masked to `run_*` bar types, so mixed frames report correctly.
|
|
298
|
+
|
|
299
|
+
## Bar types
|
|
300
|
+
|
|
301
|
+
### Standard (activity-based)
|
|
302
|
+
|
|
303
|
+
| Bar type | Closes when… | Constructor |
|
|
304
|
+
|---|---|---|
|
|
305
|
+
| tick | `num_ticks ≥ threshold` | `TickBarConstructor` |
|
|
306
|
+
| volume | `cumulative_volume ≥ threshold` | `VolumeBarConstructor` |
|
|
307
|
+
| dollar | `cumulative_dollar ≥ threshold` | `DollarBarConstructor` |
|
|
308
|
+
| time | tick crosses interval boundary | `TimeBarConstructor` |
|
|
309
|
+
|
|
310
|
+
### Information-driven (adaptive threshold)
|
|
311
|
+
|
|
312
|
+
| Bar type | Closes when… | Constructor |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| imbalance_tick | \|Σ signed ticks\| ≥ adaptive T | `ImbalanceTickBarConstructor` |
|
|
315
|
+
| imbalance_volume | \|Σ signed volume\| ≥ adaptive T | `ImbalanceVolumeBarConstructor` |
|
|
316
|
+
| imbalance_dollar | \|Σ signed dollar\| ≥ adaptive T | `ImbalanceDollarBarConstructor` |
|
|
317
|
+
| run_tick | same-sign tick run ≥ adaptive T | `RunTickBarConstructor` |
|
|
318
|
+
| run_volume | same-sign volume run ≥ adaptive T | `RunVolumeBarConstructor` |
|
|
319
|
+
| run_dollar | same-sign dollar run ≥ adaptive T | `RunDollarBarConstructor` |
|
|
320
|
+
|
|
321
|
+
All ten are available through convenience functions:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from flowbars import (
|
|
325
|
+
compute_tick_bars,
|
|
326
|
+
compute_volume_bars,
|
|
327
|
+
compute_dollar_bars,
|
|
328
|
+
compute_time_bars,
|
|
329
|
+
compute_imbalance_tick_bars,
|
|
330
|
+
compute_imbalance_volume_bars,
|
|
331
|
+
compute_imbalance_dollar_bars,
|
|
332
|
+
compute_run_tick_bars,
|
|
333
|
+
compute_run_volume_bars,
|
|
334
|
+
compute_run_dollar_bars,
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Every tick constructor and `compute_*_bars` function accepts `warmup_bars` /
|
|
339
|
+
`warmup_minutes`, `backend`, `stream_id`, `multiplier`, `volume_type`,
|
|
340
|
+
`session_policy`, `session_estimator`, and the `on_bar` /
|
|
341
|
+
`on_threshold_update` callbacks, matching the minute path's signature surface.
|
|
342
|
+
|
|
343
|
+
## Minute-OHLCV path
|
|
344
|
+
|
|
345
|
+
Non-time bars can also be built from **minute OHLCV candles** instead of raw
|
|
346
|
+
ticks. The minute path has full mode parity with the tick path — streaming
|
|
347
|
+
`update()`/`update_many()`, versioned state save/resume, callbacks,
|
|
348
|
+
`warmup_bars`/`warmup_minutes`, watermark dedup, `strict_ordering`, calendars
|
|
349
|
+
with the same session policies, the numba batch backend, and the polars
|
|
350
|
+
adapter — and produces the identical 13-column output schema. `num_ticks`
|
|
351
|
+
counts **minutes**, and `dollar_value` / `vwap` use `close × volume` as the
|
|
352
|
+
notional proxy (so dollar-family minute bars are flagged
|
|
353
|
+
`approximate=True` in the registry spec and frame metadata).
|
|
354
|
+
|
|
355
|
+
| Minute type | Closes when… | Function |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| volume | `Σ volume ≥ threshold` | `compute_volume_bars_from_minutes` |
|
|
358
|
+
| dollar | `Σ close×volume ≥ threshold` | `compute_dollar_bars_from_minutes` |
|
|
359
|
+
| imbalance_volume | \|Σ sign×volume\| ≥ EWMA T | `compute_imbalance_volume_bars_from_minutes` |
|
|
360
|
+
| imbalance_dollar | \|Σ sign×close×volume\| ≥ EWMA T | `compute_imbalance_dollar_bars_from_minutes` |
|
|
361
|
+
| run_volume | same-sign volume run ≥ EWMA T | `compute_run_volume_bars_from_minutes` |
|
|
362
|
+
| run_dollar | same-sign dollar run ≥ EWMA T | `compute_run_dollar_bars_from_minutes` |
|
|
363
|
+
| volatility | `Σ \|closeₜ−closeₜ₋₁\|/closeₜ₋₁ ≥ threshold` | `compute_volatility_bars_from_minutes` |
|
|
364
|
+
| range | `Σ (high−low)/close ≥ threshold` | `compute_range_bars_from_minutes` |
|
|
365
|
+
| renko | `\|close−anchor\|/anchor ≥ threshold` (anchor = first open) | `compute_renko_bars_from_minutes` |
|
|
366
|
+
| hybrid | both `Σ close×volume` and volatility targets met | `compute_hybrid_bars_from_minutes` |
|
|
367
|
+
|
|
368
|
+
The four structural types (volatility, range, renko, hybrid) are minute-only
|
|
369
|
+
and use **static** thresholds. Renko displacement is not additive, so its
|
|
370
|
+
anchor resets on every close with no overflow; the other three roll their
|
|
371
|
+
scalar excess into the next bar. Volatility and hybrid reset `prev_close` at
|
|
372
|
+
session boundaries so an overnight gap contributes no fake return.
|
|
373
|
+
|
|
374
|
+
### Calibrate thresholds from minute data
|
|
375
|
+
|
|
376
|
+
`MinuteCalibrationHelper` is deterministic (no RNG, fixed bisection
|
|
377
|
+
iterations): repeated calls on the same input return identical values. Targets
|
|
378
|
+
are bars **per UTC day**, scaled by the number of observed UTC days.
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
from flowbars import MinuteCalibrationHelper
|
|
382
|
+
|
|
383
|
+
vol_threshold = MinuteCalibrationHelper.estimate_volume_threshold(minutes, 50.0)
|
|
384
|
+
renko_threshold = MinuteCalibrationHelper.estimate_renko_threshold(minutes, 50.0)
|
|
385
|
+
dollar_t, variance_t = MinuteCalibrationHelper.estimate_hybrid_thresholds(
|
|
386
|
+
minutes, 50.0, volatility_threshold=0.01
|
|
387
|
+
)
|
|
388
|
+
seeds = MinuteCalibrationHelper.estimate_ewma_seeds(
|
|
389
|
+
minutes, "run", metric="dollar", span=20.0, target_bars=50
|
|
390
|
+
)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Minute bars work with the polars adapter too:
|
|
394
|
+
`from flowbars.adapters.polars import compute_volume_bars_from_minutes`.
|
|
395
|
+
|
|
396
|
+
## Install
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
pip install flowbars # pure-Python backend
|
|
400
|
+
pip install flowbars[numba] # + numba JIT backend
|
|
401
|
+
pip install flowbars[polars] # + polars DataFrame adapter
|
|
402
|
+
pip install flowbars[calendars] # + exchange_calendars (holidays/DST)
|
|
403
|
+
pip install flowbars[all] # everything
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Quickstart
|
|
407
|
+
|
|
408
|
+
### Build tick bars from the bundled sample data
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from flowbars import load_sample_data, compute_tick_bars
|
|
412
|
+
from flowbars.schema import SchemaMapping
|
|
413
|
+
|
|
414
|
+
df = load_sample_data() # 500 ticks with timestamp, price, volume
|
|
415
|
+
schema = SchemaMapping({"timestamp": "timestamp", "price": "price", "volume": "volume"})
|
|
416
|
+
|
|
417
|
+
bars = compute_tick_bars(df, threshold=50, schema=schema)
|
|
418
|
+
print(bars.head())
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Build dollar bars from your own data
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
import pandas as pd
|
|
425
|
+
from flowbars import compute_dollar_bars
|
|
426
|
+
from flowbars.schema import SchemaMapping
|
|
427
|
+
|
|
428
|
+
ticks = pd.read_csv("my_ticks.csv") # must have ts, px, qty columns
|
|
429
|
+
schema = SchemaMapping({"timestamp": "ts", "price": "px", "volume": "qty"})
|
|
430
|
+
|
|
431
|
+
bars = compute_dollar_bars(ticks, threshold=1_000_000, schema=schema)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Build imbalance bars with adaptive threshold
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
from flowbars import compute_imbalance_tick_bars
|
|
438
|
+
from flowbars.tick_rule import resolve_tick_signs
|
|
439
|
+
|
|
440
|
+
df["side"] = resolve_tick_signs(df["price"].values, None)
|
|
441
|
+
schema = SchemaMapping(
|
|
442
|
+
{
|
|
443
|
+
"timestamp": "ts",
|
|
444
|
+
"price": "px",
|
|
445
|
+
"volume": "qty",
|
|
446
|
+
"side": "side",
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
bars = compute_imbalance_tick_bars(
|
|
451
|
+
df,
|
|
452
|
+
span=20.0, # EWMA span
|
|
453
|
+
warmup_bars=5, # discard first 5 bars while EWMA converges
|
|
454
|
+
schema=schema,
|
|
455
|
+
)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Use the numba backend
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
from flowbars import VolumeBarConstructor
|
|
462
|
+
|
|
463
|
+
ctor = VolumeBarConstructor(
|
|
464
|
+
5_000.0,
|
|
465
|
+
schema=schema,
|
|
466
|
+
backend="numba", # flip this one switch
|
|
467
|
+
)
|
|
468
|
+
bars = ctor.batch(ticks_df)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
All ten tick constructors and all ten `compute_*_bars` functions accept
|
|
472
|
+
`backend="numba"`; minute constructors take the same argument. The Python
|
|
473
|
+
backend remains the default, and `update()` always runs in Python.
|
|
474
|
+
|
|
475
|
+
### Polars adapter
|
|
476
|
+
|
|
477
|
+
```python
|
|
478
|
+
import polars as pl
|
|
479
|
+
from flowbars.adapters.polars import compute_volume_bars
|
|
480
|
+
|
|
481
|
+
pl_ticks = pl.read_csv("ticks.csv")
|
|
482
|
+
pl_bars = compute_volume_bars(pl_ticks, threshold=5000)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Save and resume mid-stream
|
|
486
|
+
|
|
487
|
+
```python
|
|
488
|
+
# Process first batch
|
|
489
|
+
ctor = TickBarConstructor(threshold=100, schema=schema)
|
|
490
|
+
batch1 = ctor.batch(ticks_df.iloc[:1000])
|
|
491
|
+
|
|
492
|
+
# Save state
|
|
493
|
+
state = ctor.get_state()
|
|
494
|
+
|
|
495
|
+
# ... days later, resume with new data ...
|
|
496
|
+
ctor2 = TickBarConstructor.from_state(state, schema=schema)
|
|
497
|
+
batch2 = ctor2.batch(ticks_df.iloc[1000:])
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Check bar quality
|
|
501
|
+
|
|
502
|
+
```python
|
|
503
|
+
from flowbars import bar_quality_report
|
|
504
|
+
|
|
505
|
+
report = bar_quality_report(bars, ljung_box_lags=10)
|
|
506
|
+
print(f"Autocorrelated returns: {report['ljung_box']['reject_autocorrelation']}")
|
|
507
|
+
print(f"Non-normal returns: {report['jarque_bera']['reject_normality']}")
|
|
508
|
+
print(f"Bar-count CV: {report['bar_count_stability']['cv']:.3f}")
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Reproducing benchmarks
|
|
512
|
+
|
|
513
|
+
### Data sourcing
|
|
514
|
+
|
|
515
|
+
The benchmarks use synthetic data generated in-process (seeded random walk
|
|
516
|
+
prices + exponential volumes). No external data files are needed.
|
|
517
|
+
|
|
518
|
+
To benchmark against the same data used in the README table:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
python -m flowbars.benchmarks
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Comparing with mlfinlab / mlfinpy
|
|
525
|
+
|
|
526
|
+
The comparison notebook `notebooks/01_compare_mlfinpy.ipynb` compares flowbars
|
|
527
|
+
against [mlfinpy](https://pypi.org/project/mlfinpy/) — the free, open-source
|
|
528
|
+
reimplementation of the paid MlFinLab — on synthetic tick data (no external
|
|
529
|
+
data files needed). It requires Python ≥ 3.11:
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
conda create -n flowbars311 python=3.11 -y
|
|
533
|
+
conda activate flowbars311
|
|
534
|
+
pip install mlfinpy scikit-learn
|
|
535
|
+
pip install . # from the flowbars repo root
|
|
536
|
+
jupyter nbconvert --to notebook --execute notebooks/01_compare_mlfinpy.ipynb
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
A direct comparison against the paid MlFinLab is deferred; mlfinpy serves as
|
|
540
|
+
the free, drop-in proxy.
|
|
541
|
+
|
|
542
|
+
## Assumptions
|
|
543
|
+
|
|
544
|
+
| Assumption | Detail |
|
|
545
|
+
|---|---|
|
|
546
|
+
| **UTC only** | No timezone conversion or detection. All timestamps are treated as Unix milliseconds in UTC. |
|
|
547
|
+
| **Ticks ordered** | Ticks must be non-decreasing in timestamp. The library does not sort for you — it processes in the order given. Set `strict_ordering=True` to raise on out-of-order input. |
|
|
548
|
+
| **Explicit schema** | You tell the library your column names via `SchemaMapping`. There is no auto-detection. |
|
|
549
|
+
| **Float64 internally** | All prices, volumes, and computed values use `float64`. Float32 input is upcast. |
|
|
550
|
+
| **NaN side = unknown** | A tick with `side=NaN` is treated as undetermined (first tick of a stream). It is included in OHLCV but excluded from signed-imbalance sums. Run-bar logic treats NaN as a wildcard that matches any direction. |
|
|
551
|
+
| **Thread safety** | Bar constructors are **not** thread-safe. Feed ticks from a single thread. |
|
|
552
|
+
| **Batch = streaming** | `ctor.batch(df)` and `ctor.update(tick)` in a loop produce identical output for the same data. |
|
|
553
|
+
|
|
554
|
+
## API reference
|
|
555
|
+
|
|
556
|
+
### Top-level functions
|
|
557
|
+
|
|
558
|
+
| Function | Returns | Description |
|
|
559
|
+
|---|---|---|
|
|
560
|
+
| `compute_tick_bars(df, threshold, ...)` | DataFrame | Tick bars |
|
|
561
|
+
| `compute_volume_bars(df, threshold, ...)` | DataFrame | Volume bars |
|
|
562
|
+
| `compute_dollar_bars(df, threshold, ...)` | DataFrame | Dollar bars |
|
|
563
|
+
| `compute_time_bars(df, interval_ms, ...)` | DataFrame | Time bars |
|
|
564
|
+
| `compute_imbalance_tick_bars(df, ...)` | DataFrame | Tick-imbalance bars |
|
|
565
|
+
| `compute_imbalance_volume_bars(df, ...)` | DataFrame | Volume-imbalance bars |
|
|
566
|
+
| `compute_imbalance_dollar_bars(df, ...)` | DataFrame | Dollar-imbalance bars |
|
|
567
|
+
| `compute_run_tick_bars(df, ...)` | DataFrame | Tick-run bars |
|
|
568
|
+
| `compute_run_volume_bars(df, ...)` | DataFrame | Volume-run bars |
|
|
569
|
+
| `compute_run_dollar_bars(df, ...)` | DataFrame | Dollar-run bars |
|
|
570
|
+
| `load_sample_data()` | DataFrame | Bundled 500-tick sample |
|
|
571
|
+
| `bar_quality_report(bars)` | dict | Statistical diagnostics |
|
|
572
|
+
| `params_hash(params)` | str | Canonical SHA-256 of a params dict |
|
|
573
|
+
|
|
574
|
+
All `compute_*_bars` functions also accept keyword-only `warmup_bars` /
|
|
575
|
+
`warmup_minutes`, `backend`, `stream_id`, `multiplier`, `volume_type`,
|
|
576
|
+
`session_policy`, `session_estimator`, `on_bar`, and `on_threshold_update`.
|
|
577
|
+
The minute equivalents are `compute_*_bars_from_minutes`
|
|
578
|
+
(`compute_volume_bars_from_minutes`, …, `compute_hybrid_bars_from_minutes`)
|
|
579
|
+
with the same keyword surface.
|
|
580
|
+
|
|
581
|
+
### Output contract
|
|
582
|
+
|
|
583
|
+
Every bar — tick or minute — has the same 13 columns: `bar_id`, `open`,
|
|
584
|
+
`high`, `low`, `close`, `volume`, `dollar_value`, `vwap`, `num_ticks`,
|
|
585
|
+
`open_ts`, `close_ts`, `bar_type`, `partial`. All are **core** columns
|
|
586
|
+
(`spec.core_columns`); registered extras, if any, are appended and typed
|
|
587
|
+
(`spec.extras_columns`). `partial` is `True` only for session-boundary
|
|
588
|
+
force-closes. Returned DataFrames carry series metadata in `DataFrame.attrs`
|
|
589
|
+
(`bar_type`, `registry_name`, `params_hash`, `spec_version`, input kind,
|
|
590
|
+
engine/state version, multiplier, volume type, `approximate`). `multiplier`
|
|
591
|
+
scales `dollar_value` (and the dollar/imbalance/run metrics) while `vwap`
|
|
592
|
+
stays a price; `volume_type` (`base`/`quote`/`shares`/`contracts`/`tick`) is
|
|
593
|
+
metadata.
|
|
594
|
+
|
|
595
|
+
Timestamp conventions:
|
|
596
|
+
|
|
597
|
+
* `open_ts` is the timestamp of the bar's first input — the **stable bar
|
|
598
|
+
identity**. It is unique within a series (`validate_bar_frame` enforces
|
|
599
|
+
this). `bar_id` is run-local and resets when you rebuild from source, so
|
|
600
|
+
key downstream rows on `open_ts`, not `bar_id`.
|
|
601
|
+
* `close_ts` is the last input that completed a threshold/time bar, or the
|
|
602
|
+
crossing minute's open timestamp on the minute path.
|
|
603
|
+
* For `partial=True` bars, `close_ts` is the **session boundary** (session
|
|
604
|
+
close or next open), i.e. the instant the bar's information became
|
|
605
|
+
available.
|
|
606
|
+
|
|
607
|
+
Input timestamps must be integer Unix epoch **milliseconds** (integral
|
|
608
|
+
floats are accepted and cast; `datetime64`/tz-aware columns raise
|
|
609
|
+
`SchemaError` with a conversion hint). Pass `validate=True` to
|
|
610
|
+
`batch()` / `update_many()` to run `validate_bar_frame` on the returned frame.
|
|
611
|
+
|
|
612
|
+
### Core classes
|
|
613
|
+
|
|
614
|
+
| Class | Purpose |
|
|
615
|
+
|---|---|
|
|
616
|
+
| `BaseBarConstructor` | Configurable bar construction engine |
|
|
617
|
+
| `SchemaMapping` / `MinuteSchemaMapping` | Explicit column-name mapping |
|
|
618
|
+
| `BarRegistry` / `MinuteBarRegistry` | Discover registered bar types |
|
|
619
|
+
| `BarTypeSpec` | Machine-readable spec (params schema, output columns, capabilities) |
|
|
620
|
+
| `EWMAThresholdEstimator` | Two-component adaptive threshold |
|
|
621
|
+
| `StaticThresholdEstimator` | Fixed threshold |
|
|
622
|
+
| `StaticCalibrationHelper` | Estimate good seeds from data |
|
|
623
|
+
| `ContinuousCalendar` | No session boundaries (24/7) |
|
|
624
|
+
| `WeekdayCalendar` | Mon–Fri, 24/5 |
|
|
625
|
+
| `SessionCalendar` | Fixed daily UTC session hours |
|
|
626
|
+
| `ExchangeCalendar` | Holiday/DST-aware (via `exchange_calendars`) |
|
|
627
|
+
|
|
628
|
+
### Tick rule
|
|
629
|
+
|
|
630
|
+
| Function | Description |
|
|
631
|
+
|---|---|
|
|
632
|
+
| `derive_tick_sign(prices)` | Tick rule on raw prices → side array |
|
|
633
|
+
| `resolve_tick_signs(prices, sides)` | Use supplied sides or derive |
|
|
634
|
+
|
|
635
|
+
## Comparison to other libraries
|
|
636
|
+
|
|
637
|
+
| | flowbars | mlfinlab | mlfinpy |
|
|
638
|
+
|---|---|---|---|
|
|
639
|
+
| **EWMA formula** | Two-component (AFML spec) | Single EWMA | Single EWMA |
|
|
640
|
+
| **Streaming** | `update(tick)` + state save/resume | Batch only | Batch only |
|
|
641
|
+
| **numba backend** | Yes (85×–237×) | Yes | No |
|
|
642
|
+
| **Polars** | First-class adapter | No | No |
|
|
643
|
+
| **Calendars** | Pluggable (continuous, weekday, sessions, exchange-backed) | Hardcoded NYSE | Hardcoded |
|
|
644
|
+
| **Bar quality** | Built-in diagnostics | None | None |
|
|
645
|
+
| **Schema** | Explicit mapping | Auto-inference | Auto-inference |
|
|
646
|
+
| **Benchmarks** | Reproducible, in-repo | None | None |
|
|
647
|
+
|
|
648
|
+
## License
|
|
649
|
+
|
|
650
|
+
MIT — see [LICENSE](https://github.com/syedqaisarjalil/flowbars/blob/main/LICENSE).
|