#!/usr/bin/env bash # Scenario 3 — cross-instrument generalization (the newest piece). # # `aura generalize` runs a single brought candidate across an instrument list # and grades how consistently it holds: a per-instrument R breakdown, a # worst-case floor (min over instruments), and a sign-agreement count. The # milestone framing is "an edge that holds on one instrument but not its # neighbours is suspect" — so the worst-case-across-instruments min is the # headline number. # # Happy path asserts (GER40,USDJPY,EURUSD share Sept-2024): # - per_instrument breakdown carries all 3 symbols # - worst_case == min over the per-instrument metric values # - sign_agreement is reported # - the family persists as a CrossInstrument family in `runs families` # - each persisted member self-identifies via its `instrument` lineage field # # Refusal asserts (each exit non-zero): # - a single --real symbol # - a non-R --metric # - a multi-value candidate flag (--fast 3,5) set -euo pipefail cd "$(git rev-parse --show-toplevel)" AURA="${AURA:-cargo run -q --bin aura --}" FROM=1725148800000 # Sept-2024 shared window TO=1727740799999 CAND=(--fast 3 --slow 8 --stop-length 14 --stop-k 2.0) fail() { echo "FAIL: $1" >&2; exit 1; } echo "### Scenario 3 — cross-instrument generalization" echo echo "--- (a) happy path: GER40,USDJPY,EURUSD ---" OUT="$($AURA generalize --strategy stage1-r --real GER40,USDJPY,EURUSD "${CAND[@]}" \ --from "$FROM" --to "$TO" --name ft_generalize)" printf '%s\n' "$OUT" AGG="$(printf '%s\n' "$OUT" | head -1)" for s in GER40 USDJPY EURUSD; do printf '%s' "$AGG" | grep -q "\"$s\"" || fail "per_instrument missing $s" done printf '%s' "$AGG" | grep -q '"n_instruments":3' || fail 'n_instruments != 3' printf '%s' "$AGG" | grep -q '"sign_agreement"' || fail 'no sign_agreement' printf '%s' "$AGG" | grep -q '"worst_case"' || fail 'no worst_case' # worst_case must equal the minimum of the three per-instrument metric values. WORST="$(printf '%s' "$AGG" | grep -oE '"worst_case":[-0-9.eE]+' | cut -d: -f2)" VALS="$(printf '%s' "$AGG" \ | grep -oE '\["[A-Z0-9]+",[-0-9.eE]+\]' | sed -E 's/.*,([-0-9.eE]+)\]/\1/')" MIN="$(printf '%s\n' "$VALS" | sort -g | head -1)" echo "per-instrument values: $(printf '%s ' $VALS)" echo "worst_case = $WORST computed min = $MIN" awk -v a="$WORST" -v b="$MIN" 'BEGIN{ exit !(a==b) }' \ || fail "worst_case ($WORST) is not the min over instruments ($MIN)" # the persisted family shows up as CrossInstrument, with 3 members. # (re-running the family mints a fresh run index, per the ledger; read it back.) FID="$(printf '%s' "$OUT" | grep -oE '"family_id":"ft_generalize-[0-9]+"' | head -1 | cut -d'"' -f4)" [ -n "$FID" ] || fail 'no family_id minted for the generalize run' echo "minted family id: $FID" FAM="$($AURA runs families | grep "\"family_id\":\"$FID\"" || true)" echo "$FAM" printf '%s' "$FAM" | grep -q '"kind":"CrossInstrument"' \ || fail 'persisted family kind is not CrossInstrument' printf '%s' "$FAM" | grep -q '"members":3' \ || fail 'CrossInstrument family does not have 3 members' # each member self-identifies via its `instrument` lineage field. MEMBERS="$($AURA runs family "$FID")" for s in GER40 USDJPY EURUSD; do printf '%s' "$MEMBERS" | grep -q "\"instrument\":\"$s\"" \ || fail "member for $s missing its instrument lineage field" done echo "all 3 members carry their instrument lineage field" echo echo "--- (b) refusal: a single --real symbol ---" set +e $AURA generalize --strategy stage1-r --real GER40 "${CAND[@]}" --from "$FROM" --to "$TO" >/dev/null 2>&1 RC=$?; set -e [ "$RC" -ne 0 ] || fail 'single-symbol generalize was not refused' echo "single symbol refused, exit=$RC" echo "--- (c) refusal: a non-R --metric ---" set +e MSG="$($AURA generalize --strategy stage1-r --real GER40,USDJPY "${CAND[@]}" \ --metric total_pips --from "$FROM" --to "$TO" 2>&1)" RC=$?; set -e [ "$RC" -ne 0 ] || fail 'non-R metric was not refused' echo "non-R metric refused, exit=$RC; message:" printf ' %s\n' "$MSG" printf '%s' "$MSG" | grep -qi 'R-only\|not comparable' \ || fail 'non-R refusal message does not explain the R-only rule' echo "--- (d) refusal: a multi-value candidate flag (--fast 3,5) ---" set +e MSG2="$($AURA generalize --strategy stage1-r --real GER40,USDJPY --fast 3,5 --slow 8 \ --stop-length 14 --stop-k 2.0 --from "$FROM" --to "$TO" 2>&1)" RC=$?; set -e [ "$RC" -ne 0 ] || fail 'multi-value candidate flag was not refused' echo "multi-value candidate refused, exit=$RC; message:" printf ' %s\n' "$MSG2" echo echo "PASS: happy-path breakdown + worst-case floor + sign-agreement + persisted" echo " CrossInstrument family with per-member instrument lineage; all three" echo " refusals exit non-zero."