From 92591e5dc0b0fa8532a0bda5232c15cccbd37d10 Mon Sep 17 00:00:00 2001 From: Brummel Date: Wed, 24 Jun 2026 10:25:18 +0200 Subject: [PATCH] refactor(stage1-r): name the conviction column conviction_at_entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the dense PositionManagement record's column 9 from `bias_at_entry_abs` to `conviction_at_entry` (FIELD_NAMES, the `r_col`/test index constants, the cross-crate layout guard, and the doc comments). Why: the column holds |bias| at entry, which C10 defines AS conviction (bias = direction + conviction magnitude; ledger INDEX.md ~458). The record already names the other half of that decomposition by its domain concept (`direction` = sign, not `bias_sign`), so naming the magnitude half mechanically (`bias_at_entry_abs`) was an inconsistency; `conviction_at_entry` speaks the ledger's ubiquitous language and parallels `direction`, while `_at_entry` preserves the frozen-snapshot precision. Considered and rejected: collapsing `(direction, bias_at_entry_abs)` into one signed `bias_at_entry` and dropping `direction` as redundant — it is not redundant. On a reversal row col 4 (`direction`) tracks the reopened leg while col 9 tracks the closed trade's entry conviction, so the two describe different trades and cannot be losslessly merged; they are also distinct semantic axes (current-position direction vs per-trade entry conviction). Scope: domain name at the data boundary, mechanical name internally — the internal `bias.abs()` variables (`Open.bias_abs`, the fold's `Trade.bias_abs`) keep their computation-faithful names, exactly the raw-internal / domain-external split the record uses elsewhere. Behaviour-preserving: cargo build --workspace clean; cargo test --workspace 500 passed, 0 failed (unchanged); clippy --all-targets -D warnings clean. The layout guard (`r_col_indices_match_producer_field_layout`) confirms the renamed string. refs #117 --- crates/aura-engine/src/report.rs | 14 +++++++------- crates/aura-engine/tests/stage1_r_e2e.rs | 8 ++++---- crates/aura-std/src/position_management.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/aura-engine/src/report.rs b/crates/aura-engine/src/report.rs index b98800c..eb523e7 100644 --- a/crates/aura-engine/src/report.rs +++ b/crates/aura-engine/src/report.rs @@ -50,7 +50,7 @@ pub struct RMetrics { pub n_open_at_end: u64, // positions force-closed at window end (counted, not hidden) pub sqn: f64, // √n · mean_R / sample-stdev_R; n<2 or zero-variance -> 0.0 pub net_expectancy_r: f64, // mean(R - round_trip_cost / latched_dist) — churn-honest - pub conviction_terciles_r: [f64; 3], // E[R] by |bias_at_entry| tercile (asc); <3 trades -> [0,0,0] + pub conviction_terciles_r: [f64; 3], // E[R] by conviction_at_entry tercile (asc); <3 trades -> [0,0,0] } // Dense `PositionManagement` record column indices — the lockstep contract with @@ -61,7 +61,7 @@ mod r_col { pub const REALIZED_R: usize = 1; pub const ENTRY_PRICE: usize = 6; pub const STOP_PRICE: usize = 7; - pub const BIAS_AT_ENTRY_ABS: usize = 9; + pub const CONVICTION_AT_ENTRY: usize = 9; pub const OPEN: usize = 11; pub const UNREALIZED_R: usize = 12; } @@ -85,7 +85,7 @@ pub fn summarize_r(record: &[(Timestamp, Vec)], round_trip_cost: f64) -> if row[r_col::CLOSED].as_bool() { trades.push(Trade { r: row[r_col::REALIZED_R].as_f64(), - bias_abs: row[r_col::BIAS_AT_ENTRY_ABS].as_f64(), + bias_abs: row[r_col::CONVICTION_AT_ENTRY].as_f64(), latched: (row[r_col::ENTRY_PRICE].as_f64() - row[r_col::STOP_PRICE].as_f64()).abs(), }); } @@ -96,7 +96,7 @@ pub fn summarize_r(record: &[(Timestamp, Vec)], round_trip_cost: f64) -> { trades.push(Trade { r: last[r_col::UNREALIZED_R].as_f64(), - bias_abs: last[r_col::BIAS_AT_ENTRY_ABS].as_f64(), + bias_abs: last[r_col::CONVICTION_AT_ENTRY].as_f64(), latched: (last[r_col::ENTRY_PRICE].as_f64() - last[r_col::STOP_PRICE].as_f64()).abs(), }); n_open_at_end = 1; @@ -154,7 +154,7 @@ pub fn summarize_r(record: &[(Timestamp, Vec)], round_trip_cost: f64) -> .map(|t| t.r - if t.latched > 0.0 { round_trip_cost / t.latched } else { 0.0 }) .sum(); let net_expectancy_r = net_sum / n as f64; - // conviction terciles: sort by |bias_at_entry| ascending, split into three contiguous + // conviction terciles: sort by conviction_at_entry ascending, split into three contiguous // near-equal-count buckets (floor boundaries i*n/3), E[R] per bucket. < 3 trades -> 0s. let conviction_terciles_r = if n < 3 { [0.0; 3] @@ -666,7 +666,7 @@ mod tests { } // A fuller closed-trade dense row: also sets entry_price (6), stop_price (7) and - // bias_at_entry_abs (9) — the geometry summarize_r recovers latched_dist and + // conviction_at_entry (9) — the geometry summarize_r recovers latched_dist and // conviction from. Width up to UNREALIZED_R+1 (summarize_r never reads col 13). fn r_row_full(realized: f64, entry: f64, stop: f64, bias_abs: f64) -> (Timestamp, Vec) { let mut v = vec![Scalar::f64(0.0); r_col::UNREALIZED_R + 1]; @@ -674,7 +674,7 @@ mod tests { v[r_col::REALIZED_R] = Scalar::f64(realized); v[r_col::ENTRY_PRICE] = Scalar::f64(entry); v[r_col::STOP_PRICE] = Scalar::f64(stop); - v[r_col::BIAS_AT_ENTRY_ABS] = Scalar::f64(bias_abs); + v[r_col::CONVICTION_AT_ENTRY] = Scalar::f64(bias_abs); v[r_col::OPEN] = Scalar::bool(false); (Timestamp(0), v) } diff --git a/crates/aura-engine/tests/stage1_r_e2e.rs b/crates/aura-engine/tests/stage1_r_e2e.rs index a4fcbe3..5812702 100644 --- a/crates/aura-engine/tests/stage1_r_e2e.rs +++ b/crates/aura-engine/tests/stage1_r_e2e.rs @@ -38,7 +38,7 @@ const OPEN: usize = 11; const UNREALIZED_R: usize = 12; const ENTRY_PRICE: usize = 6; const STOP_PRICE: usize = 7; -const BIAS_AT_ENTRY_ABS: usize = 9; +const CONVICTION_AT_ENTRY: usize = 9; const SIZE: usize = 10; /// Property: the real producer->consumer seam composes. Driving `FixedStop` -> @@ -264,14 +264,14 @@ fn r_col_indices_match_producer_field_layout() { assert_eq!(PM_RECORD_KINDS[REALIZED_R], ScalarKind::F64); assert_eq!(PM_RECORD_KINDS[UNREALIZED_R], ScalarKind::F64); - // iter-2 reads: entry_price (6), stop_price (7), bias_at_entry_abs (9) — the geometry + // iter-2 reads: entry_price (6), stop_price (7), conviction_at_entry (9) — the geometry // summarize_r recovers latched_dist (net-of-cost) and conviction (terciles) from. assert_eq!(PM_FIELD_NAMES[ENTRY_PRICE], "entry_price"); assert_eq!(PM_FIELD_NAMES[STOP_PRICE], "stop_price"); - assert_eq!(PM_FIELD_NAMES[BIAS_AT_ENTRY_ABS], "bias_at_entry_abs"); + assert_eq!(PM_FIELD_NAMES[CONVICTION_AT_ENTRY], "conviction_at_entry"); assert_eq!(PM_RECORD_KINDS[ENTRY_PRICE], ScalarKind::F64); assert_eq!(PM_RECORD_KINDS[STOP_PRICE], ScalarKind::F64); - assert_eq!(PM_RECORD_KINDS[BIAS_AT_ENTRY_ABS], ScalarKind::F64); + assert_eq!(PM_RECORD_KINDS[CONVICTION_AT_ENTRY], ScalarKind::F64); // iter-2 size column (10): the Sizer's `size` flows here, and the sibling // `risk_executor.rs` fixture asserts its R-invariance by reading this index — so the diff --git a/crates/aura-std/src/position_management.rs b/crates/aura-std/src/position_management.rs index e155077..ca334c3 100644 --- a/crates/aura-std/src/position_management.rs +++ b/crates/aura-std/src/position_management.rs @@ -35,7 +35,7 @@ pub const FIELD_NAMES: [&str; WIDTH] = [ "entry_price", "stop_price", "exit_price", - "bias_at_entry_abs", + "conviction_at_entry", "size", "open", "unrealized_r",