Skip to content

Blender protocol and registry

What you implement, and how you register it. See Methods: notation for the conventions a blender is expected to honour.

Shared protocol helpers

Shared blender scaffolding: weight renormalization, masked averaging, target clipping, and per-lead-bucket fitting.

PerBucketFitter dataclass

Fit one state object per lead bucket, with a global-fit fallback.

fit_one receives the row subset for a bucket and returns the state. Without blend, buckets with fewer than min_rows rows fall back to the global state — an all-or-nothing cliff, kept for states whose parameters cannot be safely interpolated. Supplying blend replaces the cliff with empirical-Bayes-style shrinkage: every non-empty bucket serves blend(local, global, w) with weight w = n / (n + prior_rows), so the global fit acts as a prior worth prior_rows rows of evidence. The default prior of min_rows / 4 puts w = 0.8 at exactly min_rows rows — the local fit dominates right where the old cliff granted it full weight — while thinner buckets shade smoothly toward the global fit instead of a barely-qualified local fit serving its sampling noise as a constant bias.

FittedBuckets dataclass

apply

apply(
    lead: FloatArray, use: Callable[[S, ndarray], None]
) -> None

Group rows by bucket state and invoke use(state, row_indices).

renormalize_weights

renormalize_weights(
    weights: FloatArray, availability: BoolArray
) -> FloatArray

Per-row weights over available sources, renormalized to sum to 1.

Rows with no available source get all-zero weights.

masked_average

masked_average(
    values: FloatArray,
    availability: BoolArray,
    weights: FloatArray | None = None,
) -> FloatArray

Weighted average over available sources; NaN where none are available.

finalize_point

finalize_point(
    point: FloatArray,
    kind: TargetKind,
    variable: VariableSpec | None = None,
) -> FloatArray

Clip probability targets into [0, 1]; clamp declared variable bounds.

The bounds clamp mirrors the serve boundary, so the backtest scores the quantity a user would actually receive — never a negative wind speed. NaN (no prediction) passes through unchanged.

finalize_quantiles

finalize_quantiles(
    quantiles: FloatArray,
    kind: TargetKind,
    variable: VariableSpec | None = None,
) -> FloatArray

Monotone rearrangement plus the same clamps finalize_point applies.

Row-wise np.sort is the standard fix for quantile crossing: it never worsens any proper scoring rule and guarantees emitted quantiles are a valid distribution. Every quantile emitter must route through here.

quantile_blend_result

quantile_blend_result(
    quantiles: FloatArray,
    levels: tuple[float, ...],
    kind: TargetKind,
    variable: VariableSpec | None,
) -> BlendResult

Finalized quantiles served with their own median as the point.

coefficient_state

coefficient_state(
    method_id: str,
    variable: VariableSpec | None,
    fit_status: str,
    parameters: FloatArray | None,
    names: tuple[str, ...],
) -> dict[str, object]

Glass-box to_state payload shared by coefficient-vector heads.

fit_shrunk_buckets

fit_shrunk_buckets(
    product: Product,
    lead_hours: FloatArray,
    fit_one: Callable[[ndarray], FloatArray],
) -> FittedBuckets[FloatArray]

Per-bucket coefficient fits with linear shrinkage toward the global fit.

Registry

Blender registry: method_id -> factory. Factories, never instances — the backtest engine constructs a fresh blender per fold (a leakage defense).

UnknownMethodError

Bases: KeyError

No blender is registered under the requested method_id.

supports_product

supports_product(
    method_id: str,
    product: Product,
    variable: VariableSpec | None = None,
) -> bool

Whether a method's feature assumptions match the product contract.