trader.ranking.base module

What a ranker takes, what it returns, and the ordering used without one.

A ranker never sees a strategy, a broker, an executor or a repository — the same discipline daemon/ follows. It takes value objects and returns an order. That is what lets it be tested without a model and swapped without touching the pipeline.

trader.ranking.base.SOURCE_CONFIDENCE_ORDERING_ENABLED = 'confidence_ordering_enabled'

pipeline_config.confidence_ordering_enabled forced this order, bypassing whatever ranker was configured (issue #86). Distinct from SOURCE_FALLBACK_CONFIDENCE on purpose: that source means “no ranker was usable”; this one means “a ranker may well have been usable, an operator chose not to use it” — collapsing the two would misreport why a cycle’s order was what it was.

trader.ranking.base.SOURCE_LLM = 'llm'

How an order was arrived at. Recorded on every decision row, so a log line or a stored row never claims a ranking basis it did not have.

class trader.ranking.base.Candidate(symbol, confidence, reason, last_close, comparables)[source]

Bases: object

One approved BUY waiting on capital — the ranker’s whole input.

confidence is float | None rather than float: LlmStrategy puts one in Signal.indicators, and the rule strategies put nothing there. None means “this strategy does not report one”, which is a different claim from 0.0 and must stay distinguishable.

Parameters:
  • symbol (str)

  • confidence (float | None)

  • reason (str)

  • last_close (Decimal)

  • comparables (Comparables)

symbol: str
confidence: float | None
reason: str
last_close: Decimal
comparables: Comparables
class trader.ranking.base.Ranker(*args, **kwargs)[source]

Bases: Protocol

Orders approved candidates. Never raises, never vetoes, never sizes.

rank(candidates)[source]

Return an order over candidates, best first.

The result must be a permutation of exactly the symbols passed in: never shorter, never longer, never containing a symbol nobody proposed. A ranker that could drop a candidate would be vetoing, which is deliberately not its job — every candidate has already been approved by its own per-symbol decision one call earlier.

Parameters:

candidates (Sequence[Candidate])

Return type:

Ranking

class trader.ranking.base.Ranking(order, source, notes=<factory>, inputs=<factory>)[source]

Bases: object

An order over candidate symbols, and how it was arrived at.

Parameters:
  • order (tuple[str, ...])

  • source (str)

  • notes (Mapping[str, str])

  • inputs (Mapping[str, object])

order: tuple[str, ...]

Best first. Always a permutation of exactly the candidate symbols.

source: str
notes: Mapping[str, str]

Symbol -> the ranker’s own words about it. Empty on every fallback path.

inputs: Mapping[str, object]

Provenance for decisions.inputs_json.

trader.ranking.base.fallback_ranking(candidates)[source]

Confidence descending, ties broken on symbol ascending.

Used whenever a model ranking is unavailable or unusable. Safe as a default precisely because ranking only orders: every candidate here has already passed its own decision and must still clear every guardrail, so a degraded order cannot unlock risk — it only spends the headroom in a less informed sequence. Refusing to trade at all on a ranker outage would make a dead endpoint a single point of failure for trading, which is the shape the discovery rules forbid.

Ties break on symbol so a cycle is reproducible.

Parameters:

candidates (Sequence[Candidate])

Return type:

Ranking