feat(aura-cli, aura-engine): zero-trade cell + synthetic-smoke notes; pending-builder serialize fence
Harvest sweep, batch 4 of 6. - present_campaign's cell loop gains the non-walk-forward sibling of the #313 note: a cell whose families are all non-wf and whose every report traded zero times gets 'aura: note: ... metrics are vacuous, not a break-even result' on stderr (emit-independent; wf cells keep their window note, gate-truncated cells stay silent). - exec's single-run bias leg (always RunData::Synthetic — a direct blueprint exec never binds real data) notes on a zero-trade run that the built-in 18-cycle stream is a smoke fixture and points at a campaign document's data section for a meaningful run. - blueprint_serde refuses to serialize a still-pending arg-bearing builder (SerializeError::PendingBuilder naming node type + missing args + the try_args remedy) instead of emitting an args-free v1 document whose fault only surfaces at load as MissingArg — the write-side twin of the load-path fence; only the Rust GraphBuilder path can reach this state. closes #324 refs #341
This commit is contained in:
@@ -380,6 +380,21 @@ fn present_campaign(run: CampaignRun, env: &Env) -> Result<usize, String> {
|
||||
crate::diag::note_zero_trade_windows(
|
||||
wf.reports.iter().map(|r| r.metrics.r.as_ref().map_or(0, |m| m.n_trades)),
|
||||
);
|
||||
} else if !cell_out.families.is_empty() {
|
||||
// #324 (re-scoped): the non-walk-forward sibling — a cell whose
|
||||
// families are ALL non-`std::walk_forward` (grid sweep, MC
|
||||
// bootstrap, …) has no OOS-window vocabulary, so without a note
|
||||
// here a cell that never traded is indistinguishable from a
|
||||
// break-even one. Over every report across every family this
|
||||
// cell ran (a cell with zero families, e.g. gate-truncated,
|
||||
// emits nothing new here).
|
||||
let trades: Vec<u64> = cell_out
|
||||
.families
|
||||
.iter()
|
||||
.flat_map(|f| f.reports.iter())
|
||||
.map(|r| r.metrics.r.as_ref().map_or(0, |m| m.n_trades))
|
||||
.collect();
|
||||
crate::diag::note_zero_trade_cell(trades.into_iter());
|
||||
}
|
||||
if emit_family {
|
||||
for fam in &cell_out.families {
|
||||
|
||||
@@ -44,9 +44,53 @@ pub(crate) fn note_zero_trade_windows(mut window_trades: impl ExactSizeIterator<
|
||||
}
|
||||
}
|
||||
|
||||
/// #324 (re-scoped): the non-walk-forward sibling of `note_zero_trade_windows`
|
||||
/// — a campaign cell whose families are ALL non-walk-forward (grid sweep, MC
|
||||
/// bootstrap, …) has no per-window vocabulary to note through, yet a
|
||||
/// consumer still cannot tell "0.0 = break-even" from "0.0 = never traded"
|
||||
/// without this. Takes the per-report trade counts across every family the
|
||||
/// cell ran; a no-op unless there is at least one report and every one of
|
||||
/// them traded zero times.
|
||||
fn zero_trade_cell_note_text(n_reports: usize) -> String {
|
||||
if n_reports == 1 {
|
||||
"the cell's one report recorded zero trades; its metrics are vacuous, not a break-even \
|
||||
result"
|
||||
.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"the cell's {n_reports} reports all recorded zero trades; its metrics are vacuous, \
|
||||
not a break-even result"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// See `zero_trade_cell_note_text`. Called from the campaign cell loop's
|
||||
/// non-walk-forward branch (`campaign_run.rs`), independent of `emit`.
|
||||
pub(crate) fn note_zero_trade_cell(mut report_trades: impl ExactSizeIterator<Item = u64>) {
|
||||
let n_reports = report_trades.len();
|
||||
if n_reports > 0 && report_trades.all(|n| n == 0) {
|
||||
note!("{}", zero_trade_cell_note_text(n_reports));
|
||||
}
|
||||
}
|
||||
|
||||
/// #324 (comment 4501): `exec <blueprint.json>`'s single-run signal/bias leg
|
||||
/// always drives the built-in synthetic stream (`RunData::Synthetic`, no
|
||||
/// direct-blueprint exec ever binds real data — only a campaign document's
|
||||
/// `data` section does) — a tiny `n_cycles`-bar smoke fixture, not market
|
||||
/// data. Zero trades on it is expected, not a broken strategy; a caller that
|
||||
/// validates against it needs telling so. Unconditional — the caller gates
|
||||
/// on the zero-trade condition before calling.
|
||||
pub(crate) fn note_synthetic_smoke_zero_trades(n_cycles: usize) {
|
||||
note!(
|
||||
"the built-in synthetic stream ({n_cycles} cycles) is a small smoke fixture; zero \
|
||||
trades is expected here — bind real data via a campaign document's `data` section \
|
||||
(`aura exec <campaign.json>`) for a meaningful run"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::zero_trade_note_text;
|
||||
use super::{zero_trade_cell_note_text, zero_trade_note_text};
|
||||
|
||||
#[test]
|
||||
/// #278 fieldtest friction: the single-window form must read as
|
||||
@@ -62,4 +106,20 @@ mod tests {
|
||||
"all 3 walk-forward windows recorded zero trades"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// #324: the cell-level sibling pluralizes the same way and names the
|
||||
/// vacuous-metrics property, not just the raw zero count.
|
||||
fn zero_trade_cell_note_pluralizes() {
|
||||
assert_eq!(
|
||||
zero_trade_cell_note_text(1),
|
||||
"the cell's one report recorded zero trades; its metrics are vacuous, not a \
|
||||
break-even result"
|
||||
);
|
||||
assert_eq!(
|
||||
zero_trade_cell_note_text(4),
|
||||
"the cell's 4 reports all recorded zero trades; its metrics are vacuous, not a \
|
||||
break-even result"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,11 @@ use aura_runner::member::{
|
||||
use aura_runner::{TapPlan, TapSubscription};
|
||||
#[cfg(test)]
|
||||
use aura_runner::member::{run_blueprint_member, wrap_r, SYNTHETIC_PIP_SIZE};
|
||||
// `r_sma_prices` sizes the #324 synthetic-smoke-fixture note (`exec`'s
|
||||
// single-run bias leg names the built-in stream's actual cycle count) —
|
||||
// production-reached now, not test-only (contrast the `#[cfg(test)]`
|
||||
// imports around it, which lost their production callers to #295).
|
||||
use aura_runner::member::r_sma_prices;
|
||||
// The shared `DataSource` data provider lives in `aura_runner::family`
|
||||
// (#295 Task 8) — its production caller is `aura_runner::reproduce`;
|
||||
// `showcase_prices`/`synthetic_walk_sources` are its synthetic stream
|
||||
@@ -1300,6 +1305,13 @@ fn exec_blueprint_leg(path: &str, overrides: &[String], tap: &[String], env: &au
|
||||
eprintln!("aura: {}", panic_refusal_prose(&msg));
|
||||
std::process::exit(1);
|
||||
});
|
||||
// #324 (comment 4501): this leg always runs `RunData::Synthetic` (no
|
||||
// direct-blueprint exec ever binds real data), so a validating
|
||||
// caller reading zero trades here as "broken strategy" needs telling
|
||||
// it never touched real data.
|
||||
if report.metrics.r.as_ref().map_or(0, |m| m.n_trades) == 0 {
|
||||
crate::diag::note_synthetic_smoke_zero_trades(r_sma_prices().len());
|
||||
}
|
||||
println!("{}", report.to_json());
|
||||
} else if has_tap {
|
||||
if !overrides.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user