Add a tick-bar aggregator, factoring the windowed-rollover fold into the engine layer #289

Open
opened 2026-07-20 11:10:45 +02:00 by claude · 0 comments
Collaborator

Context

Phase 4 of the Stratification milestone (#288) placed Resample in the new
aura-market crate. Its mechanics, though, are domain-free: a windowed-rollover
fold over N f64 channels — keyed by a time bucket, one reduction per channel
(first / max / min / last), emitting a completed window only on bucket
rollover (C2). Only three things make Resample market-specific: the o/h/l/c
port/field naming, the OHLC reduction quartet [first, max, min, last], and the
nominal-minute bucketing (snap_to_nearest_minute, #280).

Extracting that generic fold into the engine layer (aura-std) was deliberately
not done in #288: with Resample the only consumer, it would have been a
one-implementor abstraction — the pattern aura defers by its demand-driven rule
(the same reasoning behind the twice-ratified #136 / #147 deferral). A second
consumer
removes that objection.

The work

  1. A tick → bar aggregator (market, new). Aggregate a raw tick stream into
    bars. This is the second consumer that justifies the shared fold.

  2. Extract the generic fold into aura-std (engine). A domain-free
    windowed-rollover reducer over N channels with a closed reduction vocabulary,
    emit-on-complete (C2), aura-core-only. Proposed shape (a design sketch,
    not yet implemented — validate against the aura-core Node model during
    spec production):

    enum Reducer { First, Max, Min, Last /*, Sum — for volume bars? see below */ }
    struct BucketFold<const N: usize> {
        bucket: Option<i64>,
        acc: [f64; N],
        reducers: [Reducer; N],
    }
    impl<const N: usize> BucketFold<N> {
        /// Some(completed window) on rollover, None while still accumulating.
        fn step(&mut self, key: i64, sample: [f64; N]) -> Option<[f64; N]> { /* ... */ }
    }
    
  3. Re-express Resample (market) over the shared fold — behaviour-preserving.
    BucketFold<4> with reducers [First, Max, Min, Last] + minute bucketing + the
    o/h/l/c schema. The existing Resample tests (crates/aura-market/src/
    resample.rs) must stay green unchanged — this is a pure factoring; the
    observable OHLC behaviour is identical.

Design forks (to settle in the spec)

  • Bar-boundary policy for ticks: fixed time buckets (reuse Resample's
    keying), fixed tick count, or accumulated volume — likely more than one, as a
    closed policy vocabulary rather than a single choice.
  • OHLC vs OHLCV. If volume bars or an OHLCV output are in scope, the reduction
    vocabulary gains a Sum reducer. Worth deciding up front so BucketFold's
    Reducer set is right the first time rather than widened later.

Scope

  • Independent of #288's merge: the Phase 4 split stands (Resample lives in
    aura-market, moved verbatim); this is a follow-up that cleans up the one node
    C28 flagged as mechanically-generic-but-market-placed.
  • Behaviour-preserving for Resample; net-new for the tick aggregator. The
    factoring lands the generic fold in the engine layer, keeping the C28 import
    direction (market → engine) intact.
## Context Phase 4 of the Stratification milestone (#288) placed `Resample` in the new `aura-market` crate. Its mechanics, though, are domain-free: a windowed-rollover fold over N `f64` channels — keyed by a time bucket, one reduction per channel (`first` / `max` / `min` / `last`), emitting a completed window only on bucket rollover (C2). Only three things make `Resample` market-specific: the `o/h/l/c` port/field naming, the OHLC reduction quartet `[first, max, min, last]`, and the nominal-minute bucketing (`snap_to_nearest_minute`, #280). Extracting that generic fold into the engine layer (`aura-std`) was deliberately **not** done in #288: with `Resample` the only consumer, it would have been a one-implementor abstraction — the pattern aura defers by its demand-driven rule (the same reasoning behind the twice-ratified #136 / #147 deferral). A **second consumer** removes that objection. ## The work 1. **A tick → bar aggregator (market, new).** Aggregate a raw tick stream into bars. This is the second consumer that justifies the shared fold. 2. **Extract the generic fold into `aura-std` (engine).** A domain-free windowed-rollover reducer over N channels with a closed reduction vocabulary, emit-on-complete (C2), `aura-core`-only. Proposed shape (a **design sketch**, not yet implemented — validate against the `aura-core` `Node` model during spec production): ```rust enum Reducer { First, Max, Min, Last /*, Sum — for volume bars? see below */ } struct BucketFold<const N: usize> { bucket: Option<i64>, acc: [f64; N], reducers: [Reducer; N], } impl<const N: usize> BucketFold<N> { /// Some(completed window) on rollover, None while still accumulating. fn step(&mut self, key: i64, sample: [f64; N]) -> Option<[f64; N]> { /* ... */ } } ``` 3. **Re-express `Resample` (market) over the shared fold — behaviour-preserving.** `BucketFold<4>` with reducers `[First, Max, Min, Last]` + minute bucketing + the `o/h/l/c` schema. The existing `Resample` tests (`crates/aura-market/src/` `resample.rs`) must stay green **unchanged** — this is a pure factoring; the observable OHLC behaviour is identical. ## Design forks (to settle in the spec) - **Bar-boundary policy for ticks:** fixed time buckets (reuse `Resample`'s keying), fixed tick count, or accumulated volume — likely more than one, as a closed policy vocabulary rather than a single choice. - **OHLC vs OHLCV.** If volume bars or an OHLCV output are in scope, the reduction vocabulary gains a `Sum` reducer. Worth deciding up front so `BucketFold`'s `Reducer` set is right the first time rather than widened later. ## Scope - Independent of #288's merge: the Phase 4 split stands (`Resample` lives in `aura-market`, moved verbatim); this is a follow-up that cleans up the one node C28 flagged as mechanically-generic-but-market-placed. - Behaviour-preserving for `Resample`; net-new for the tick aggregator. The factoring lands the generic fold in the engine layer, keeping the C28 import direction (market → engine) intact.
claude added the feature label 2026-07-20 11:10:45 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#289