use std::collections::BTreeSet;
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::composite::CompositeScore;
use crate::evidence_coverage::{Coverage, DigestId, EvidenceInventory};
pub const ACCOUNTING_RESIDUAL_TOLERANCE: f64 = 1e-12;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ControlProperty {
ProtocolAndAccounting,
RefusalOfInvalidOrder,
EconomicComparator,
}
impl ControlProperty {
pub fn identifier(self) -> &'static str {
match self {
Self::ProtocolAndAccounting => "protocol_and_accounting",
Self::RefusalOfInvalidOrder => "refusal_of_invalid_order",
Self::EconomicComparator => "economic_comparator",
}
}
}
impl fmt::Display for ControlProperty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.identifier())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "observed", rename_all = "snake_case")]
pub enum ControlObservation {
ProtocolAndAccounting {
decisions_expected: usize,
decisions_round_tripped: usize,
accounting_residual: f64,
},
RefusalOfInvalidOrder {
invalid_orders_submitted: usize,
refusals_observed: usize,
},
EconomicComparator {
periods_expected: usize,
periods_observed: usize,
mean_return: f64,
},
}
impl ControlObservation {
pub fn property(&self) -> ControlProperty {
match self {
Self::ProtocolAndAccounting { .. } => ControlProperty::ProtocolAndAccounting,
Self::RefusalOfInvalidOrder { .. } => ControlProperty::RefusalOfInvalidOrder,
Self::EconomicComparator { .. } => ControlProperty::EconomicComparator,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ControlRun {
pub control_id: String,
pub property: ControlProperty,
pub observation: ControlObservation,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "shortfall", rename_all = "snake_case")]
pub enum ControlShortfall {
ProtocolIncomplete {
expected: usize,
round_tripped: usize,
},
AccountingDidNotClose { residual: f64 },
RefusalNotExercised,
InvalidOrderAccepted { submitted: usize, refused: usize },
ComparatorSeriesIncomplete { expected: usize, observed: usize },
ComparatorReturnNotFinite { mean_return: f64 },
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ControlVerdict {
pub control_id: String,
pub property: ControlProperty,
pub observation: ControlObservation,
pub held: bool,
pub shortfalls: Vec<ControlShortfall>,
pub detail: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SuiteControlEvidence {
pub controls: Vec<ControlVerdict>,
pub all_held: bool,
}
impl SuiteControlEvidence {
pub fn control(&self, control_id: &str) -> Option<&ControlVerdict> {
self.controls.iter().find(|c| c.control_id == control_id)
}
pub fn property_held(&self, property: ControlProperty) -> Option<bool> {
let mut declared = self
.controls
.iter()
.filter(|c| c.property == property)
.peekable();
declared.peek()?;
Some(declared.all(|c| c.held))
}
pub fn failed(&self) -> Vec<&str> {
self.controls
.iter()
.filter(|c| !c.held)
.map(|c| c.control_id.as_str())
.collect()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ControlError {
NoControls,
DuplicateControl { control_id: String },
PropertyMismatch {
control_id: String,
declared: ControlProperty,
observed: ControlProperty,
},
ControlRankedAsEntrant { control_id: String },
}
impl fmt::Display for ControlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoControls => write!(
f,
"suite controls: the suite declared no controls; an empty battery \
establishes nothing and is not reported as held"
),
Self::DuplicateControl { control_id } => write!(
f,
"suite controls: control `{control_id}` is declared more than once"
),
Self::PropertyMismatch {
control_id,
declared,
observed,
} => write!(
f,
"suite controls: control `{control_id}` was declared to establish \
`{declared}` but observed `{observed}`, which cannot be evidence for it"
),
Self::ControlRankedAsEntrant { control_id } => write!(
f,
"suite controls: `{control_id}` is a control and also a ranked entrant. \
A control is validation of the apparatus, not an attainable score"
),
}
}
}
impl std::error::Error for ControlError {}
fn shortfalls_of(observation: &ControlObservation) -> Vec<ControlShortfall> {
let mut out = Vec::new();
match *observation {
ControlObservation::ProtocolAndAccounting {
decisions_expected,
decisions_round_tripped,
accounting_residual,
} => {
if decisions_round_tripped != decisions_expected || decisions_expected == 0 {
out.push(ControlShortfall::ProtocolIncomplete {
expected: decisions_expected,
round_tripped: decisions_round_tripped,
});
}
if !accounting_residual.is_finite()
|| accounting_residual.abs() > ACCOUNTING_RESIDUAL_TOLERANCE
{
out.push(ControlShortfall::AccountingDidNotClose {
residual: accounting_residual,
});
}
}
ControlObservation::RefusalOfInvalidOrder {
invalid_orders_submitted,
refusals_observed,
} => {
if invalid_orders_submitted == 0 {
out.push(ControlShortfall::RefusalNotExercised);
} else if refusals_observed != invalid_orders_submitted {
out.push(ControlShortfall::InvalidOrderAccepted {
submitted: invalid_orders_submitted,
refused: refusals_observed,
});
}
}
ControlObservation::EconomicComparator {
periods_expected,
periods_observed,
mean_return,
} => {
if periods_observed != periods_expected || periods_expected == 0 {
out.push(ControlShortfall::ComparatorSeriesIncomplete {
expected: periods_expected,
observed: periods_observed,
});
}
if !mean_return.is_finite() {
out.push(ControlShortfall::ComparatorReturnNotFinite { mean_return });
}
}
}
out
}
fn describe(run: &ControlRun, shortfalls: &[ControlShortfall]) -> String {
let outcome = match run.observation {
ControlObservation::ProtocolAndAccounting {
decisions_expected,
decisions_round_tripped,
accounting_residual,
} => format!(
"{decisions_round_tripped}/{decisions_expected} decisions round-tripped, \
accounting residual {accounting_residual:e}"
),
ControlObservation::RefusalOfInvalidOrder {
invalid_orders_submitted,
refusals_observed,
} => format!("{refusals_observed}/{invalid_orders_submitted} invalid orders refused"),
ControlObservation::EconomicComparator {
periods_expected,
periods_observed,
mean_return,
} => format!(
"{periods_observed}/{periods_expected} periods produced, \
mean return {mean_return:.6} (reported, not a condition)"
),
};
let verdict = if shortfalls.is_empty() {
"held".to_string()
} else {
format!("withheld: {shortfalls:?}")
};
format!(
"`{}` was run to establish `{}`; it observed {outcome}; {verdict}",
run.control_id, run.property
)
}
pub fn evaluate_controls(runs: &[ControlRun]) -> Result<SuiteControlEvidence, ControlError> {
if runs.is_empty() {
return Err(ControlError::NoControls);
}
let mut seen: BTreeSet<&str> = BTreeSet::new();
let mut controls = Vec::with_capacity(runs.len());
for run in runs {
if !seen.insert(run.control_id.as_str()) {
return Err(ControlError::DuplicateControl {
control_id: run.control_id.clone(),
});
}
let observed = run.observation.property();
if observed != run.property {
return Err(ControlError::PropertyMismatch {
control_id: run.control_id.clone(),
declared: run.property,
observed,
});
}
let shortfalls = shortfalls_of(&run.observation);
controls.push(ControlVerdict {
control_id: run.control_id.clone(),
property: run.property,
observation: run.observation,
held: shortfalls.is_empty(),
detail: describe(run, &shortfalls),
shortfalls,
});
}
let all_held = controls.iter().all(|c| c.held);
Ok(SuiteControlEvidence { controls, all_held })
}
pub fn bind_to_suite(
runs: &[ControlRun],
board: &[CompositeScore],
) -> Result<SuiteControlEvidence, ControlError> {
for run in runs {
if board.iter().any(|s| s.agent_id == run.control_id) {
return Err(ControlError::ControlRankedAsEntrant {
control_id: run.control_id.clone(),
});
}
}
evaluate_controls(runs)
}
const COVERED_RUN: Coverage = Coverage::Covered {
digest: DigestId::RunProvenance,
};
pub const SUITE_CONTROL_INVENTORY: EvidenceInventory = EvidenceInventory {
document: "sharpebench_core::suite_controls::ControlVerdict",
fields: &[
("control_id", COVERED_RUN),
("property", COVERED_RUN),
("observation", COVERED_RUN),
("held", COVERED_RUN),
("shortfalls", COVERED_RUN),
(
"detail",
Coverage::Excluded {
reason: "a rendering of control_id, property, observation and shortfalls, all \
of which are covered by run_provenance; binding the prose would let a \
wording edit break a digest over unchanged evidence",
},
),
],
};
#[cfg(test)]
mod tests {
use super::*;
use crate::composite::{rank, AgentSubmission, Run, ScoreConfig};
fn cash(residual: f64) -> ControlRun {
ControlRun {
control_id: "cash".to_string(),
property: ControlProperty::ProtocolAndAccounting,
observation: ControlObservation::ProtocolAndAccounting {
decisions_expected: 100,
decisions_round_tripped: 100,
accounting_residual: residual,
},
}
}
fn refusal(submitted: usize, refused: usize) -> ControlRun {
ControlRun {
control_id: "invalid-order".to_string(),
property: ControlProperty::RefusalOfInvalidOrder,
observation: ControlObservation::RefusalOfInvalidOrder {
invalid_orders_submitted: submitted,
refusals_observed: refused,
},
}
}
fn comparator(mean_return: f64) -> ControlRun {
ControlRun {
control_id: "buy-and-hold".to_string(),
property: ControlProperty::EconomicComparator,
observation: ControlObservation::EconomicComparator {
periods_expected: 250,
periods_observed: 250,
mean_return,
},
}
}
#[test]
fn an_unprofitable_comparator_still_holds_its_control() {
let evidence =
evaluate_controls(&[cash(0.0), refusal(3, 3), comparator(-0.004)]).expect("evaluates");
let bh = evidence.control("buy-and-hold").expect("declared control");
assert!(
bh.held,
"a complete series from a losing policy still establishes the comparator: {bh:?}"
);
assert!(bh.shortfalls.is_empty());
assert!(evidence.all_held, "{evidence:?}");
assert!(bh.detail.contains("economic_comparator"), "{}", bh.detail);
assert!(
bh.detail.contains("not a condition"),
"the row must not read as a skill claim: {}",
bh.detail
);
}
#[test]
fn a_broken_accounting_path_fails_only_its_own_control() {
let evidence =
evaluate_controls(&[cash(1e-6), refusal(3, 3), comparator(0.002)]).expect("evaluates");
assert_eq!(evidence.failed(), vec!["cash"], "{evidence:?}");
assert_eq!(
evidence.control("cash").expect("declared").shortfalls,
vec![ControlShortfall::AccountingDidNotClose { residual: 1e-6 }]
);
assert_eq!(
evidence.property_held(ControlProperty::ProtocolAndAccounting),
Some(false)
);
assert_eq!(
evidence.property_held(ControlProperty::RefusalOfInvalidOrder),
Some(true),
"the refusal path is a different question and answered it"
);
assert!(!evidence.all_held);
}
#[test]
fn a_refusal_path_that_accepted_an_invalid_order_fails_only_its_own_control() {
let evidence =
evaluate_controls(&[cash(0.0), refusal(3, 2), comparator(0.002)]).expect("evaluates");
assert_eq!(evidence.failed(), vec!["invalid-order"], "{evidence:?}");
assert_eq!(
evidence
.control("invalid-order")
.expect("declared")
.shortfalls,
vec![ControlShortfall::InvalidOrderAccepted {
submitted: 3,
refused: 2,
}]
);
assert_eq!(
evidence.property_held(ControlProperty::ProtocolAndAccounting),
Some(true),
"the accounting path is a different question and answered it"
);
}
#[test]
fn a_refusal_control_with_nothing_to_refuse_does_not_hold() {
let evidence = evaluate_controls(&[refusal(0, 0)]).expect("evaluates");
let verdict = evidence.control("invalid-order").expect("declared");
assert!(
!verdict.held,
"a refusal control that refused nothing because nothing was submitted \
establishes nothing: {verdict:?}"
);
assert_eq!(
verdict.shortfalls,
vec![ControlShortfall::RefusalNotExercised]
);
}
#[test]
fn an_incomplete_comparator_series_fails_whatever_its_return() {
let mut run = comparator(0.05);
run.observation = ControlObservation::EconomicComparator {
periods_expected: 250,
periods_observed: 12,
mean_return: 0.05,
};
let evidence = evaluate_controls(&[run]).expect("evaluates");
let verdict = evidence.control("buy-and-hold").expect("declared");
assert!(!verdict.held);
assert_eq!(
verdict.shortfalls,
vec![ControlShortfall::ComparatorSeriesIncomplete {
expected: 250,
observed: 12,
}]
);
}
#[test]
fn a_non_finite_comparator_return_does_not_establish_the_comparator() {
for mean in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let evidence = evaluate_controls(&[comparator(mean)]).expect("evaluates");
let verdict = evidence.control("buy-and-hold").expect("declared");
assert!(
!verdict.held,
"mean {mean} is not a series anything can be compared against: {verdict:?}"
);
assert_eq!(
verdict.shortfalls.len(),
1,
"the series itself is complete, so finiteness is the only shortfall: {verdict:?}"
);
assert!(
matches!(
verdict.shortfalls[0],
ControlShortfall::ComparatorReturnNotFinite { .. }
),
"{verdict:?}"
);
assert!(!evidence.all_held);
}
let losing = evaluate_controls(&[comparator(-0.1)]).expect("evaluates");
let verdict = losing.control("buy-and-hold").expect("declared");
assert!(
verdict.held,
"a finite losing mean is a fact about the market, not a broken control: {verdict:?}"
);
assert!(verdict.shortfalls.is_empty());
assert!(losing.all_held);
}
#[test]
fn a_protocol_control_that_was_asked_for_nothing_does_not_hold() {
let run = ControlRun {
control_id: "cash".to_string(),
property: ControlProperty::ProtocolAndAccounting,
observation: ControlObservation::ProtocolAndAccounting {
decisions_expected: 0,
decisions_round_tripped: 0,
accounting_residual: 0.0,
},
};
let evidence = evaluate_controls(&[run]).expect("evaluates");
let verdict = evidence.control("cash").expect("declared");
assert!(!verdict.held, "{verdict:?}");
assert_eq!(
verdict.shortfalls,
vec![ControlShortfall::ProtocolIncomplete {
expected: 0,
round_tripped: 0,
}]
);
}
#[test]
fn a_residual_exactly_at_the_tolerance_still_closes() {
let at = evaluate_controls(&[cash(ACCOUNTING_RESIDUAL_TOLERANCE)]).expect("evaluates");
assert!(
at.control("cash").expect("declared").held,
"{:?}",
at.control("cash")
);
let past =
evaluate_controls(&[cash(ACCOUNTING_RESIDUAL_TOLERANCE * 2.0)]).expect("evaluates");
assert!(!past.control("cash").expect("declared").held);
}
#[test]
fn an_observation_of_another_property_is_refused() {
let run = ControlRun {
control_id: "invalid-order".to_string(),
property: ControlProperty::RefusalOfInvalidOrder,
observation: ControlObservation::ProtocolAndAccounting {
decisions_expected: 100,
decisions_round_tripped: 100,
accounting_residual: 0.0,
},
};
assert_eq!(
evaluate_controls(&[run]),
Err(ControlError::PropertyMismatch {
control_id: "invalid-order".to_string(),
declared: ControlProperty::RefusalOfInvalidOrder,
observed: ControlProperty::ProtocolAndAccounting,
})
);
}
#[test]
fn an_empty_battery_is_refused_rather_than_reported_as_held() {
assert_eq!(evaluate_controls(&[]), Err(ControlError::NoControls));
}
#[test]
fn a_repeated_control_identity_is_refused() {
assert_eq!(
evaluate_controls(&[cash(0.0), cash(0.0)]),
Err(ControlError::DuplicateControl {
control_id: "cash".to_string(),
})
);
}
fn board_with(agent_id: &str) -> Vec<CompositeScore> {
rank(
&[AgentSubmission {
agent_id: agent_id.to_string(),
runs: (0..4)
.map(|_| Run {
returns: (0..60)
.map(|i| 0.002 + 0.0005 * (i as f64 * 0.7).sin())
.collect(),
..Run::default()
})
.collect(),
..AgentSubmission::default()
}],
&ScoreConfig::default(),
)
}
#[test]
fn a_control_cannot_also_be_a_ranked_entrant() {
let board = board_with("buy-and-hold");
assert_eq!(
bind_to_suite(&[cash(0.0), comparator(0.002)], &board),
Err(ControlError::ControlRankedAsEntrant {
control_id: "buy-and-hold".to_string(),
})
);
let entrants = board_with("momentum");
assert!(bind_to_suite(&[cash(0.0), comparator(0.002)], &entrants).is_ok());
}
#[test]
fn a_control_verdict_carries_no_competitive_score() {
let evidence = evaluate_controls(&[comparator(0.002)]).expect("evaluates");
let json = serde_json::to_value(&evidence.controls[0]).expect("serializes");
let keys: BTreeSet<String> = json
.as_object()
.expect("a verdict serializes as an object")
.keys()
.cloned()
.collect();
for forbidden in [
"composite",
"deflated_sharpe",
"psr",
"rank_eligible",
"rank_ordinal",
] {
assert!(
!keys.contains(forbidden),
"a control verdict must carry no competitive score: found `{forbidden}` in {keys:?}"
);
}
}
#[test]
fn the_control_inventory_matches_the_verdict_it_declares() {
let evidence = evaluate_controls(&[cash(1e-6)]).expect("evaluates");
let json = serde_json::to_value(&evidence.controls[0]).expect("serializes");
let names: Vec<String> = json
.as_object()
.expect("a verdict serializes as an object")
.keys()
.cloned()
.collect();
let audit = SUITE_CONTROL_INVENTORY.audit(&names);
assert!(audit.is_complete(), "{audit:?}");
assert_eq!(
SUITE_CONTROL_INVENTORY.fields_for(DigestId::RunProvenance),
vec![
"control_id",
"property",
"observation",
"held",
"shortfalls"
],
"identity, intent and outcome are all bound"
);
}
}