feat(std,engine): SessionFrankfurt — the Session node enters the closed roster

A Frankfurt-baked zero-arg Session preset (open 09:00 Europe/Berlin as
literals) joins the std vocabulary as SessionFrankfurt, declaring
period_minutes: I64 (default 15) as its one scalar knob — the
single-knob roster pattern the cost nodes established. A data-only
blueprint can now reach a session/time-of-day stream (bars_since_open),
unblocking time-of-day conditioners for the confirm/refute research
model. Roster count pins bump 28 -> 29 in both conscious-act sites.

Fork decision on the issue: the preset variant over a param-vocabulary
extension — a timezone param would need a string kind, breaching the
four-scalar discipline; the preset keeps the vocabulary closed and is
the additive extension the roster-exclusion comment anticipates. The
4-arg Session::builder stays untouched; in_session/session_open_ts
stay deferred (#154).

closes #261
This commit is contained in:
2026-07-14 00:02:50 +02:00
parent 89d703dd99
commit ca4a89864c
4 changed files with 37 additions and 7 deletions
+2 -2
View File
@@ -121,8 +121,8 @@ fn graph_introspect_vocabulary_lists_exactly_the_closed_roster_count() {
let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(
lines.len(),
28,
"the std-only (no project) vocabulary has exactly the roster's 28 entries: {stdout}"
29,
"the std-only (no project) vocabulary has exactly the roster's 29 entries: {stdout}"
);
}
+1 -1
View File
@@ -88,7 +88,7 @@ pub use rolling_max::RollingMax;
pub use rolling_min::RollingMin;
pub use scale::Scale;
pub use series_reducer::SeriesReducer;
pub use session::Session;
pub use session::{Session, SessionFrankfurt};
pub use sim_broker::SimBroker;
pub use sizer::Sizer;
pub use sma::Sma;
+30 -1
View File
@@ -32,7 +32,9 @@
//!
//! **Cold guard.** If the trigger column is empty (not yet fired) → `None`.
use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind};
use aura_core::{
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, ScalarKind,
};
use chrono::{TimeZone, Timelike};
/// Frankfurt-session bar indexer. Holds the open offset (minutes past local
@@ -76,6 +78,33 @@ impl Session {
}
}
/// The zero-arg `SessionFrankfurt` roster preset (#261): the Frankfurt open
/// (09:00 `Europe/Berlin`) baked as a literal, so the closed `std_vocabulary`
/// roster can reach a `Session` without a structural construction argument
/// (`Session::builder` itself stays absent from the roster — #155 scope).
/// The one remaining knob, `period_minutes` (i64, conventionally 15 — the
/// resampler's bar width), is declared as the node's sole `ParamSpec` and
/// bound through it via the single-knob `CarryCost`/`ConstantCost`/
/// `VolSlippageCost` pattern. This is additive: `Session::builder` is
/// untouched and stays the vehicle for other timezones/opens.
pub struct SessionFrankfurt;
impl SessionFrankfurt {
/// The param-generic recipe: one `period_minutes` I64 knob, the Frankfurt
/// open/timezone baked in the closure.
pub fn builder() -> PrimitiveBuilder {
PrimitiveBuilder::new(
"SessionFrankfurt",
NodeSchema {
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "trigger".into() }],
output: vec![FieldSpec { name: "bars_since_open".into(), kind: ScalarKind::I64 }],
params: vec![ParamSpec { name: "period_minutes".into(), kind: ScalarKind::I64 }],
},
|p| Box::new(Session::new(9, 0, chrono_tz::Europe::Berlin, p[0].i64())),
)
}
}
impl Node for Session {
fn lookbacks(&self) -> Vec<usize> {
vec![1]
+4 -3
View File
@@ -20,7 +20,7 @@
use crate::{
Abs, Add, And, Bias, CarryCost, Const, ConstantCost, Delay, Div, Ema, EqConst, FixedStop, Gt,
Latch, LongOnly, Max, Min, Mul, PositionManagement, Resample, RollingMax, RollingMin, Scale,
Sizer, Sma, Sqrt, Sub, VolSlippageCost,
SessionFrankfurt, Sizer, Sma, Sqrt, Sub, VolSlippageCost,
};
use aura_core::PrimitiveBuilder;
@@ -81,6 +81,7 @@ std_vocabulary_roster! {
"RollingMax" => RollingMax,
"RollingMin" => RollingMin,
"Scale" => Scale,
"SessionFrankfurt" => SessionFrankfurt,
"Sizer" => Sizer,
"SMA" => Sma,
"Sqrt" => Sqrt,
@@ -129,7 +130,7 @@ mod tests {
assert!(!std_vocabulary_types().contains(&"LinComb")); // construction-arg node
assert!(!std_vocabulary_types().contains(&"Recorder")); // sink
assert!(!std_vocabulary_types().contains(&"nope"));
// count guard: pins the roster at exactly 28 entries
assert_eq!(std_vocabulary_types().len(), 28);
// count guard: pins the roster at exactly 29 entries
assert_eq!(std_vocabulary_types().len(), 29);
}
}