feat(0086): cost-flag CLI ergonomics — units note, named diagnostics, non-R-harness guard
#153 (friction surfaced by the milestone-#148 close fieldtest). Three cost-flag fixes on `aura run`, all on the pure parse_run_args grammar: - Units discoverable: a COST_FLAGS_NOTE appended to the run usage + --help (price units, charged in R as cost/|entry-stop|, per-ENGINE-cycle carry, >= 0, stage1-r only). - A negative rate names the cause + flag ("<flag> must be non-negative, got <v>") via a factored parse_nonneg_rate helper, not a bare usage dump. - A cost flag on a non-R harness (sma/macd) is refused (exit 2, "cost flags require an R-evaluator harness (stage1-r)") instead of silently ignored — the named spec_gap, decided reject/fail-loud (refuse-don't-guess, recorded on #153), pinned by a new C10 ledger note. Internal RunArgs/RunData/HarnessKind gain #[derive(Debug)] (behaviour-neutral) so the pure parse_run_args unit tests can use expect_err. Verified independently: cargo test -p aura-cli green, the eight stage1-r cost goldens byte-unchanged; clippy -p aura-cli --all-targets -D warnings clean. Only the grammar/usage strings and the previously-unguarded sma-cost path changed; the cost-model graph and R math are untouched. closes #153
This commit is contained in:
+94
-18
@@ -2581,6 +2581,7 @@ struct CostConfig {
|
||||
|
||||
/// Which data a `run` drives a harness on: the built-in synthetic stream, or real M1
|
||||
/// close bars for a vetted symbol over an optional window.
|
||||
#[derive(Debug)]
|
||||
enum RunData {
|
||||
Synthetic,
|
||||
Real { symbol: String, from: Option<i64>, to: Option<i64> },
|
||||
@@ -3111,6 +3112,7 @@ fn persist_traces_r(
|
||||
/// Which built-in harness `aura run` drives. A fixed compile-time enumeration over
|
||||
/// Rust-authored harnesses — NOT a runtime node registry and NOT a DSL (C9/C17): the
|
||||
/// CLI *runs* an authored harness, it does not wire one.
|
||||
#[derive(Debug)]
|
||||
enum HarnessKind {
|
||||
Sma,
|
||||
Macd,
|
||||
@@ -3119,6 +3121,7 @@ enum HarnessKind {
|
||||
|
||||
/// The parsed `aura run` invocation: a harness, a data source, an optional trace name, and
|
||||
/// an optional flat round-trip cost per trade (`--cost-per-trade`, stage1-r only this cycle).
|
||||
#[derive(Debug)]
|
||||
struct RunArgs {
|
||||
harness: HarnessKind,
|
||||
data: RunData,
|
||||
@@ -3128,6 +3131,18 @@ struct RunArgs {
|
||||
carry_per_cycle: Option<f64>,
|
||||
}
|
||||
|
||||
/// Parse a cost-rate flag value and enforce non-negativity, naming the offending
|
||||
/// flag so a sign typo is distinguishable from a mistyped flag name. A *malformed*
|
||||
/// value stays a grammar usage error (unchanged); only a parsed-but-negative value
|
||||
/// yields the named message. Pure — no I/O, no exit.
|
||||
fn parse_nonneg_rate(flag: &str, value: &str, usage: &impl Fn() -> String) -> Result<f64, String> {
|
||||
let v: f64 = value.parse().map_err(|_| usage())?;
|
||||
if v < 0.0 {
|
||||
return Err(format!("{flag} must be non-negative, got {v}"));
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Parse the `run` tail: `[--harness <name>] [--real <SYM> [--from <ms>] [--to <ms>]]
|
||||
/// [--trace <name>] [--cost-per-trade <f64>] [--slip-vol-mult <f64>]` in any order, each flag at most once.
|
||||
/// `--harness sma` is the default (`--harness macd` / `--harness stage1-r` select the
|
||||
@@ -3135,8 +3150,9 @@ struct RunArgs {
|
||||
/// grammar is unit-testable; `main` does the side effects.
|
||||
fn parse_run_args(rest: &[&str]) -> Result<RunArgs, String> {
|
||||
let usage = || {
|
||||
"usage: aura run [--harness <sma|macd|stage1-r>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--trace <name>] [--cost-per-trade <f64>] [--slip-vol-mult <f64>] [--carry-per-cycle <f64>]"
|
||||
.to_string()
|
||||
format!(
|
||||
"usage: aura run [--harness <sma|macd|stage1-r>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--trace <name>] [--cost-per-trade <f64>] [--slip-vol-mult <f64>] [--carry-per-cycle <f64>]{COST_FLAGS_NOTE}"
|
||||
)
|
||||
};
|
||||
let mut harness: Option<HarnessKind> = None;
|
||||
let mut symbol: Option<String> = None;
|
||||
@@ -3184,35 +3200,35 @@ fn parse_run_args(rest: &[&str]) -> Result<RunArgs, String> {
|
||||
}
|
||||
"--cost-per-trade" if cost.is_none() => {
|
||||
let (value, t) = t.split_first().ok_or_else(usage)?;
|
||||
let v: f64 = value.parse().map_err(|_| usage())?;
|
||||
if v < 0.0 {
|
||||
return Err(usage());
|
||||
}
|
||||
cost = Some(v);
|
||||
cost = Some(parse_nonneg_rate("--cost-per-trade", value, &usage)?);
|
||||
tail = t;
|
||||
}
|
||||
"--slip-vol-mult" if slip_vol_mult.is_none() => {
|
||||
let (value, t) = t.split_first().ok_or_else(usage)?;
|
||||
let v: f64 = value.parse().map_err(|_| usage())?;
|
||||
if v < 0.0 {
|
||||
return Err(usage());
|
||||
}
|
||||
slip_vol_mult = Some(v);
|
||||
slip_vol_mult = Some(parse_nonneg_rate("--slip-vol-mult", value, &usage)?);
|
||||
tail = t;
|
||||
}
|
||||
"--carry-per-cycle" if carry_per_cycle.is_none() => {
|
||||
let (value, t) = t.split_first().ok_or_else(usage)?;
|
||||
let v: f64 = value.parse().map_err(|_| usage())?;
|
||||
if v < 0.0 {
|
||||
return Err(usage());
|
||||
}
|
||||
carry_per_cycle = Some(v);
|
||||
carry_per_cycle = Some(parse_nonneg_rate("--carry-per-cycle", value, &usage)?);
|
||||
tail = t;
|
||||
}
|
||||
_ => return Err(usage()),
|
||||
}
|
||||
}
|
||||
let harness = harness.unwrap_or(HarnessKind::Sma);
|
||||
// Cost flags are defined only against the R chain (gross-R -> net-R); a non-R
|
||||
// harness produces no R to charge against, so a cost flag there is a usage error,
|
||||
// not a silent no-op (refuse-don't-guess, C10).
|
||||
if !matches!(harness, HarnessKind::Stage1R)
|
||||
&& (cost.is_some() || slip_vol_mult.is_some() || carry_per_cycle.is_some())
|
||||
{
|
||||
return Err(
|
||||
"cost flags require an R-evaluator harness (stage1-r); \
|
||||
--harness sma/macd produces no R to charge against"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
// --from/--to require --real.
|
||||
if symbol.is_none() && (from.is_some() || to.is_some()) {
|
||||
return Err(usage());
|
||||
@@ -3241,6 +3257,14 @@ fn run_dispatch(args: RunArgs) -> Result<RunReport, String> {
|
||||
})
|
||||
}
|
||||
|
||||
/// One-line note appended to the `aura run` usage surfaces (the grammar-error
|
||||
/// usage closure and `--help`) so the cost-flag units, scale, per-engine-cycle
|
||||
/// semantics, non-negativity, and R-harness requirement are discoverable at the
|
||||
/// point of use (#153). A leading newline puts it on its own line under the usage.
|
||||
const COST_FLAGS_NOTE: &str = "\ncost flags (stage1-r only; all >= 0, charged in R as cost/|entry−stop|): \
|
||||
--cost-per-trade = per-trade cost in price units; --slip-vol-mult = slippage multiplier on realized vol; \
|
||||
--carry-per-cycle = carry in price units per ENGINE cycle (not per day, not an overnight swap)";
|
||||
|
||||
const USAGE: &str =
|
||||
"usage: aura run [--harness <sma|macd|stage1-r>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--trace <name>] [--cost-per-trade <f64>] [--slip-vol-mult <f64>] [--carry-per-cycle <f64>] | aura chart <name> [--tap <t>] [--panels] | aura graph | aura sweep [--strategy <sma|momentum|stage1-r|stage1-breakout|stage1-meanrev>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--name <n>|--trace <n>] | aura mc [--name <n>|--trace <n>] | aura mc --strategy stage1-r [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--fast <csv>] [--slow <csv>] [--stop-length <csv>] [--stop-k <csv>] [--block-len <n>] [--resamples <n>] [--seed <n>] | aura walkforward [--strategy <sma|momentum|stage1-r|stage1-breakout|stage1-meanrev>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--name <n>|--trace <n>] [--fast <csv>] [--slow <csv>] [--stop-length <csv>] [--stop-k <csv>] [--select <argmax|plateau:mean|plateau:worst>] | aura generalize [--strategy stage1-r] --real <SYM1,SYM2,...> --fast <n> --slow <n> --stop-length <n> --stop-k <f> [--from <ms>] [--to <ms>] [--metric <r-metric>] [--name <n>] | aura runs families | aura runs family <id> [rank <metric>]";
|
||||
|
||||
@@ -3262,7 +3286,7 @@ fn main() {
|
||||
// subcommands (#131): `aura chart --help` shows usage instead of erroring
|
||||
// "unexpected argument", and the bare `aura --help` is the same affordance.
|
||||
if args.iter().any(|a| a == "--help" || a == "-h") {
|
||||
println!("{USAGE}");
|
||||
println!("{USAGE}{COST_FLAGS_NOTE}");
|
||||
return;
|
||||
}
|
||||
match args.iter().map(String::as_str).collect::<Vec<_>>().as_slice() {
|
||||
@@ -4531,6 +4555,58 @@ mod tests {
|
||||
assert!(parse_run_args(&["bogus"]).is_err()); // unknown trailing token
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_args_negative_cost_rates_are_named_not_usage() {
|
||||
for flag in ["--cost-per-trade", "--slip-vol-mult", "--carry-per-cycle"] {
|
||||
let err = parse_run_args(&["--harness", "stage1-r", flag, "-0.5"])
|
||||
.expect_err("a negative cost rate must be refused");
|
||||
assert!(err.contains("must be non-negative"), "{flag}: {err}");
|
||||
assert!(err.contains(flag), "message must name the offending flag: {err}");
|
||||
assert!(err.contains("-0.5"), "message must carry the value: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_args_malformed_cost_rate_stays_grammar_usage() {
|
||||
let err = parse_run_args(&["--harness", "stage1-r", "--cost-per-trade", "x"])
|
||||
.expect_err("a malformed rate is a grammar error");
|
||||
assert!(err.contains("usage"), "a malformed value stays the usage path: {err}");
|
||||
// The grammar-error usage surface — the more frequently hit one — carries the
|
||||
// cost-flag note too (#153), so pin that dropping {COST_FLAGS_NOTE} from the
|
||||
// `usage` closure regresses it, not only the `--help` print.
|
||||
assert!(err.contains("per ENGINE cycle"), "the grammar usage must carry the cost-flag note: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_args_cost_flags_require_r_harness() {
|
||||
let cases: [(&[&str], &str); 3] = [
|
||||
(&["--harness", "sma"], "--cost-per-trade"),
|
||||
(&["--harness", "macd"], "--slip-vol-mult"),
|
||||
(&["--harness", "sma"], "--carry-per-cycle"),
|
||||
];
|
||||
for (harness, flag) in cases {
|
||||
let mut argv: Vec<&str> = harness.to_vec();
|
||||
argv.extend_from_slice(&[flag, "0.001"]);
|
||||
let err = parse_run_args(&argv).expect_err("cost flag on a non-R harness must be refused");
|
||||
assert!(err.contains("R-evaluator harness"), "{harness:?}+{flag}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_args_cost_flag_without_harness_defaults_sma_and_is_refused() {
|
||||
let err = parse_run_args(&["--carry-per-cycle", "0.5"])
|
||||
.expect_err("the default harness is sma → cost flag refused");
|
||||
assert!(err.contains("R-evaluator harness"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_run_args_cost_flags_accepted_on_stage1_r() {
|
||||
let a = parse_run_args(&["--harness", "stage1-r", "--cost-per-trade", "0.001"])
|
||||
.expect("cost flags are accepted on the R harness");
|
||||
assert!(matches!(a.harness, HarnessKind::Stage1R));
|
||||
assert_eq!(a.cost, Some(0.001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mc_args_bare_and_name_route_to_synthetic() {
|
||||
assert_eq!(parse_mc_args(&[]), Ok(McArgs::Synthetic { name: "mc".to_string(), persist: false }));
|
||||
|
||||
@@ -662,6 +662,40 @@ fn per_subcommand_help_is_uniform_stdout_exit_zero() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Property (#153): the cost-flag units/constraints are discoverable from
|
||||
/// `aura run --help` — the appended note states the in-R charge, the per-ENGINE-
|
||||
/// cycle carry semantics, the `>= 0` constraint, and that cost flags are stage1-r
|
||||
/// only. Pre-#153 the usage listed the flags with no unit or scale guidance.
|
||||
#[test]
|
||||
fn run_help_surfaces_cost_flag_units_note() {
|
||||
let out = Command::new(BIN).args(["run", "--help"]).output().expect("spawn aura run --help");
|
||||
assert_eq!(out.status.code(), Some(0), "run --help exits 0: {:?}", out.status);
|
||||
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
|
||||
assert!(stdout.contains("per ENGINE cycle"), "help must explain carry units: {stdout}");
|
||||
assert!(stdout.contains("stage1-r only"), "help must state the R-harness requirement: {stdout}");
|
||||
}
|
||||
|
||||
/// Property (#153): the cost-flag note reaches the user through the *grammar-error*
|
||||
/// path too, not only `--help`. A malformed cost value (`--cost-per-trade x`) is a
|
||||
/// usage error → exit 2 with the note on STDERR (nothing on stdout). The note is
|
||||
/// appended at two independent source sites (the `USAGE` const for `--help` and the
|
||||
/// `parse_run_args` usage closure for grammar errors); the help test pins the first,
|
||||
/// this pins the second — the more frequently hit surface — at the binary boundary,
|
||||
/// where the closure's String must actually route to stderr+exit-2.
|
||||
#[test]
|
||||
fn run_malformed_cost_value_usage_error_carries_note_on_stderr() {
|
||||
let out = Command::new(BIN)
|
||||
.args(["run", "--harness", "stage1-r", "--cost-per-trade", "x"])
|
||||
.output()
|
||||
.expect("spawn aura run --cost-per-trade x");
|
||||
assert_eq!(out.status.code(), Some(2), "a malformed cost value is a usage error (exit 2); stderr: {}",
|
||||
String::from_utf8_lossy(&out.stderr));
|
||||
assert!(out.stdout.is_empty(), "a usage error must not emit a report on stdout: {:?}", out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("usage"), "the grammar-error path stays a usage message: {stderr:?}");
|
||||
assert!(stderr.contains("per ENGINE cycle"), "the grammar-error usage must carry the cost-flag note: {stderr:?}");
|
||||
}
|
||||
|
||||
/// Property: `aura graph` emits a single self-contained HTML page — the
|
||||
/// interactive pin-graph viewer with the deterministic model inlined and the
|
||||
/// vendored Graphviz-WASM + pan-zoom inlined (no remote fetch). Driven through
|
||||
@@ -1583,6 +1617,49 @@ fn run_harness_sma_is_pip_only_no_r_block() {
|
||||
assert!(!s.contains("\"r\":"), "a pip run must omit the r key: {s}");
|
||||
}
|
||||
|
||||
/// Property (#153): a cost flag on a non-R harness (sma/macd, which produce no R)
|
||||
/// is a usage error — exit 2 with a named diagnostic, no silent no-op, no `runs/`
|
||||
/// write. The pre-#153 behaviour silently ignored the flag and exited 0.
|
||||
#[test]
|
||||
fn run_cost_flag_on_non_r_harness_is_refused_exit_2() {
|
||||
let dir = temp_cwd("sma-cost-refused");
|
||||
let out = Command::new(BIN)
|
||||
.current_dir(&dir)
|
||||
.args(["run", "--harness", "sma", "--cost-per-trade", "0.001"])
|
||||
.output()
|
||||
.expect("spawn aura run --harness sma --cost-per-trade");
|
||||
assert_eq!(out.status.code(), Some(2), "cost on a non-R harness must exit 2; stderr: {}",
|
||||
String::from_utf8_lossy(&out.stderr));
|
||||
assert!(out.stdout.is_empty(), "a refused run must not emit a report: {:?}", out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("R-evaluator harness"), "stderr must name the cause: {stderr:?}");
|
||||
assert!(!dir.join("runs").exists(), "a refused run must not write the registry");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property (#153): the non-R-harness cost guard fires on the IMPLICIT default
|
||||
/// harness too — `aura run --cost-per-trade ...` with no `--harness` defaults to
|
||||
/// sma (non-R) and is refused (exit 2, named cause, no `runs/` write). Distinct
|
||||
/// from the explicit `--harness sma` case: this pins that the guard reads the
|
||||
/// *resolved* harness (after the `unwrap_or(Sma)` default), so a refactor that only
|
||||
/// guarded an explicitly-named harness would leak a silent no-op through the default.
|
||||
#[test]
|
||||
fn run_cost_flag_on_default_harness_is_refused_exit_2() {
|
||||
let dir = temp_cwd("default-harness-cost-refused");
|
||||
let out = Command::new(BIN)
|
||||
.current_dir(&dir)
|
||||
.args(["run", "--cost-per-trade", "0.001"])
|
||||
.output()
|
||||
.expect("spawn aura run --cost-per-trade (no --harness)");
|
||||
assert_eq!(out.status.code(), Some(2), "cost on the default (sma) harness must exit 2; stderr: {}",
|
||||
String::from_utf8_lossy(&out.stderr));
|
||||
assert!(out.stdout.is_empty(), "a refused run must not emit a report: {:?}", out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("R-evaluator harness"), "stderr must name the cause: {stderr:?}");
|
||||
assert!(!dir.join("runs").exists(), "a refused run must not write the registry");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property: an unknown `--harness` name is a usage error at the binary boundary —
|
||||
/// exit 2 (never a silent default run), the `Err` arm wiring through to `exit(2)`.
|
||||
#[test]
|
||||
@@ -1814,16 +1891,15 @@ fn stage1_r_carry_net_r_equity_bleeds_over_the_hold() {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property (cycle 0085, cost-flag validation symmetry): a NEGATIVE `--carry-per-cycle`
|
||||
/// is refused at the binary boundary — exit 2, usage on stderr, nothing on stdout, no
|
||||
/// `runs/` write — the same shape the sibling `--cost-per-trade`/`--slip-vol-mult` flags
|
||||
/// enforce. The `v < 0.0` parse guard is the load-bearing line: without it a negative
|
||||
/// carry would reach `CarryCost::new` and PANIC (assert), crashing the binary instead of
|
||||
/// a clean usage refusal, and a negative price-unit carry would CREDIT R — inverting the
|
||||
/// sign of the whole accrual mechanism. None of the cycle-0085 goldens/bleed tests
|
||||
/// exercise the rejection branch. Deterministic; the refusal precedes any data access.
|
||||
/// Property (cycle 0085 + #153, cost-flag validation symmetry): a NEGATIVE
|
||||
/// `--carry-per-cycle` is refused at the binary boundary — exit 2, a named
|
||||
/// non-negativity diagnostic on stderr, nothing on stdout, no `runs/` write — the
|
||||
/// same shape the sibling `--cost-per-trade`/`--slip-vol-mult` flags enforce. The
|
||||
/// `v < 0.0` guard is load-bearing: without it a negative carry reaches
|
||||
/// `CarryCost::new` and PANICs, and a negative price-unit carry would CREDIT R —
|
||||
/// inverting the accrual sign. Deterministic; the refusal precedes any data access.
|
||||
#[test]
|
||||
fn stage1_r_negative_carry_per_cycle_refused_with_usage_exit_2() {
|
||||
fn stage1_r_negative_carry_per_cycle_refused_non_negative_exit_2() {
|
||||
let dir = temp_cwd("stage1-r-carry-neg");
|
||||
let out = Command::new(BIN)
|
||||
.current_dir(&dir)
|
||||
@@ -1835,7 +1911,27 @@ fn stage1_r_negative_carry_per_cycle_refused_with_usage_exit_2() {
|
||||
out.status, String::from_utf8_lossy(&out.stderr));
|
||||
assert!(out.stdout.is_empty(), "a refused run must not emit a report on stdout: {:?}", out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("usage"), "negative carry stderr must carry usage: {stderr:?}");
|
||||
assert!(stderr.contains("must be non-negative"), "negative carry stderr must name the cause: {stderr:?}");
|
||||
assert!(!dir.join("runs").exists(), "a refused run must not write the registry");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property (#153): a negative `--cost-per-trade` is refused at the binary
|
||||
/// boundary — exit 2, a named non-negativity diagnostic on stderr, no `runs/`
|
||||
/// write — the sibling of the carry guard, exercising the per-trade arm.
|
||||
#[test]
|
||||
fn run_negative_cost_per_trade_refused_non_negative_exit_2() {
|
||||
let dir = temp_cwd("cost-per-trade-neg");
|
||||
let out = Command::new(BIN)
|
||||
.current_dir(&dir)
|
||||
.args(["run", "--harness", "stage1-r", "--cost-per-trade", "-0.5"])
|
||||
.output()
|
||||
.expect("spawn aura run --cost-per-trade -0.5");
|
||||
assert_eq!(out.status.code(), Some(2), "negative cost must exit 2; stderr: {}",
|
||||
String::from_utf8_lossy(&out.stderr));
|
||||
assert!(out.stdout.is_empty(), "a refused run must not emit a report: {:?}", out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("must be non-negative"), "stderr must name the cause: {stderr:?}");
|
||||
assert!(!dir.join("runs").exists(), "a refused run must not write the registry");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -830,6 +830,13 @@ integration test. **Deferred (each its own #148 cycle):** a notional-based carry
|
||||
(rollover-boundary timing, 3× Wednesday, long/short asymmetry — deploy-edge realism), and
|
||||
sweep-path cost. Decision log: #148.
|
||||
|
||||
**Realization (cost-flag harness scoping, #153).** Cost flags are defined only
|
||||
against an R-evaluator harness (the gross-R → net-R chain). On a non-R harness
|
||||
(`--harness sma`/`macd`, which produce no R) a cost flag is a **usage error
|
||||
(exit 2)**, not a silent no-op — refuse-don't-guess. Negative cost rates are
|
||||
likewise rejected (exit 2) with a named diagnostic that identifies the offending
|
||||
flag. CLI ergonomics only; the cost-model graph and the R math are untouched.
|
||||
|
||||
**Reframe (2026-06-23, #117 — exposure → bias, R as the signal-quality unit).**
|
||||
[HISTORY — its R spine survives into the 2026-06-28 contract; its Stage-2 currency /
|
||||
realistic-broker / register / flat-1R-vs-compounding portions are SUPERSEDED by that
|
||||
|
||||
Reference in New Issue
Block a user