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,1115 @@
1
+ """Polars adapter — mirror functions for every ``compute_*_bars()``.
2
+
3
+ These functions accept ``polars.DataFrame`` as input and return
4
+ ``polars.DataFrame`` as output. Internally they convert
5
+ polars → pandas, delegate to the core functions, then convert
6
+ the result back to polars.
7
+
8
+ Polars uses Apache Arrow under the hood, so ``to_pandas()`` /
9
+ ``from_pandas()`` are near-zero-copy when pyarrow is installed.
10
+
11
+ Batch-only — no streaming equivalent for polars.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import TYPE_CHECKING, Any, Literal, cast
17
+
18
+ import polars as pl # type: ignore
19
+
20
+ # Import pandas compute functions with _pd_ prefix to avoid
21
+ # shadowing the polars functions defined in this module.
22
+ from flowbars.bars.information import (
23
+ compute_imbalance_dollar_bars as _pd_compute_imbalance_dollar_bars,
24
+ )
25
+ from flowbars.bars.information import (
26
+ compute_imbalance_tick_bars as _pd_compute_imbalance_tick_bars,
27
+ )
28
+ from flowbars.bars.information import (
29
+ compute_imbalance_volume_bars as _pd_compute_imbalance_volume_bars,
30
+ )
31
+ from flowbars.bars.information import (
32
+ compute_run_dollar_bars as _pd_compute_run_dollar_bars,
33
+ )
34
+ from flowbars.bars.information import (
35
+ compute_run_tick_bars as _pd_compute_run_tick_bars,
36
+ )
37
+ from flowbars.bars.information import (
38
+ compute_run_volume_bars as _pd_compute_run_volume_bars,
39
+ )
40
+ from flowbars.bars.minute import (
41
+ compute_dollar_bars_from_minutes as _pd_compute_dollar_bars_from_minutes,
42
+ )
43
+ from flowbars.bars.minute import (
44
+ compute_hybrid_bars_from_minutes as _pd_compute_hybrid_bars_from_minutes,
45
+ )
46
+ from flowbars.bars.minute import (
47
+ compute_imbalance_dollar_bars_from_minutes as _pd_compute_imbalance_dollar_bars_from_minutes,
48
+ )
49
+ from flowbars.bars.minute import (
50
+ compute_imbalance_volume_bars_from_minutes as _pd_compute_imbalance_volume_bars_from_minutes,
51
+ )
52
+ from flowbars.bars.minute import (
53
+ compute_range_bars_from_minutes as _pd_compute_range_bars_from_minutes,
54
+ )
55
+ from flowbars.bars.minute import (
56
+ compute_renko_bars_from_minutes as _pd_compute_renko_bars_from_minutes,
57
+ )
58
+ from flowbars.bars.minute import (
59
+ compute_run_dollar_bars_from_minutes as _pd_compute_run_dollar_bars_from_minutes,
60
+ )
61
+ from flowbars.bars.minute import (
62
+ compute_run_volume_bars_from_minutes as _pd_compute_run_volume_bars_from_minutes,
63
+ )
64
+ from flowbars.bars.minute import (
65
+ compute_volatility_bars_from_minutes as _pd_compute_volatility_bars_from_minutes,
66
+ )
67
+ from flowbars.bars.minute import (
68
+ compute_volume_bars_from_minutes as _pd_compute_volume_bars_from_minutes,
69
+ )
70
+ from flowbars.bars.standard import (
71
+ compute_dollar_bars as _pd_compute_dollar_bars,
72
+ )
73
+ from flowbars.bars.standard import (
74
+ compute_tick_bars as _pd_compute_tick_bars,
75
+ )
76
+ from flowbars.bars.standard import (
77
+ compute_time_bars as _pd_compute_time_bars,
78
+ )
79
+ from flowbars.bars.standard import (
80
+ compute_volume_bars as _pd_compute_volume_bars,
81
+ )
82
+ from flowbars.calendars import TradingCalendar
83
+ from flowbars.schema import MinuteSchemaMapping, SchemaMapping
84
+
85
+ if TYPE_CHECKING:
86
+ pass
87
+
88
+ __all__ = [
89
+ "compute_dollar_bars",
90
+ "compute_dollar_bars_from_minutes",
91
+ "compute_hybrid_bars_from_minutes",
92
+ "compute_imbalance_dollar_bars",
93
+ "compute_imbalance_dollar_bars_from_minutes",
94
+ "compute_imbalance_tick_bars",
95
+ "compute_imbalance_volume_bars",
96
+ "compute_imbalance_volume_bars_from_minutes",
97
+ "compute_range_bars_from_minutes",
98
+ "compute_renko_bars_from_minutes",
99
+ "compute_run_dollar_bars",
100
+ "compute_run_dollar_bars_from_minutes",
101
+ "compute_run_tick_bars",
102
+ "compute_run_volume_bars",
103
+ "compute_run_volume_bars_from_minutes",
104
+ "compute_tick_bars",
105
+ "compute_time_bars",
106
+ "compute_volatility_bars_from_minutes",
107
+ "compute_volume_bars",
108
+ "compute_volume_bars_from_minutes",
109
+ ]
110
+
111
+ # ═══════════════════════════════════════════════════════════════════════════════
112
+ # Standard bars
113
+ # ═══════════════════════════════════════════════════════════════════════════════
114
+
115
+
116
+ def compute_tick_bars(
117
+ ticks_df: pl.DataFrame,
118
+ threshold: int,
119
+ schema: SchemaMapping | None = None,
120
+ watermark: str | None = "timestamp",
121
+ strict_ordering: bool = False,
122
+ calendar: TradingCalendar | None = None,
123
+ multiplier: float = 1.0,
124
+ volume_type: str = "base",
125
+ warmup_bars: int = 0,
126
+ warmup_minutes: int | None = None,
127
+ ) -> pl.DataFrame:
128
+ """Build tick bars from a Polars DataFrame of ticks.
129
+
130
+ Parameters
131
+ ----------
132
+ ticks_df : pl.DataFrame
133
+ Tick-level data with user-mapped columns.
134
+ threshold : int
135
+ Number of ticks per bar. Must be positive.
136
+ schema : SchemaMapping, optional
137
+ Maps user column names to internal keys.
138
+ calendar : TradingCalendar, optional
139
+
140
+ Returns
141
+ -------
142
+ pl.DataFrame
143
+ Completed bars.
144
+ """
145
+ pd_ticks = ticks_df.to_pandas()
146
+ pd_bars = _pd_compute_tick_bars(
147
+ pd_ticks,
148
+ threshold=threshold,
149
+ schema=schema,
150
+ calendar=calendar,
151
+ watermark=watermark,
152
+ strict_ordering=strict_ordering,
153
+ multiplier=multiplier,
154
+ volume_type=volume_type,
155
+ warmup_bars=warmup_bars,
156
+ warmup_minutes=warmup_minutes,
157
+ )
158
+ return pl.from_pandas(pd_bars)
159
+
160
+
161
+ def compute_volume_bars(
162
+ ticks_df: pl.DataFrame,
163
+ threshold: float,
164
+ schema: SchemaMapping | None = None,
165
+ watermark: str | None = "timestamp",
166
+ strict_ordering: bool = False,
167
+ calendar: TradingCalendar | None = None,
168
+ multiplier: float = 1.0,
169
+ volume_type: str = "base",
170
+ warmup_bars: int = 0,
171
+ warmup_minutes: int | None = None,
172
+ ) -> pl.DataFrame:
173
+ """Build volume bars from a Polars DataFrame of ticks.
174
+
175
+ Parameters
176
+ ----------
177
+ ticks_df : pl.DataFrame
178
+ Tick-level data with user-mapped columns.
179
+ threshold : float
180
+ Volume threshold per bar.
181
+ schema : SchemaMapping, optional
182
+ calendar : TradingCalendar, optional
183
+
184
+ Returns
185
+ -------
186
+ pl.DataFrame
187
+ Completed bars.
188
+ """
189
+ pd_ticks = ticks_df.to_pandas()
190
+ pd_bars = _pd_compute_volume_bars(
191
+ pd_ticks,
192
+ threshold=threshold,
193
+ schema=schema,
194
+ calendar=calendar,
195
+ watermark=watermark,
196
+ strict_ordering=strict_ordering,
197
+ multiplier=multiplier,
198
+ volume_type=volume_type,
199
+ warmup_bars=warmup_bars,
200
+ warmup_minutes=warmup_minutes,
201
+ )
202
+ return pl.from_pandas(pd_bars)
203
+
204
+
205
+ def compute_dollar_bars(
206
+ ticks_df: pl.DataFrame,
207
+ threshold: float,
208
+ schema: SchemaMapping | None = None,
209
+ watermark: str | None = "timestamp",
210
+ strict_ordering: bool = False,
211
+ calendar: TradingCalendar | None = None,
212
+ multiplier: float = 1.0,
213
+ volume_type: str = "base",
214
+ warmup_bars: int = 0,
215
+ warmup_minutes: int | None = None,
216
+ ) -> pl.DataFrame:
217
+ """Build dollar bars from a Polars DataFrame of ticks.
218
+
219
+ Parameters
220
+ ----------
221
+ ticks_df : pl.DataFrame
222
+ Tick-level data with user-mapped columns.
223
+ threshold : float
224
+ Notional-value threshold per bar (e.g. 1_000_000.0 for $1M bars).
225
+ schema : SchemaMapping, optional
226
+ calendar : TradingCalendar, optional
227
+
228
+ Returns
229
+ -------
230
+ pl.DataFrame
231
+ Completed bars.
232
+ """
233
+ pd_ticks = ticks_df.to_pandas()
234
+ pd_bars = _pd_compute_dollar_bars(
235
+ pd_ticks,
236
+ threshold=threshold,
237
+ schema=schema,
238
+ calendar=calendar,
239
+ watermark=watermark,
240
+ strict_ordering=strict_ordering,
241
+ multiplier=multiplier,
242
+ volume_type=volume_type,
243
+ warmup_bars=warmup_bars,
244
+ warmup_minutes=warmup_minutes,
245
+ )
246
+ return pl.from_pandas(pd_bars)
247
+
248
+
249
+ def compute_time_bars(
250
+ ticks_df: pl.DataFrame,
251
+ interval_ms: int,
252
+ anchor: str = "clock",
253
+ schema: SchemaMapping | None = None,
254
+ watermark: str | None = "timestamp",
255
+ strict_ordering: bool = False,
256
+ calendar: TradingCalendar | None = None,
257
+ multiplier: float = 1.0,
258
+ volume_type: str = "base",
259
+ warmup_bars: int = 0,
260
+ warmup_minutes: int | None = None,
261
+ ) -> pl.DataFrame:
262
+ """Build time bars from a Polars DataFrame of ticks.
263
+
264
+ Parameters
265
+ ----------
266
+ ticks_df : pl.DataFrame
267
+ Tick-level data with user-mapped columns.
268
+ interval_ms : int
269
+ Bar interval in milliseconds.
270
+ anchor : str, default ``"clock"``
271
+ ``"clock"`` or ``"first_tick"``.
272
+ schema : SchemaMapping, optional
273
+ calendar : TradingCalendar, optional
274
+
275
+ Returns
276
+ -------
277
+ pl.DataFrame
278
+ Completed bars.
279
+ """
280
+ pd_ticks = ticks_df.to_pandas()
281
+ pd_bars = _pd_compute_time_bars(
282
+ pd_ticks,
283
+ interval_ms=interval_ms,
284
+ anchor=anchor,
285
+ schema=schema,
286
+ calendar=calendar,
287
+ watermark=watermark,
288
+ strict_ordering=strict_ordering,
289
+ multiplier=multiplier,
290
+ volume_type=volume_type,
291
+ warmup_bars=warmup_bars,
292
+ warmup_minutes=warmup_minutes,
293
+ )
294
+ return pl.from_pandas(pd_bars)
295
+
296
+
297
+ # ═══════════════════════════════════════════════════════════════════════════════
298
+ # Information-driven bars — imbalance
299
+ # ═══════════════════════════════════════════════════════════════════════════════
300
+
301
+
302
+ def compute_imbalance_tick_bars(
303
+ ticks_df: pl.DataFrame,
304
+ span: float = 20.0,
305
+ halflife: float | None = None,
306
+ initial_ewa_t: float = 1.0,
307
+ initial_ewa_proportion: float = 0.5,
308
+ warmup_bars: int = 0,
309
+ warmup_minutes: int | None = None,
310
+ schema: SchemaMapping | None = None,
311
+ watermark: str | None = "timestamp",
312
+ strict_ordering: bool = False,
313
+ calendar: TradingCalendar | None = None,
314
+ multiplier: float = 1.0,
315
+ volume_type: str = "base",
316
+ ) -> pl.DataFrame:
317
+ """Build tick-imbalance bars from a Polars DataFrame of ticks.
318
+
319
+ Parameters
320
+ ----------
321
+ ticks_df : pl.DataFrame
322
+ Tick-level data with user-mapped columns.
323
+ span : float, default 20.0
324
+ EWMA span for the adaptive threshold.
325
+ halflife : float or None, default None
326
+ EWMA halflife (takes precedence over *span*).
327
+ initial_ewa_t : float, default 1.0
328
+ Initial seed for expected tick count per bar.
329
+ initial_ewa_proportion : float, default 0.5
330
+ Initial seed for expected imbalance proportion.
331
+ warmup_bars : int, default 0
332
+ Number of initial bars to discard.
333
+ schema : SchemaMapping, optional
334
+ calendar : TradingCalendar, optional
335
+
336
+ Returns
337
+ -------
338
+ pl.DataFrame
339
+ Completed bars.
340
+ """
341
+ pd_ticks = ticks_df.to_pandas()
342
+ pd_bars = _pd_compute_imbalance_tick_bars(
343
+ pd_ticks,
344
+ span=span,
345
+ halflife=halflife,
346
+ initial_ewa_t=initial_ewa_t,
347
+ initial_ewa_proportion=initial_ewa_proportion,
348
+ warmup_bars=warmup_bars,
349
+ warmup_minutes=warmup_minutes,
350
+ schema=schema,
351
+ calendar=calendar,
352
+ watermark=watermark,
353
+ strict_ordering=strict_ordering,
354
+ multiplier=multiplier,
355
+ volume_type=volume_type,
356
+ )
357
+ return pl.from_pandas(pd_bars)
358
+
359
+
360
+ def compute_imbalance_volume_bars(
361
+ ticks_df: pl.DataFrame,
362
+ span: float = 20.0,
363
+ halflife: float | None = None,
364
+ initial_ewa_t: float = 1.0,
365
+ initial_ewa_proportion: float = 0.5,
366
+ warmup_bars: int = 0,
367
+ warmup_minutes: int | None = None,
368
+ schema: SchemaMapping | None = None,
369
+ watermark: str | None = "timestamp",
370
+ strict_ordering: bool = False,
371
+ calendar: TradingCalendar | None = None,
372
+ multiplier: float = 1.0,
373
+ volume_type: str = "base",
374
+ ) -> pl.DataFrame:
375
+ """Build volume-imbalance bars from a Polars DataFrame of ticks.
376
+
377
+ Parameters
378
+ ----------
379
+ ticks_df : pl.DataFrame
380
+ Tick-level data with user-mapped columns.
381
+ span : float, default 20.0
382
+ EWMA span for the adaptive threshold.
383
+ halflife : float or None, default None
384
+ EWMA halflife (takes precedence over *span*).
385
+ initial_ewa_t : float, default 1.0
386
+ Initial seed for expected volume per bar.
387
+ initial_ewa_proportion : float, default 0.5
388
+ Initial seed for expected imbalance proportion.
389
+ warmup_bars : int, default 0
390
+ Number of initial bars to discard.
391
+ schema : SchemaMapping, optional
392
+ calendar : TradingCalendar, optional
393
+
394
+ Returns
395
+ -------
396
+ pl.DataFrame
397
+ Completed bars.
398
+ """
399
+ pd_ticks = ticks_df.to_pandas()
400
+ pd_bars = _pd_compute_imbalance_volume_bars(
401
+ pd_ticks,
402
+ span=span,
403
+ halflife=halflife,
404
+ initial_ewa_t=initial_ewa_t,
405
+ initial_ewa_proportion=initial_ewa_proportion,
406
+ warmup_bars=warmup_bars,
407
+ warmup_minutes=warmup_minutes,
408
+ schema=schema,
409
+ calendar=calendar,
410
+ watermark=watermark,
411
+ strict_ordering=strict_ordering,
412
+ multiplier=multiplier,
413
+ volume_type=volume_type,
414
+ )
415
+ return pl.from_pandas(pd_bars)
416
+
417
+
418
+ def compute_imbalance_dollar_bars(
419
+ ticks_df: pl.DataFrame,
420
+ span: float = 20.0,
421
+ halflife: float | None = None,
422
+ initial_ewa_t: float = 1.0,
423
+ initial_ewa_proportion: float = 0.5,
424
+ warmup_bars: int = 0,
425
+ warmup_minutes: int | None = None,
426
+ schema: SchemaMapping | None = None,
427
+ watermark: str | None = "timestamp",
428
+ strict_ordering: bool = False,
429
+ calendar: TradingCalendar | None = None,
430
+ multiplier: float = 1.0,
431
+ volume_type: str = "base",
432
+ ) -> pl.DataFrame:
433
+ """Build dollar-imbalance bars from a Polars DataFrame of ticks.
434
+
435
+ Parameters
436
+ ----------
437
+ ticks_df : pl.DataFrame
438
+ Tick-level data with user-mapped columns.
439
+ span : float, default 20.0
440
+ EWMA span for the adaptive threshold.
441
+ halflife : float or None, default None
442
+ EWMA halflife (takes precedence over *span*).
443
+ initial_ewa_t : float, default 1.0
444
+ Initial seed for expected dollar value per bar.
445
+ initial_ewa_proportion : float, default 0.5
446
+ Initial seed for expected imbalance proportion.
447
+ warmup_bars : int, default 0
448
+ Number of initial bars to discard.
449
+ schema : SchemaMapping, optional
450
+ calendar : TradingCalendar, optional
451
+
452
+ Returns
453
+ -------
454
+ pl.DataFrame
455
+ Completed bars.
456
+ """
457
+ pd_ticks = ticks_df.to_pandas()
458
+ pd_bars = _pd_compute_imbalance_dollar_bars(
459
+ pd_ticks,
460
+ span=span,
461
+ halflife=halflife,
462
+ initial_ewa_t=initial_ewa_t,
463
+ initial_ewa_proportion=initial_ewa_proportion,
464
+ warmup_bars=warmup_bars,
465
+ warmup_minutes=warmup_minutes,
466
+ schema=schema,
467
+ calendar=calendar,
468
+ watermark=watermark,
469
+ strict_ordering=strict_ordering,
470
+ multiplier=multiplier,
471
+ volume_type=volume_type,
472
+ )
473
+ return pl.from_pandas(pd_bars)
474
+
475
+
476
+ # ═══════════════════════════════════════════════════════════════════════════════
477
+ # Information-driven bars — run
478
+ # ═══════════════════════════════════════════════════════════════════════════════
479
+
480
+
481
+ def compute_run_tick_bars(
482
+ ticks_df: pl.DataFrame,
483
+ span: float = 20.0,
484
+ halflife: float | None = None,
485
+ initial_ewa_t: float = 1.0,
486
+ initial_ewa_proportion: float = 0.5,
487
+ warmup_bars: int = 0,
488
+ warmup_minutes: int | None = None,
489
+ min_run_length: int = 0,
490
+ schema: SchemaMapping | None = None,
491
+ watermark: str | None = "timestamp",
492
+ strict_ordering: bool = False,
493
+ calendar: TradingCalendar | None = None,
494
+ multiplier: float = 1.0,
495
+ volume_type: str = "base",
496
+ ) -> pl.DataFrame:
497
+ """Build tick-run bars from a Polars DataFrame of ticks.
498
+
499
+ Parameters
500
+ ----------
501
+ ticks_df : pl.DataFrame
502
+ Tick-level data with user-mapped columns.
503
+ span : float, default 20.0
504
+ EWMA span for the adaptive threshold.
505
+ halflife : float or None, default None
506
+ EWMA halflife (takes precedence over *span*).
507
+ initial_ewa_t : float, default 1.0
508
+ Initial seed for expected run length per bar.
509
+ initial_ewa_proportion : float, default 0.5
510
+ Initial seed for expected buy-run proportion.
511
+ warmup_bars : int, default 0
512
+ Number of initial bars to discard.
513
+ min_run_length : int, default 0
514
+ Minimum ticks per bar to return.
515
+ schema : SchemaMapping, optional
516
+ calendar : TradingCalendar, optional
517
+
518
+ Returns
519
+ -------
520
+ pl.DataFrame
521
+ Completed bars.
522
+ """
523
+ pd_ticks = ticks_df.to_pandas()
524
+ pd_bars = _pd_compute_run_tick_bars(
525
+ pd_ticks,
526
+ span=span,
527
+ halflife=halflife,
528
+ initial_ewa_t=initial_ewa_t,
529
+ initial_ewa_proportion=initial_ewa_proportion,
530
+ warmup_bars=warmup_bars,
531
+ warmup_minutes=warmup_minutes,
532
+ min_run_length=min_run_length,
533
+ schema=schema,
534
+ calendar=calendar,
535
+ watermark=watermark,
536
+ strict_ordering=strict_ordering,
537
+ multiplier=multiplier,
538
+ volume_type=volume_type,
539
+ )
540
+ return pl.from_pandas(pd_bars)
541
+
542
+
543
+ def compute_run_volume_bars(
544
+ ticks_df: pl.DataFrame,
545
+ span: float = 20.0,
546
+ halflife: float | None = None,
547
+ initial_ewa_t: float = 1.0,
548
+ initial_ewa_proportion: float = 0.5,
549
+ warmup_bars: int = 0,
550
+ warmup_minutes: int | None = None,
551
+ min_run_length: int = 0,
552
+ schema: SchemaMapping | None = None,
553
+ watermark: str | None = "timestamp",
554
+ strict_ordering: bool = False,
555
+ calendar: TradingCalendar | None = None,
556
+ multiplier: float = 1.0,
557
+ volume_type: str = "base",
558
+ ) -> pl.DataFrame:
559
+ """Build volume-run bars from a Polars DataFrame of ticks.
560
+
561
+ Parameters
562
+ ----------
563
+ ticks_df : pl.DataFrame
564
+ Tick-level data with user-mapped columns.
565
+ span : float, default 20.0
566
+ EWMA span for the adaptive threshold.
567
+ halflife : float or None, default None
568
+ EWMA halflife (takes precedence over *span*).
569
+ initial_ewa_t : float, default 1.0
570
+ Initial seed for expected run length per bar.
571
+ initial_ewa_proportion : float, default 0.5
572
+ Initial seed for expected buy-run proportion.
573
+ warmup_bars : int, default 0
574
+ Number of initial bars to discard.
575
+ min_run_length : int, default 0
576
+ Minimum ticks per bar to return.
577
+ schema : SchemaMapping, optional
578
+ calendar : TradingCalendar, optional
579
+
580
+ Returns
581
+ -------
582
+ pl.DataFrame
583
+ Completed bars.
584
+ """
585
+ pd_ticks = ticks_df.to_pandas()
586
+ pd_bars = _pd_compute_run_volume_bars(
587
+ pd_ticks,
588
+ span=span,
589
+ halflife=halflife,
590
+ initial_ewa_t=initial_ewa_t,
591
+ initial_ewa_proportion=initial_ewa_proportion,
592
+ warmup_bars=warmup_bars,
593
+ warmup_minutes=warmup_minutes,
594
+ min_run_length=min_run_length,
595
+ schema=schema,
596
+ calendar=calendar,
597
+ watermark=watermark,
598
+ strict_ordering=strict_ordering,
599
+ multiplier=multiplier,
600
+ volume_type=volume_type,
601
+ )
602
+ return pl.from_pandas(pd_bars)
603
+
604
+
605
+ def compute_run_dollar_bars(
606
+ ticks_df: pl.DataFrame,
607
+ span: float = 20.0,
608
+ halflife: float | None = None,
609
+ initial_ewa_t: float = 1.0,
610
+ initial_ewa_proportion: float = 0.5,
611
+ warmup_bars: int = 0,
612
+ warmup_minutes: int | None = None,
613
+ min_run_length: int = 0,
614
+ schema: SchemaMapping | None = None,
615
+ watermark: str | None = "timestamp",
616
+ strict_ordering: bool = False,
617
+ calendar: TradingCalendar | None = None,
618
+ multiplier: float = 1.0,
619
+ volume_type: str = "base",
620
+ ) -> pl.DataFrame:
621
+ """Build dollar-run bars from a Polars DataFrame of ticks.
622
+
623
+ Parameters
624
+ ----------
625
+ ticks_df : pl.DataFrame
626
+ Tick-level data with user-mapped columns.
627
+ span : float, default 20.0
628
+ EWMA span for the adaptive threshold.
629
+ halflife : float or None, default None
630
+ EWMA halflife (takes precedence over *span*).
631
+ initial_ewa_t : float, default 1.0
632
+ Initial seed for expected run length per bar.
633
+ initial_ewa_proportion : float, default 0.5
634
+ Initial seed for expected buy-run proportion.
635
+ warmup_bars : int, default 0
636
+ Number of initial bars to discard.
637
+ min_run_length : int, default 0
638
+ Minimum ticks per bar to return.
639
+ schema : SchemaMapping, optional
640
+ calendar : TradingCalendar, optional
641
+
642
+ Returns
643
+ -------
644
+ pl.DataFrame
645
+ Completed bars.
646
+ """
647
+ pd_ticks = ticks_df.to_pandas()
648
+ pd_bars = _pd_compute_run_dollar_bars(
649
+ pd_ticks,
650
+ span=span,
651
+ halflife=halflife,
652
+ initial_ewa_t=initial_ewa_t,
653
+ initial_ewa_proportion=initial_ewa_proportion,
654
+ warmup_bars=warmup_bars,
655
+ warmup_minutes=warmup_minutes,
656
+ min_run_length=min_run_length,
657
+ schema=schema,
658
+ calendar=calendar,
659
+ watermark=watermark,
660
+ strict_ordering=strict_ordering,
661
+ multiplier=multiplier,
662
+ volume_type=volume_type,
663
+ )
664
+ return pl.from_pandas(pd_bars)
665
+
666
+
667
+ # ═══════════════════════════════════════════════════════════════════════════════
668
+ # Minute bars
669
+ # ═══════════════════════════════════════════════════════════════════════════════
670
+
671
+
672
+ def _minute_call(fn: Any, minutes_df: pl.DataFrame, *args: Any, **kwargs: Any) -> pl.DataFrame:
673
+ """Convert, delegate to the pandas minute compute function, convert back."""
674
+ pd_bars = fn(minutes_df.to_pandas(), *args, **kwargs)
675
+ return cast(pl.DataFrame, pl.from_pandas(pd_bars))
676
+
677
+
678
+ def compute_volume_bars_from_minutes(
679
+ minutes_df: pl.DataFrame,
680
+ threshold: float,
681
+ schema: MinuteSchemaMapping | None = None,
682
+ *,
683
+ warmup_bars: int = 0,
684
+ warmup_minutes: int | None = None,
685
+ backend: str = "python",
686
+ watermark: str | None = "timestamp",
687
+ strict_ordering: bool = False,
688
+ calendar: TradingCalendar | None = None,
689
+ stream_id: str = "default",
690
+ on_bar: Any = None,
691
+ on_threshold_update: Any = None,
692
+ multiplier: float = 1.0,
693
+ volume_type: str = "base",
694
+ session_policy: Literal["close", "carry", "discard"] = "close",
695
+ session_estimator: Literal["persist", "reset"] = "persist",
696
+ ) -> pl.DataFrame:
697
+ """Build volume bars from a Polars DataFrame of minute OHLCV candles."""
698
+ return _minute_call(
699
+ _pd_compute_volume_bars_from_minutes,
700
+ minutes_df,
701
+ threshold,
702
+ schema,
703
+ warmup_bars=warmup_bars,
704
+ warmup_minutes=warmup_minutes,
705
+ backend=backend,
706
+ watermark=watermark,
707
+ strict_ordering=strict_ordering,
708
+ calendar=calendar,
709
+ stream_id=stream_id,
710
+ on_bar=on_bar,
711
+ on_threshold_update=on_threshold_update,
712
+ multiplier=multiplier,
713
+ volume_type=volume_type,
714
+ session_policy=session_policy,
715
+ session_estimator=session_estimator,
716
+ )
717
+
718
+
719
+ def compute_dollar_bars_from_minutes(
720
+ minutes_df: pl.DataFrame,
721
+ threshold: float,
722
+ schema: MinuteSchemaMapping | None = None,
723
+ *,
724
+ warmup_bars: int = 0,
725
+ warmup_minutes: int | None = None,
726
+ backend: str = "python",
727
+ watermark: str | None = "timestamp",
728
+ strict_ordering: bool = False,
729
+ calendar: TradingCalendar | None = None,
730
+ stream_id: str = "default",
731
+ on_bar: Any = None,
732
+ on_threshold_update: Any = None,
733
+ multiplier: float = 1.0,
734
+ volume_type: str = "base",
735
+ session_policy: Literal["close", "carry", "discard"] = "close",
736
+ session_estimator: Literal["persist", "reset"] = "persist",
737
+ ) -> pl.DataFrame:
738
+ """Build dollar bars from a Polars DataFrame of minute OHLCV candles."""
739
+ return _minute_call(
740
+ _pd_compute_dollar_bars_from_minutes,
741
+ minutes_df,
742
+ threshold,
743
+ schema,
744
+ warmup_bars=warmup_bars,
745
+ warmup_minutes=warmup_minutes,
746
+ backend=backend,
747
+ watermark=watermark,
748
+ strict_ordering=strict_ordering,
749
+ calendar=calendar,
750
+ stream_id=stream_id,
751
+ on_bar=on_bar,
752
+ on_threshold_update=on_threshold_update,
753
+ multiplier=multiplier,
754
+ volume_type=volume_type,
755
+ session_policy=session_policy,
756
+ session_estimator=session_estimator,
757
+ )
758
+
759
+
760
+ def compute_imbalance_volume_bars_from_minutes(
761
+ minutes_df: pl.DataFrame,
762
+ span: float = 20.0,
763
+ halflife: float | None = None,
764
+ initial_ewa_t: float = 1.0,
765
+ initial_ewa_proportion: float = 0.5,
766
+ warmup_bars: int = 0,
767
+ warmup_minutes: int | None = None,
768
+ schema: MinuteSchemaMapping | None = None,
769
+ *,
770
+ backend: str = "python",
771
+ watermark: str | None = "timestamp",
772
+ strict_ordering: bool = False,
773
+ calendar: TradingCalendar | None = None,
774
+ stream_id: str = "default",
775
+ on_bar: Any = None,
776
+ on_threshold_update: Any = None,
777
+ multiplier: float = 1.0,
778
+ volume_type: str = "base",
779
+ session_policy: Literal["close", "carry", "discard"] = "close",
780
+ session_estimator: Literal["persist", "reset"] = "persist",
781
+ ) -> pl.DataFrame:
782
+ """Build volume-imbalance bars from a Polars DataFrame of minute candles."""
783
+ return _minute_call(
784
+ _pd_compute_imbalance_volume_bars_from_minutes,
785
+ minutes_df,
786
+ span,
787
+ halflife,
788
+ initial_ewa_t,
789
+ initial_ewa_proportion,
790
+ warmup_bars,
791
+ warmup_minutes,
792
+ schema,
793
+ backend=backend,
794
+ watermark=watermark,
795
+ strict_ordering=strict_ordering,
796
+ calendar=calendar,
797
+ stream_id=stream_id,
798
+ on_bar=on_bar,
799
+ on_threshold_update=on_threshold_update,
800
+ multiplier=multiplier,
801
+ volume_type=volume_type,
802
+ session_policy=session_policy,
803
+ session_estimator=session_estimator,
804
+ )
805
+
806
+
807
+ def compute_imbalance_dollar_bars_from_minutes(
808
+ minutes_df: pl.DataFrame,
809
+ span: float = 20.0,
810
+ halflife: float | None = None,
811
+ initial_ewa_t: float = 1.0,
812
+ initial_ewa_proportion: float = 0.5,
813
+ warmup_bars: int = 0,
814
+ warmup_minutes: int | None = None,
815
+ schema: MinuteSchemaMapping | None = None,
816
+ *,
817
+ backend: str = "python",
818
+ watermark: str | None = "timestamp",
819
+ strict_ordering: bool = False,
820
+ calendar: TradingCalendar | None = None,
821
+ stream_id: str = "default",
822
+ on_bar: Any = None,
823
+ on_threshold_update: Any = None,
824
+ multiplier: float = 1.0,
825
+ volume_type: str = "base",
826
+ session_policy: Literal["close", "carry", "discard"] = "close",
827
+ session_estimator: Literal["persist", "reset"] = "persist",
828
+ ) -> pl.DataFrame:
829
+ """Build dollar-imbalance bars from a Polars DataFrame of minute candles."""
830
+ return _minute_call(
831
+ _pd_compute_imbalance_dollar_bars_from_minutes,
832
+ minutes_df,
833
+ span,
834
+ halflife,
835
+ initial_ewa_t,
836
+ initial_ewa_proportion,
837
+ warmup_bars,
838
+ warmup_minutes,
839
+ schema,
840
+ backend=backend,
841
+ watermark=watermark,
842
+ strict_ordering=strict_ordering,
843
+ calendar=calendar,
844
+ stream_id=stream_id,
845
+ on_bar=on_bar,
846
+ on_threshold_update=on_threshold_update,
847
+ multiplier=multiplier,
848
+ volume_type=volume_type,
849
+ session_policy=session_policy,
850
+ session_estimator=session_estimator,
851
+ )
852
+
853
+
854
+ def compute_run_volume_bars_from_minutes(
855
+ minutes_df: pl.DataFrame,
856
+ span: float = 20.0,
857
+ halflife: float | None = None,
858
+ initial_ewa_t: float = 1.0,
859
+ initial_ewa_proportion: float = 0.5,
860
+ warmup_bars: int = 0,
861
+ warmup_minutes: int | None = None,
862
+ min_run_length: int = 0,
863
+ schema: MinuteSchemaMapping | None = None,
864
+ *,
865
+ backend: str = "python",
866
+ watermark: str | None = "timestamp",
867
+ strict_ordering: bool = False,
868
+ calendar: TradingCalendar | None = None,
869
+ stream_id: str = "default",
870
+ on_bar: Any = None,
871
+ on_threshold_update: Any = None,
872
+ multiplier: float = 1.0,
873
+ volume_type: str = "base",
874
+ session_policy: Literal["close", "carry", "discard"] = "close",
875
+ session_estimator: Literal["persist", "reset"] = "persist",
876
+ ) -> pl.DataFrame:
877
+ """Build volume-run bars from a Polars DataFrame of minute candles."""
878
+ return _minute_call(
879
+ _pd_compute_run_volume_bars_from_minutes,
880
+ minutes_df,
881
+ span,
882
+ halflife,
883
+ initial_ewa_t,
884
+ initial_ewa_proportion,
885
+ warmup_bars,
886
+ warmup_minutes,
887
+ min_run_length,
888
+ schema,
889
+ backend=backend,
890
+ watermark=watermark,
891
+ strict_ordering=strict_ordering,
892
+ calendar=calendar,
893
+ stream_id=stream_id,
894
+ on_bar=on_bar,
895
+ on_threshold_update=on_threshold_update,
896
+ multiplier=multiplier,
897
+ volume_type=volume_type,
898
+ session_policy=session_policy,
899
+ session_estimator=session_estimator,
900
+ )
901
+
902
+
903
+ def compute_run_dollar_bars_from_minutes(
904
+ minutes_df: pl.DataFrame,
905
+ span: float = 20.0,
906
+ halflife: float | None = None,
907
+ initial_ewa_t: float = 1.0,
908
+ initial_ewa_proportion: float = 0.5,
909
+ warmup_bars: int = 0,
910
+ warmup_minutes: int | None = None,
911
+ min_run_length: int = 0,
912
+ schema: MinuteSchemaMapping | None = None,
913
+ *,
914
+ backend: str = "python",
915
+ watermark: str | None = "timestamp",
916
+ strict_ordering: bool = False,
917
+ calendar: TradingCalendar | None = None,
918
+ stream_id: str = "default",
919
+ on_bar: Any = None,
920
+ on_threshold_update: Any = None,
921
+ multiplier: float = 1.0,
922
+ volume_type: str = "base",
923
+ session_policy: Literal["close", "carry", "discard"] = "close",
924
+ session_estimator: Literal["persist", "reset"] = "persist",
925
+ ) -> pl.DataFrame:
926
+ """Build dollar-run bars from a Polars DataFrame of minute candles."""
927
+ return _minute_call(
928
+ _pd_compute_run_dollar_bars_from_minutes,
929
+ minutes_df,
930
+ span,
931
+ halflife,
932
+ initial_ewa_t,
933
+ initial_ewa_proportion,
934
+ warmup_bars,
935
+ warmup_minutes,
936
+ min_run_length,
937
+ schema,
938
+ backend=backend,
939
+ watermark=watermark,
940
+ strict_ordering=strict_ordering,
941
+ calendar=calendar,
942
+ stream_id=stream_id,
943
+ on_bar=on_bar,
944
+ on_threshold_update=on_threshold_update,
945
+ multiplier=multiplier,
946
+ volume_type=volume_type,
947
+ session_policy=session_policy,
948
+ session_estimator=session_estimator,
949
+ )
950
+
951
+
952
+ def compute_volatility_bars_from_minutes(
953
+ minutes_df: pl.DataFrame,
954
+ threshold: float,
955
+ schema: MinuteSchemaMapping | None = None,
956
+ *,
957
+ warmup_bars: int = 0,
958
+ warmup_minutes: int | None = None,
959
+ backend: str = "python",
960
+ watermark: str | None = "timestamp",
961
+ strict_ordering: bool = False,
962
+ calendar: TradingCalendar | None = None,
963
+ stream_id: str = "default",
964
+ on_bar: Any = None,
965
+ on_threshold_update: Any = None,
966
+ multiplier: float = 1.0,
967
+ volume_type: str = "base",
968
+ session_policy: Literal["close", "carry", "discard"] = "close",
969
+ session_estimator: Literal["persist", "reset"] = "persist",
970
+ ) -> pl.DataFrame:
971
+ """Build volatility bars from a Polars DataFrame of minute candles."""
972
+ return _minute_call(
973
+ _pd_compute_volatility_bars_from_minutes,
974
+ minutes_df,
975
+ threshold,
976
+ schema,
977
+ warmup_bars=warmup_bars,
978
+ warmup_minutes=warmup_minutes,
979
+ backend=backend,
980
+ watermark=watermark,
981
+ strict_ordering=strict_ordering,
982
+ calendar=calendar,
983
+ stream_id=stream_id,
984
+ on_bar=on_bar,
985
+ on_threshold_update=on_threshold_update,
986
+ multiplier=multiplier,
987
+ volume_type=volume_type,
988
+ session_policy=session_policy,
989
+ session_estimator=session_estimator,
990
+ )
991
+
992
+
993
+ def compute_range_bars_from_minutes(
994
+ minutes_df: pl.DataFrame,
995
+ threshold: float,
996
+ schema: MinuteSchemaMapping | None = None,
997
+ *,
998
+ warmup_bars: int = 0,
999
+ warmup_minutes: int | None = None,
1000
+ backend: str = "python",
1001
+ watermark: str | None = "timestamp",
1002
+ strict_ordering: bool = False,
1003
+ calendar: TradingCalendar | None = None,
1004
+ stream_id: str = "default",
1005
+ on_bar: Any = None,
1006
+ on_threshold_update: Any = None,
1007
+ multiplier: float = 1.0,
1008
+ volume_type: str = "base",
1009
+ session_policy: Literal["close", "carry", "discard"] = "close",
1010
+ session_estimator: Literal["persist", "reset"] = "persist",
1011
+ ) -> pl.DataFrame:
1012
+ """Build relative-range bars from a Polars DataFrame of minute candles."""
1013
+ return _minute_call(
1014
+ _pd_compute_range_bars_from_minutes,
1015
+ minutes_df,
1016
+ threshold,
1017
+ schema,
1018
+ warmup_bars=warmup_bars,
1019
+ warmup_minutes=warmup_minutes,
1020
+ backend=backend,
1021
+ watermark=watermark,
1022
+ strict_ordering=strict_ordering,
1023
+ calendar=calendar,
1024
+ stream_id=stream_id,
1025
+ on_bar=on_bar,
1026
+ on_threshold_update=on_threshold_update,
1027
+ multiplier=multiplier,
1028
+ volume_type=volume_type,
1029
+ session_policy=session_policy,
1030
+ session_estimator=session_estimator,
1031
+ )
1032
+
1033
+
1034
+ def compute_renko_bars_from_minutes(
1035
+ minutes_df: pl.DataFrame,
1036
+ threshold: float,
1037
+ schema: MinuteSchemaMapping | None = None,
1038
+ *,
1039
+ warmup_bars: int = 0,
1040
+ warmup_minutes: int | None = None,
1041
+ backend: str = "python",
1042
+ watermark: str | None = "timestamp",
1043
+ strict_ordering: bool = False,
1044
+ calendar: TradingCalendar | None = None,
1045
+ stream_id: str = "default",
1046
+ on_bar: Any = None,
1047
+ on_threshold_update: Any = None,
1048
+ multiplier: float = 1.0,
1049
+ volume_type: str = "base",
1050
+ session_policy: Literal["close", "carry", "discard"] = "close",
1051
+ session_estimator: Literal["persist", "reset"] = "persist",
1052
+ ) -> pl.DataFrame:
1053
+ """Build renko-displacement bars from a Polars DataFrame of minute candles."""
1054
+ return _minute_call(
1055
+ _pd_compute_renko_bars_from_minutes,
1056
+ minutes_df,
1057
+ threshold,
1058
+ schema,
1059
+ warmup_bars=warmup_bars,
1060
+ warmup_minutes=warmup_minutes,
1061
+ backend=backend,
1062
+ watermark=watermark,
1063
+ strict_ordering=strict_ordering,
1064
+ calendar=calendar,
1065
+ stream_id=stream_id,
1066
+ on_bar=on_bar,
1067
+ on_threshold_update=on_threshold_update,
1068
+ multiplier=multiplier,
1069
+ volume_type=volume_type,
1070
+ session_policy=session_policy,
1071
+ session_estimator=session_estimator,
1072
+ )
1073
+
1074
+
1075
+ def compute_hybrid_bars_from_minutes(
1076
+ minutes_df: pl.DataFrame,
1077
+ dollar_threshold: float,
1078
+ volatility_threshold: float,
1079
+ schema: MinuteSchemaMapping | None = None,
1080
+ *,
1081
+ warmup_bars: int = 0,
1082
+ warmup_minutes: int | None = None,
1083
+ backend: str = "python",
1084
+ watermark: str | None = "timestamp",
1085
+ strict_ordering: bool = False,
1086
+ calendar: TradingCalendar | None = None,
1087
+ stream_id: str = "default",
1088
+ on_bar: Any = None,
1089
+ on_threshold_update: Any = None,
1090
+ multiplier: float = 1.0,
1091
+ volume_type: str = "base",
1092
+ session_policy: Literal["close", "carry", "discard"] = "close",
1093
+ session_estimator: Literal["persist", "reset"] = "persist",
1094
+ ) -> pl.DataFrame:
1095
+ """Build hybrid bars from a Polars DataFrame of minute candles."""
1096
+ return _minute_call(
1097
+ _pd_compute_hybrid_bars_from_minutes,
1098
+ minutes_df,
1099
+ dollar_threshold,
1100
+ volatility_threshold,
1101
+ schema,
1102
+ warmup_bars=warmup_bars,
1103
+ warmup_minutes=warmup_minutes,
1104
+ backend=backend,
1105
+ watermark=watermark,
1106
+ strict_ordering=strict_ordering,
1107
+ calendar=calendar,
1108
+ stream_id=stream_id,
1109
+ on_bar=on_bar,
1110
+ on_threshold_update=on_threshold_update,
1111
+ multiplier=multiplier,
1112
+ volume_type=volume_type,
1113
+ session_policy=session_policy,
1114
+ session_estimator=session_estimator,
1115
+ )