///|
pub(all) enum DecisionPolicyRule {
NoExecutableMarketOrder
ReversalRiskBlocksOrder
OrderRequiresRiskNotes
OrderRequiresSufficientConfidence
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) enum DecisionPolicyStatus {
PolicyPass
PolicyFail
} derive(Debug, Eq, ToJson, FromJson)
///|
/// Responsibility classification for legacy price-action decisions. Even the
/// conditional shape remains research/advisory data; it is never a broker
/// instruction.
pub(all) enum DecisionActionResponsibility {
PassiveAdvisory
ConditionalAdvisory
ForbiddenExecutableShape
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct DecisionPolicyFinding {
rule : DecisionPolicyRule
status : DecisionPolicyStatus
path : String
message : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct DecisionPolicyInput {
run_id : @domain.RunId
diagnosis : @domain.Stage1Diagnosis
decision : @domain.Stage2Decision
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct DecisionPolicyReport {
run_id : @domain.RunId
allowed : Bool
findings : Array[DecisionPolicyFinding]
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn decision_policy_input(
run_id : @domain.RunId,
diagnosis : @domain.Stage1Diagnosis,
decision : @domain.Stage2Decision,
) -> DecisionPolicyInput {
{ run_id, diagnosis, decision }
}
///|
fn finding(
rule : DecisionPolicyRule,
status : DecisionPolicyStatus,
path : String,
message : String,
) -> DecisionPolicyFinding {
{ rule, status, path, message }
}
///|
pub fn decision_action_responsibility(
action : @domain.DecisionAction,
) -> DecisionActionResponsibility {
match action {
NoOrder | Wait => PassiveAdvisory
LimitOrder | BreakoutOrder => ConditionalAdvisory
MarketOrder => ForbiddenExecutableShape
}
}
///|
fn order_shaped(action : @domain.DecisionAction) -> Bool {
decision_action_responsibility(action) != PassiveAdvisory
}
///|
fn reversal_risk_pattern(pattern : @domain.PatternTag) -> Bool {
pattern is ReversalAttempt ||
pattern is MajorTrendReversal ||
pattern is FinalFlag ||
pattern is DoubleTopBottom
}
///|
fn reversal_risk(diagnosis : @domain.Stage1Diagnosis) -> Bool {
diagnosis.patterns.any(reversal_risk_pattern)
}
///|
fn no_market_order_finding(
decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
if decision_action_responsibility(decision.action) is ForbiddenExecutableShape {
finding(
NoExecutableMarketOrder,
PolicyFail,
"stage2.action",
"market orders are not allowed in Moonfish advisory output",
)
} else {
finding(
NoExecutableMarketOrder,
PolicyPass,
"stage2.action",
"decision has no immediate executable market-order shape",
)
}
}
///|
fn reversal_risk_finding(
diagnosis : @domain.Stage1Diagnosis,
decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
if reversal_risk(diagnosis) && order_shaped(decision.action) {
finding(
ReversalRiskBlocksOrder,
PolicyFail,
"stage1.patterns",
"reversal-risk patterns may only produce wait or no-order decisions",
)
} else {
finding(
ReversalRiskBlocksOrder,
PolicyPass,
"stage1.patterns",
"reversal-risk hard ban is satisfied",
)
}
}
///|
fn risk_notes_finding(
decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
if order_shaped(decision.action) && decision.risk_notes.length() == 0 {
finding(
OrderRequiresRiskNotes,
PolicyFail,
"stage2.risk_notes",
"order-shaped decisions must cite explicit risk notes",
)
} else {
finding(
OrderRequiresRiskNotes,
PolicyPass,
"stage2.risk_notes",
"risk notes requirement is satisfied",
)
}
}
///|
fn confidence_finding(
decision : @domain.Stage2Decision,
) -> DecisionPolicyFinding {
if order_shaped(decision.action) && decision.confidence < 50 {
finding(
OrderRequiresSufficientConfidence,
PolicyFail,
"stage2.confidence",
"order-shaped decisions require confidence of at least 50",
)
} else {
finding(
OrderRequiresSufficientConfidence,
PolicyPass,
"stage2.confidence",
"order-shaped confidence threshold is satisfied",
)
}
}
///|
fn count_status(
findings : Array[DecisionPolicyFinding],
status : DecisionPolicyStatus,
) -> Int {
findings.fold(init=0, fn(count, item) {
if item.status == status {
count + 1
} else {
count
}
})
}
///|
fn policy_summary(report : DecisionPolicyReport) -> String {
let pass_count = count_status(report.findings, PolicyPass)
if report.allowed {
"decision policy passed \{pass_count}/\{report.findings.length()} checks for \{report.run_id}"
} else {
let fail_count = count_status(report.findings, PolicyFail)
"decision policy blocks \{fail_count}/\{report.findings.length()} check(s) for \{report.run_id}"
}
}
///|
pub fn evaluate_decision_policy(
input : DecisionPolicyInput,
) -> DecisionPolicyReport {
let findings = [
no_market_order_finding(input.decision),
reversal_risk_finding(input.diagnosis, input.decision),
risk_notes_finding(input.decision),
confidence_finding(input.decision),
]
let report : DecisionPolicyReport = {
run_id: input.run_id,
allowed: count_status(findings, PolicyFail) == 0,
findings,
summary: "",
}
{ ..report, summary: policy_summary(report) }
}
///|
pub fn decision_policy_validation(
report : DecisionPolicyReport,
) -> @domain.ValidationReport {
if report.allowed {
@domain.validation_ok()
} else {
let issues = report.findings.filter_map(fn(item) {
if item.status is PolicyFail {
Some(@domain.validation_issue(Error, item.path, item.message))
} else {
None
}
})
{ ok: false, issues }
}
}