Use case · Rank

Rank candidates by expected value under constraints

Construction emits everything feasible. Ranker imposes the trader's priorities — rank by expected value, capital efficiency, or a custom composite, with hard constraints on capital, PoP floor, max-loss ceiling, and DTE window. Output is a sorted Vec<RankedCandidate> with the metric and the constraint trace.

When to use it

  • You have a constructed candidate set and need it ordered by a single, explicit objective.
  • You want hard constraints (capital, PoP, max-loss) applied as typed ranges, not ad-hoc filtering.
  • You want the constraint trace retained on each ranked candidate for audit.

Example

use ferro_spread::{Ranker, RankingMetric};

let ranker = Ranker::default()
    .by(RankingMetric::ExpectedValue)
    .constrain(RankingMetric::CapitalRequired,
               0.0..=1_000.0)
    .constrain(RankingMetric::ProbabilityOfProfit,
               0.60..)
    .constrain(RankingMetric::MaxLoss,
               ..=500.0);

let ranked = ranker.rank(&candidates);

for r in ranked.iter().take(10) {
    println!("{} → EV={:.2}, PoP={:.0}%",
             r.candidate.label, r.ev, r.pop * 100.0);
}

Notes

  • Constraints are typed ranges over the same metrics used for ranking — half-open ranges express floors and ceilings.
  • The full ordering survives in Vec<RankedCandidate>; each entry carries its rank, the metric value, and which constraints bound it.
Explain

Expand any ranked candidate into a typed Explanation to see which score components drove its rank.