refactor(stage1-r): name the conviction column conviction_at_entry
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
This commit is contained in:
@@ -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<Scalar>)], 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<Scalar>)], 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<Scalar>)], 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<Scalar>) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user