fix(aura-strategy): charge-basis-specific cost-node one-liners
The three cost nodes (ConstantCost, CarryCost, VolSlippageCost) shared a byte-identical generic doc line, so `aura graph introspect --vocabulary` could not tell them apart — C29's per-entry meaning was formally met but not entry-specific (binary-only M1 fieldtest finding). cost_node_builder now takes the factor-specific one-line doc as a parameter (the shared string is gone from the single schema home); each builder states its own charge basis. RED-first: cost_node_descriptions_are_pairwise_distinct failed against the shared string before the fix. closes #330
This commit is contained in:
@@ -29,6 +29,7 @@ impl CarryCost {
|
|||||||
"CarryCost",
|
"CarryCost",
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
vec![ParamSpec { name: "carry_per_cycle".into(), kind: ScalarKind::F64 }],
|
vec![ParamSpec { name: "carry_per_cycle".into(), kind: ScalarKind::F64 }],
|
||||||
|
"cost-model node: cost accrued per held cycle (param carry_per_cycle)",
|
||||||
|p| Box::new(CarryCost::new(p[0].f64())),
|
|p| Box::new(CarryCost::new(p[0].f64())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ impl ConstantCost {
|
|||||||
"ConstantCost",
|
"ConstantCost",
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
vec![ParamSpec { name: "cost_per_trade".into(), kind: ScalarKind::F64 }],
|
vec![ParamSpec { name: "cost_per_trade".into(), kind: ScalarKind::F64 }],
|
||||||
|
"cost-model node: fixed cost per trade (param cost_per_trade), charged at close",
|
||||||
|p| Box::new(ConstantCost::new(p[0].f64())),
|
|p| Box::new(ConstantCost::new(p[0].f64())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,26 +214,20 @@ impl<F: CostNode> Node for CostRunner<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Assemble a cost-node `PrimitiveBuilder`: the 4 geometry inputs ++ the factor's
|
/// Assemble a cost-node `PrimitiveBuilder`: the 4 geometry inputs ++ the factor's
|
||||||
/// extra inputs, the standard 3-field cost output, the given params, and a build
|
/// extra inputs, the standard 3-field cost output, the given params, a
|
||||||
/// closure. The single home for the cost-node schema shape.
|
/// factor-specific one-line `doc` (C29 — each cost node names its own charge
|
||||||
|
/// basis rather than sharing one generic sentence, #330), and a build closure.
|
||||||
|
/// The single home for the cost-node schema shape.
|
||||||
pub fn cost_node_builder(
|
pub fn cost_node_builder(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
extra_inputs: Vec<PortSpec>,
|
extra_inputs: Vec<PortSpec>,
|
||||||
params: Vec<ParamSpec>,
|
params: Vec<ParamSpec>,
|
||||||
|
doc: &'static str,
|
||||||
build: impl Fn(&[Cell]) -> Box<dyn Node> + 'static,
|
build: impl Fn(&[Cell]) -> Box<dyn Node> + 'static,
|
||||||
) -> PrimitiveBuilder {
|
) -> PrimitiveBuilder {
|
||||||
let mut inputs = geometry_input_ports();
|
let mut inputs = geometry_input_ports();
|
||||||
inputs.extend(extra_inputs);
|
inputs.extend(extra_inputs);
|
||||||
PrimitiveBuilder::new(
|
PrimitiveBuilder::new(name, NodeSchema { inputs, output: cost_output_fields(), params, doc }, build)
|
||||||
name,
|
|
||||||
NodeSchema {
|
|
||||||
inputs,
|
|
||||||
output: cost_output_fields(),
|
|
||||||
params,
|
|
||||||
doc: "cost-model node: charges its cost in R from position geometry, at close or accrued per held cycle",
|
|
||||||
},
|
|
||||||
build,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -501,6 +495,7 @@ mod tests {
|
|||||||
"Probe",
|
"Probe",
|
||||||
extra.clone(),
|
extra.clone(),
|
||||||
params.clone(),
|
params.clone(),
|
||||||
|
"cost-model node: probe factor for the builder-assembly test",
|
||||||
|_p| -> Box<dyn Node> { Box::new(CostRunner::new(StubCost(0.0))) },
|
|_p| -> Box<dyn Node> { Box::new(CostRunner::new(StubCost(0.0))) },
|
||||||
);
|
);
|
||||||
let schema = builder.schema();
|
let schema = builder.schema();
|
||||||
@@ -537,6 +532,21 @@ mod tests {
|
|||||||
assert_eq!(a, "volatility");
|
assert_eq!(a, "volatility");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cost_node_descriptions_are_pairwise_distinct() {
|
||||||
|
// C29: `aura graph introspect --vocabulary` must be able to tell the
|
||||||
|
// three cost nodes apart by their charge basis (flat-per-trade vs
|
||||||
|
// per-held-cycle-accrual vs volatility-scaled), not read one
|
||||||
|
// identical generic sentence three times (#330).
|
||||||
|
use crate::{CarryCost, ConstantCost, VolSlippageCost};
|
||||||
|
let constant_doc = ConstantCost::builder().schema().doc;
|
||||||
|
let carry_doc = CarryCost::builder().schema().doc;
|
||||||
|
let vol_slippage_doc = VolSlippageCost::builder().schema().doc;
|
||||||
|
assert_ne!(constant_doc, carry_doc, "ConstantCost vs CarryCost doc must differ");
|
||||||
|
assert_ne!(constant_doc, vol_slippage_doc, "ConstantCost vs VolSlippageCost doc must differ");
|
||||||
|
assert_ne!(carry_doc, vol_slippage_doc, "CarryCost vs VolSlippageCost doc must differ");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn producer_and_aggregator_share_the_triple() {
|
fn producer_and_aggregator_share_the_triple() {
|
||||||
// The structural lockstep: producer output and aggregator output/inputs all
|
// The structural lockstep: producer output and aggregator output/inputs all
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ impl VolSlippageCost {
|
|||||||
"VolSlippageCost",
|
"VolSlippageCost",
|
||||||
volatility_input_ports(),
|
volatility_input_ports(),
|
||||||
vec![ParamSpec { name: "slip_vol_mult".into(), kind: ScalarKind::F64 }],
|
vec![ParamSpec { name: "slip_vol_mult".into(), kind: ScalarKind::F64 }],
|
||||||
|
"cost-model node: slippage proportional to volatility (slip_vol_mult × volatility input), charged at close",
|
||||||
|p| Box::new(VolSlippageCost::new(p[0].f64())),
|
|p| Box::new(VolSlippageCost::new(p[0].f64())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user