Source code for factor_agent.candidate
"""A factor candidate as the proposer writes it: a stated mechanism and the KunQuant code."""
import dataclasses
from collections.abc import Collection
from dataclasses import dataclass
[docs]
@dataclass(frozen=True)
class Candidate:
"""One proposed factor.
``code`` is a Python body over the vocabulary's operators that ends by
assigning the factor to ``alpha``. A candidate states no direction:
which way it predicts is read from its training IC.
"""
hypothesis: str
mechanism_type: str
horizon_rationale: str
description: str
code: str
[docs]
@classmethod
def from_dict(cls, item: dict, mechanism_types: Collection[str]) -> "Candidate":
"""Build a candidate from the proposer's JSON object, whose type must be one of ``mechanism_types``.
Raises
------
ValueError
If a field is missing or extra, ``mechanism_type`` is not one of
``mechanism_types``, or a field is not a string.
"""
if not isinstance(item, dict):
raise ValueError(f"a candidate must be a JSON object, got {type(item).__name__}")
names = [field.name for field in dataclasses.fields(cls)]
missing = [name for name in names if name not in item]
extra = sorted(set(item) - set(names))
if missing or extra:
raise ValueError(f"candidate fields: missing {missing}, unexpected {extra}")
if item["mechanism_type"] not in mechanism_types:
raise ValueError(
f"mechanism_type {item['mechanism_type']!r} is not one of {list(mechanism_types)}"
)
for name in names:
if not isinstance(item[name], str):
raise ValueError(f"{name} must be a string, got {type(item[name]).__name__}")
return cls(**{name: item[name] for name in names})
[docs]
def to_dict(self) -> dict:
"""Return the candidate as the JSON object it was parsed from."""
return dataclasses.asdict(self)