Alpha Signal input paths — two shapes, one timing rule
status: ACTIVE tier: 2 (design reference) last-verified: 2026-11-06 verified-against: master @ b7af9e70 (Alpha Signal v3.14.3, Python backtest.py v1.7.1) owner-initiative: Projects/Levels-Gated PA Evolution
Purpose. The Alpha Signal core receives market/level facts through two different
transports, not one. This page records the two shapes, why they stay two (the owner
ruled out merging them — "one input layer" deliberation, 2026-11-05), and the single
timing rule that governs both. Citations are file:line at the verified commit.
The two input shapes
| Shape | Transport | What it carries | Where |
|---|---|---|---|
| Per-candle record (fixed-size scalars, pushed) | Built once per closed bar, then handed to the core on OnClosedBar |
VWAP, value area (VAH/VAL/POC), Demand Index — the per-bar scalar series | Alpha Signal.cs:5134-5136, BuildAlphaBarRecord :2643-2660; AlphaSignalCore.cs:690 |
| On-demand level lists (variable-length sets, pulled) | Read from TJ Levels through the host seam, at anchor bar a = bar − rm (a closed, past bar) |
Naked levels, UA levels, swing zones, bands | Alpha Signal.cs:2990-2991; AlphaSignalCore.cs:1365-1398 (via IAlphaSignalHost) |
The split is dictated by the data's shape, not by accident:
- Scalars are pushed. They are a fixed, small per-bar struct; pushing them avoids a lookup per field and keeps the record the goldens feed.
- Level lists are pulled. They are variable-length and already stored per-bar inside
TJ Levels (flat arrays + per-bar offset/count —
Levels.cs:6292-6331naked,:6356-6399UA,:6424-6431bands). Pushing them would copy that history into the core — the duplication the owner ruled out.TJLevelSetis areadonly struct, so the extraGetLevelsForBarread costs a copy, not an allocation (Levels.cs:76). - Zones are a filter, not a series.
CopyActive*Zones(dest, beforeBar)filters the live work-set under lock (Levels.cs:3283-3316); it is not per-bar history. The parent bounds it by drivingWalkZoneStepup tobarimmediately before detection (Alpha Signal.cs:5121-5128). The result mixes an origin cutoff at the anchor with liveIsActivestate through N−1 — correct only because Python mirrors it exactly.
The same facts reach the core by both paths (VWAP/VA via the record for the legacy
filter at AlphaSignalCore.cs:1010-1050, and again via GetLevelsForBar for the gate at
:1365-1398). They agree because both read the same per-bar series — but at different bars:
the record is read at the signal bar N, the gate at the anchor N−rm.
The timing rule (one owner)
Values are final at the candle close. The rule, and the mechanism that enforces it:
- ATAS re-drives each bar at its close. When bar N+1 forms,
ProcessBar(N)is re-driven withN < CurrentBar−1, rewritingseries[N]to through-N before Alpha reads it. The live and Ticks-replay probes both observed this rewrite, so the value Alpha snapshots for a closed bar is the final (through-N) value. - History walk is trivially final. On a chart load/recalc, the whole chart is pre-written
in order (
RebuildSessionNow,Levels.cs:4046-4089; eachProcessBar(b)writes onlyseries[b]) before the parent reads. - Load-end candle (the one real bug, fixed in v3.14.3). The old load-end branch advanced
_finalizedBarto the still-forming last loaded bar and blocked its true-close re-record, leaving that bar's VA/POC snapshot stale by one bar. v3.14.3 replaces it with one pure decision (DecideTransitionFinalization) that returns −1 for the load-end transition, so the last loaded bar is recorded at its real close like every other live bar. - One live-field trap.
RthVwapStdDevis a live-only scalar, not a per-bar series (Levels.cs:3252sets it from the live_rthVwapStdDev). It is marked do-not-use inLevels.cs; consumers that need a per-bar σ must derive it asRthVwapDev1Upper − RthVwap(AlphaSignalCore.cs:1388-1390).
Deliberation outcome
The owner approved the "one input layer" brief (2026-11-06) with the recommendation do not
refactor now. The two shapes stay; the single timing owner is the rule above. The refactor
("one doorway") is on the waiting list, to be revisited after the demo. See
Projects/Levels-Gated PA Evolution/deliberation/2026-11-05 One input layer/.