R-sweep families + rank-by-sqn (SQN as the sweep objective) #133
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Deferred from cycle 0065 (the Stage-1 R single-run CLI surface, #129). SQN is documented as the dispersion-adjusted sweep objective, but no sweep currently produces R metrics or ranks by them.
Scope:
stage1-rharness over a param grid);runs family rank <metric>(metric_cmpinaura-registry) to understand the R metrics —sqn(higher-is-better),expectancy_r,net_expectancy_r.This realizes SQN's role as the single-number objective for ranking a sweep family by signal quality. Larger than the single-run cut: touches the sweep builders + the registry rank vocabulary. Sibling of the n-normalized SQN idea (#130).
Design reconciliation (specify, cycle 0066) — /boss, signed below by the grounding-check
Spec:
docs/specs/0066-r-sweep-rank.md. The forks below were derived from thesources, the code, and the existing design; this records them on the reference
issue so the call made without a human in the loop is auditable and vetoable.
Fork: which knobs the stage1-r sweep grid varies -> the SIGNAL params
(
fast.lengthxslow.length); the stop and sizing stay fixed;bias.scaleis also excluded.
Basis (derived):
risk_budgetis R-invariant by construction (#128 — scalingthe budget leaves every realized_r unchanged), and under flat-1R the R outcomes
depend on the bias SIGN (direction/presence), not its magnitude, so
bias.scaleis degenerate for the ranked metric too — an axis that cannot move SQN/expectancy
is a non-axis. The stop DEFINES the R unit (1R = the loss if stopped); varying it
changes what R means per member and changes
n_trades, making cross-member rawSQN an apples-to-oranges comparison — which is exactly the motivation for the
deferred n-normalized SQN100 (#130). Holding the stop (the measurement yardstick)
fixed and varying the signal makes SQN a coherent single-number signal-quality
objective, the issue's stated purpose.
Fork: ranking a member whose
rblock is absent (RunMetrics.r == None)-> it sorts to the WORST position for an R metric (treated as -inf for the
higher-is-better R metrics).
Basis (derived): a member with no R block has no R signal quality, so ranking it
below every member that does is the correct semantic; it also collapses harmlessly
to ordinal order for a homogeneous all-None family (a pip-only SMA/momentum family
ranked by an R metric), avoiding a new error path for a degenerate-but-harmless
case. A stage1-r family is homogeneous
r: Some; the None case is the cross-familyguard, not the common path.
Fork: the CLI surface for sweeping stage1-r -> extend the sweep's own
--strategygrammar with the valuestage1-r; do NOT unify it with therunsubcommand's
--harnessflag.Basis (derived): the sweep already selects its harness via
--strategy {sma, momentum}(a different flag and a different value-set thanrun --harness {sma, macd,stage1-r}— a pre-existing, orthogonal divergence). The minimal coherentextension is one more
--strategyvalue; unifying the two flag namespaces wouldtouch the SMA/momentum paths and is its own CLI-UX change, out of scope here
(a candidate follow-up
idea).Fork: how the swept knobs are floated without breaking the single run
-> one blueprint with
fast.length/slow.lengthleft free inparam_space; thesingle run binds the existing constants (fast=2, slow=4) via a compile-time param
point (byte-output preserved, enforced by the single-run byte-pins); the sweep
varies them via
.axis(...).Basis (derived): a single wiring source avoids a drifting duplicate blueprint, and
it mirrors the established momentum harness pattern (one
*_blueprint_with_sinks,bound by params for the single run, by axes for the sweep).
Settled (issue + domain), not a fork: the three R metrics added to the rank
vocabulary —
sqn,expectancy_r,net_expectancy_r— are all higher-is-better.Contract fit: ranking a family by a metric is C12 axis-2 (argmax-metric) applied
to the C18 family store (
families.jsonl), which already persists the fullRunReportincl. therblock — so no persistence change is needed, only themetric_cmpvocabulary reaching intor.Cycle number 0066 (cycle 0065 closed; its spec/plan were
git rm'd).Grounding revision (specify, /boss auto-sign)
The first grounding-check BLOCKed one assumption: the spec claimed the stage1-r
single-run output was already byte-pinned and the blueprint refactor thereby
byte-protected — but no such golden exists (the only output test compares
run() == run()within one build, blind to a refactor drift vs the pre-refactorbuild).
Resolution (not an override — the spec was corrected and re-grounded):
extracted into a shared helper; the single run keeps its identical
.bind(...)calls (output unchanged by construction), only the sweep floats fast/slow.
current HEAD, so byte-preservation is verified rather than assumed; a drift
bounces to debug.
Re-grounded → PASS. Spec committed
1c8678a, boss-signed via the grounding PASS.Proceeding to planner → implement.
Plan-stage decisions (planner recon flagged two; both derived)
The single run stays fast=2/slow=4 (byte-pinned by the new golden — aligning it
to the grid would break the byte-preservation contract for no research benefit);
the sweep grid spans distinct signal configs, not the smoke config.
fast < slowholds in both.
--tracefor the stage1-r sweep → out of scope this cycle.#133's deliverable is the rankable family store (
append_family+ rank); thefamily is persisted to
families.jsonlregardless (the--namepath). Per-member4-tap trace dirs (chartable members) are a separable follow-up; the
stage1_r_sweep_family(_trace, data)slot stays for dispatch uniformity but isunused. (Candidate follow-up
idea.)metricsobject(not the full line), because
manifest.commitis a volatile build constant; therefactor can only move computed values (metrics), so a metrics-exact golden is the
precise, commit-agnostic guard.