Source code for trader.evaluation.base

"""The evaluator interface: the second opinion between a signal and an order.

A strategy says "this looks like a buy". An evaluator says whether that is worth
acting on. Keeping them separate is what lets `OllamaEvaluator` drop in next
slice without touching a strategy — and, because `BacktestEvaluator` is
deterministic, what lets the LLM's decisions be diffed against a reproducible
baseline. Without that baseline there is no way to tell whether the model added
judgement or noise.
"""

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Protocol, runtime_checkable

from trader.domain import Bar
from trader.strategies.base import Signal

__all__ = ["Evaluator", "Verdict"]


[docs] @dataclass(frozen=True, slots=True) class Verdict: """Whether to act on a signal, and the evidence behind that. `evidence` holds backtest metrics today and an LLM's output later, with no interface change — which is the point of it being a dict rather than a fixed set of metric fields. """ approved: bool reason: str evidence: dict[str, object] = field(default_factory=dict)
[docs] @runtime_checkable class Evaluator(Protocol): """Decides whether a signal is worth acting on."""
[docs] def evaluate(self, symbol: str, bars: Sequence[Bar], signal: Signal) -> Verdict: """Judge `signal` for `symbol`, given bars up to the decision bar.""" ...