Milestone 'Strategy node vocabulary I — temporal, logic, resample & session': the 7 aura-std nodes (EqConst, Gt, And, Delay, Latch, Resample, Session) plus a synthetic e2e fixture, driven by the GER40 15m session-breakout. Records the design pass's cross-cutting decisions: close-instant timestamp convention, Latch emits f64 (Exposure dropped from the DAG), the EqConst i64->bool gate, the Resample-only Barrier(0) firing regime, and the build order. refs #84-#91.
8.8 KiB
0050 — Session-breakout node vocabulary (aura-std)
Status: draft (milestone spec) Driver: the GER40 / DAX 15-minute session-breakout strategy (below) as a forcing function for the stateful / temporal / logic / resampling vocabulary aura-std is still missing.
1. Goal & non-goal
aura today ships eight nodes (Sma, Ema, Add, Sub, LinComb, Exposure, SimBroker, Recorder). That set composes cross / linear-combination signals but
has no previous-bar lag (C5 register), no comparison producing bool,
no boolean logic, no resampler (C2 complete-bar), and no session /
calendar awareness. The GER40 session breakout needs all of them, so it is the
ideal driver to grow that vocabulary.
This milestone is engine-side, Phase 1: the reusable nodes in aura-std
plus one synthetic end-to-end fixture that pins the whole strategy on
hand-built bars. The real GER40 strategy — wired against real GER40 M1 data
with the correct pip value — is Phase 2, a separate consumer crate in its
own repo (C9), out of this milestone's scope (see §7).
2. The strategy (the driver)
- Resample raw M1 OHLC → 15-minute OHLC bars. Frankfurt session opens 09:00 Europe/Berlin (DST-aware).
- Bars indexed from the open: bar1 = 09:00–09:15, bar2 = 09:15–09:30, bar3 = 09:30–09:45, bar4, bar5 …
- Entry (long): when bar3 closes above the high of bar2 (the previous
15m bar):
close(bar3) > high(bar2). - Exit: flat when bar5 closes.
- Exposure is therefore
+1held from bar3-close through bar5-close, then0. Long-only (0or+1).
3. Dataflow (the DAG)
M1 OHLC: open/high/low/close as 4 separate f64 sources [aura-ingest M1FieldSource]
└─ Resample[15m] ⇒ record{open15,high15,low15,close15} [NEW, C2 emit-on-rollover]
close15 ─────────────────────────────────────────┐ ┐ ┐
high15 ─→ Delay[1] ─→ prevHigh15 ─┐ │ │ │
▼ │ │ │
Gt(close15 > prevHigh15) ⇒ breakout:bool [NEW]
close15 ─→ Session[FRA 09:00,15m] ⇒ bars_since_open:i64 [NEW]
├─ EqConst(·,3) ⇒ isBar3:bool [NEW]
└─ EqConst(·,5) ⇒ isBar5Close:bool [NEW]
And(breakout, isBar3) ⇒ entry:bool [NEW]
Latch(set=entry, reset=isBar5Close) ⇒ held:f64 (0.0/1.0) [NEW]
held ─→ SimBroker(exposure=held, price=close15) ⇒ equity(pips) ─→ Recorder [exists]
Note vs. the first sketch: Exposure is dropped — Latch emits f64
directly, so an on/off latch needs no clamp/scale. EqConst is added — it is
the i64 → bool gate that turns Session's bars_since_open into the per-bar
booleans; without it the i64 → bool seam is unbridged.
4. Cross-cutting decisions (settled by the design pass)
- Timestamp = close-instant, everywhere. A resampled bar carries only its
OHLC
f64values (no timestamp on the hot path, C4); its actionable cycle timestamp is the close-instant the engine stamps on the forwarded edge (harness.rs:427).Resampleemits bucket B exactly whenctx.now()first lands in bucket B+1, so the emission cycle's ts is the close instant of B — C2-correct (a bar is actionable only once complete).SimBrokermarks the held exposure againstclose15at that instant;Sessionindexes off the same close-instant. Off-by-one: the prose "bar3" is 1-indexed by ordinal; under close-count indexing,bars_since_open == 3is the close of the 09:30–09:45 bar. Pin this once inSession's RED test and reuse the constant in theEqConstbindings. bool → f64viaLatchoutput, no cast node.SimBrokerslot 0 (exposure) isf64and reads a cold leg as0.0; a held position is exposure+1, flat is0.0. Every otherboolin the DAG (breakout,isBar3,isBar5Close,entry) staysboolon the hot path (a first-class streamed scalar). The onlybool → f64transition lives insideLatch.eval.- Firing. The only
Barrier(0)group isResample's 4 OHLC inputs (four separate M1 sources at the same M1 timestamp — theOhlcvfixture precedent). Everything downstream isFiring::Anyand co-fresh because it all hangs off the singleResampleemission cycle.Latch.set/resetareAny(disjoint pulses on different cycles). This holds providedSessionis triggered off aResamplefield (see 4). Sessiontrigger isf64Any, wired fromclose15(the trigger value is unused —Sessionreads onlyctx.now()+ config). This makesSessionfire on exactly the bar-close cycle (the once-per-bar contract) without a newResamplefield. Pin inSession's RED test that the trigger value is ignored.- Dependency:
Sessionneeds DST-correct wall-clock math →chrono/chrono-tzentersaura-std(a vetted standard crate; do not hand-roll timezone/DST).chronois already transitive via aura-ingest's data-server.
5. Node contracts
| Node | in | out | params | state / notes |
|---|---|---|---|---|
EqConst |
value:i64 Any |
bool |
target:i64 |
stateless; out = (value == target). Operator is topology (separate type), only target is a knob (C11/C12). The i64→bool analogue of Gt. |
Gt |
a:f64 Any, b:f64 Any |
bool |
— | stateless; out = (a > b). First bool-emitting node. Relational family (Lt/Ge/…) are separate types, not a swept op param. |
And |
a:bool Any, b:bool Any |
bool |
— | stateless; out = a && b. Logic family (Or/Not) are separate types. |
Delay |
series:f64 Any |
value:f64 |
lag:i64 |
C5 register; node-owned ring of length lag; warm-up skip-emit (None until lag+1 samples). Output depends only on pre-cycle state (the half that lets a future engine close a feedback loop). |
Latch |
set:bool Any, reset:bool Any |
held:f64 |
— | C5 SR register; held → 1.0 on set, → 0.0 on reset; initial 0.0; define set&reset-same-cycle priority (cannot coincide here, but specify). Feeds SimBroker.slot0 directly. |
Resample |
open,high,low,close: 4×f64 Barrier(0) |
record open15,high15,low15,close15: f64 |
period_minutes:i64 |
accumulator (open=first, high=max, low=min, close=last); emit-on-rollover via ctx.now() bucket (C2); drops the partial last bar. |
Session |
trigger:f64 Any (value ignored) |
bars_since_open:i64 |
open_time, tz, period_minutes (+ indices via EqConst downstream) |
derives Frankfurt local time from ctx.now() (chrono-tz, DST); close-count indexing; emits 0/none outside the session. |
6. Build order & deliverable
Dependency-ordered (tiny independent primitives first), each a RED-first TDD unit per the skills pipeline:
EqConst— unblocks theSessiongate seam (biggest gap).Gt—Gt.rhsdepends onDelay; build the comparator early.And.Delay.Latch.Resample(heaviest internal accumulator).Session(heaviest external surface — admitschrono-tz).- Synthetic end-to-end fixture — the Phase-1 C9 deliverable.
E2E fixture (deliverable 8)
crates/aura-engine test ger40_session_breakout_holds_exposure_bar3_to_bar5,
a hand-wired FlatGraph (no composite/strategy-builder API is in scope — that
is a later milestone). One Frankfurt session, 4 synthetic OHLC VecSource
streams declared in fixed source order (k-way merge tie-breaks by source index).
Asserts:
- equity flat
0.0through bar1..bar3-close, then integrates+1·Δpriceper price cycle bar3-close → bar5-close, then flat after exit; - a second
RecorderonLatch.held:0.0before bar3-close,1.0through the hold,0.0after; - a no-entry control session (
bar3 close == bar2 high, strict->false) stays flat throughout — pins the strict-greater semantic; - run twice → byte-identical (C1 determinism).
Two traps the fixture must avoid: (a) include an M1 tick in bar6's window
so bar5 actually closes (the partial last bar is dropped) — else exposure leaks
to EOF; (b) supply ≥2 bars before the entry so Delay is warm.
7. Phase 2 (out of scope here)
The real GER40 strategy lives in a separate consumer crate (its own repo),
depending on aura-engine + aura-std cargo-natively (C9). It is not built via
aura new / cdylib hosting (a later milestone) — just a hand-wired Rust
bin/test running bootstrap → run on real GER40 M1 bars. Related existing
issues it will need: #22 (SimBroker pip value is per-asset — GER40 is an index,
pip_size = 1.0, not FX 0.0001), #80 / #81 (real-data ingestion seams).